localStorage kaldırıldı, herşey [EasyPeasyStore][0] taşındı
This commit is contained in:
parent
904f8ed447
commit
7f7c6fab36
19 changed files with 440 additions and 431 deletions
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
||||||
namespace Sozsoft.Platform.Migrations
|
namespace Sozsoft.Platform.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PlatformDbContext))]
|
[DbContext(typeof(PlatformDbContext))]
|
||||||
[Migration("20260708113003_Initial")]
|
[Migration("20260709175406_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -4,23 +4,8 @@ import { useNavigate } from 'react-router-dom'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { Button } from '@/components/ui'
|
import { Button } from '@/components/ui'
|
||||||
import { BasketItem, BillingCycle } from '@/proxy/order/models'
|
import type { OrderSuccessData } from '@/store/client.model'
|
||||||
import { CustomTenantDto } from '@/proxy/config/models'
|
import type { BasketItem } from '@/proxy/order/models'
|
||||||
|
|
||||||
export interface OrderSuccessData {
|
|
||||||
orderId: string
|
|
||||||
tenant: CustomTenantDto
|
|
||||||
items: BasketItem[]
|
|
||||||
billingCycle: BillingCycle
|
|
||||||
period: number
|
|
||||||
licenseStartTime?: string
|
|
||||||
licenseEndTime?: string
|
|
||||||
subtotal: number
|
|
||||||
vatTotal: number
|
|
||||||
commission: number
|
|
||||||
total: number
|
|
||||||
installment?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OrderSuccessProps {
|
interface OrderSuccessProps {
|
||||||
orderId: string
|
orderId: string
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
FaCheckCircle,
|
FaCheckCircle,
|
||||||
FaSitemap,
|
FaSitemap,
|
||||||
} from 'react-icons/fa'
|
} from 'react-icons/fa'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
|
@ -30,6 +30,8 @@ import {
|
||||||
getCountries,
|
getCountries,
|
||||||
getDistricts,
|
getDistricts,
|
||||||
} from '@/services/location.service'
|
} from '@/services/location.service'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
|
import type { TenantFormDraft } from '@/store/client.model'
|
||||||
|
|
||||||
interface LocationOption<T> {
|
interface LocationOption<T> {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -38,8 +40,6 @@ interface LocationOption<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
const TENANT_DATA_STORAGE_KEY = 'tenantData'
|
|
||||||
const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft'
|
|
||||||
const DEFAULT_PHONE_FORMAT = '000 000 000 000 000'
|
const DEFAULT_PHONE_FORMAT = '000 000 000 000 000'
|
||||||
const TURKEY_PHONE_FORMAT = '(000) 000-0000'
|
const TURKEY_PHONE_FORMAT = '(000) 000-0000'
|
||||||
const REQUIRED_NEW_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [
|
const REQUIRED_NEW_CUSTOMER_FIELDS: Array<keyof CustomTenantDto> = [
|
||||||
|
|
@ -169,11 +169,6 @@ const getUniqueLocationOptions = <T,>(
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
type TenantFormDraft = Partial<CustomTenantDto> & {
|
|
||||||
isExisting?: boolean
|
|
||||||
foundTenantName?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TenantFormProps {
|
interface TenantFormProps {
|
||||||
onSubmit: (tenant: CustomTenantDto) => void
|
onSubmit: (tenant: CustomTenantDto) => void
|
||||||
onBack: () => void
|
onBack: () => void
|
||||||
|
|
@ -196,22 +191,25 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
||||||
const [foundTenantName, setFoundTenantName] = useState('')
|
const [foundTenantName, setFoundTenantName] = useState('')
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const { tenantData, tenantFormDraft } = useStoreState(
|
||||||
|
(state) => state.client.order,
|
||||||
|
)
|
||||||
|
const { setTenantFormDraft, clearTenant } = useStoreActions(
|
||||||
|
(actions) => actions.client.order,
|
||||||
|
)
|
||||||
|
const hasRestoredTenantData = useRef(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedDraft = localStorage.getItem(TENANT_FORM_DRAFT_STORAGE_KEY)
|
if (hasRestoredTenantData.current) return
|
||||||
const savedTenant = localStorage.getItem(TENANT_DATA_STORAGE_KEY)
|
hasRestoredTenantData.current = true
|
||||||
const savedData = savedDraft || savedTenant
|
|
||||||
|
const savedData: TenantFormDraft | null = tenantFormDraft || tenantData
|
||||||
if (!savedData) return
|
if (!savedData) return
|
||||||
|
|
||||||
try {
|
setIsExisting(savedData.isExisting ?? true)
|
||||||
const parsed = JSON.parse(savedData) as TenantFormDraft
|
setFoundTenantName(savedData.foundTenantName || savedData.name || '')
|
||||||
setIsExisting(parsed.isExisting ?? true)
|
setFormData(savedData)
|
||||||
setFoundTenantName(parsed.foundTenantName || parsed.name || '')
|
}, [tenantData, tenantFormDraft])
|
||||||
setFormData(parsed)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Müşteri form taslağı okunamadı:', error)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadCountries = async () => {
|
const loadCountries = async () => {
|
||||||
|
|
@ -259,15 +257,12 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(formData).length === 0) return
|
if (Object.keys(formData).length === 0) return
|
||||||
|
|
||||||
localStorage.setItem(
|
setTenantFormDraft({
|
||||||
TENANT_FORM_DRAFT_STORAGE_KEY,
|
|
||||||
JSON.stringify({
|
|
||||||
...formData,
|
...formData,
|
||||||
isExisting,
|
isExisting,
|
||||||
foundTenantName,
|
foundTenantName,
|
||||||
}),
|
})
|
||||||
)
|
}, [formData, foundTenantName, isExisting, setTenantFormDraft])
|
||||||
}, [formData, foundTenantName, isExisting])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadLocationOptions = async () => {
|
const loadLocationOptions = async () => {
|
||||||
|
|
@ -399,8 +394,7 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
||||||
setPhoneError('')
|
setPhoneError('')
|
||||||
setDistricts([])
|
setDistricts([])
|
||||||
setFormData(nextIsExisting ? {} : getDefaultNewCustomerData())
|
setFormData(nextIsExisting ? {} : getDefaultNewCustomerData())
|
||||||
localStorage.removeItem(TENANT_DATA_STORAGE_KEY)
|
clearTenant()
|
||||||
localStorage.removeItem(TENANT_FORM_DRAFT_STORAGE_KEY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetExistingTenantDetails = (name: string) => {
|
const resetExistingTenantDetails = (name: string) => {
|
||||||
|
|
@ -679,8 +673,8 @@ export const TenantForm: React.FC<TenantFormProps> = ({ onSubmit }) => {
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FaPhone className="w-4 h-4 text-gray-500" />
|
<FaPhone className="w-4 h-4 text-gray-500" />
|
||||||
<span className="font-medium">{translate('::blog.categories.mobile')}:</span>
|
<span className="font-medium">{translate('::Abp.Identity.User.UserInformation.PhoneNumber')}:</span>
|
||||||
<span>{formData.mobileNumber}</span>
|
<span>{formData.phoneNumber}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,32 @@ import { Injections } from './store'
|
||||||
import { GridOptionsEditDto } from '../proxy/form/models'
|
import { GridOptionsEditDto } from '../proxy/form/models'
|
||||||
import setNull from '../utils/setNull'
|
import setNull from '../utils/setNull'
|
||||||
import { ListState } from '@/proxy/admin/list-form/models'
|
import { ListState } from '@/proxy/admin/list-form/models'
|
||||||
import { Message } from '@/proxy/ai/models'
|
import { Conversation } from '@/proxy/ai/models'
|
||||||
|
|
||||||
|
export type DashboardLayout = 'default' | 'wide-center' | 'balanced' | 'wide-left' | 'wide-right'
|
||||||
|
|
||||||
|
export interface DashboardState {
|
||||||
|
dashboardLayout: DashboardLayout
|
||||||
|
hiddenWidgetIds: string[]
|
||||||
|
widgetOrder: {
|
||||||
|
left: string[]
|
||||||
|
center: string[]
|
||||||
|
right: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiConversationsState {
|
||||||
|
items: Conversation[]
|
||||||
|
aiActiveConversationId: string | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface AdminStoreModel {
|
export interface AdminStoreModel {
|
||||||
lists: {
|
lists: {
|
||||||
values: GridOptionsEditDto | undefined
|
values: GridOptionsEditDto | undefined
|
||||||
states: ListState[]
|
states: ListState[]
|
||||||
}
|
}
|
||||||
messages: {
|
dashboard: DashboardState
|
||||||
aiPosts: Message[]
|
aiConversations: AiConversationsState
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AdminStoreActions {
|
export interface AdminStoreActions {
|
||||||
|
|
@ -27,21 +43,36 @@ export interface AdminStoreActions {
|
||||||
>
|
>
|
||||||
setStates: Action<AdminStoreModel['lists'], ListState>
|
setStates: Action<AdminStoreModel['lists'], ListState>
|
||||||
}
|
}
|
||||||
messages: {
|
dashboard: {
|
||||||
addAiPost: Action<AdminStoreModel['messages'], Message>
|
setDashboardLayout: Action<DashboardState, DashboardLayout>
|
||||||
setAiPosts: Action<AdminStoreModel['messages'], Message[]>
|
setHiddenWidgetIds: Action<DashboardState, string[]>
|
||||||
|
setWidgetOrder: Action<DashboardState, DashboardState['widgetOrder']>
|
||||||
|
reset: Action<DashboardState>
|
||||||
}
|
}
|
||||||
|
setAiConversations: Action<AdminStoreModel, AiConversationsState>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AdminModel = AdminStoreModel & AdminStoreActions
|
export type AdminModel = AdminStoreModel & AdminStoreActions
|
||||||
|
|
||||||
|
const initialDashboardState: DashboardState = {
|
||||||
|
dashboardLayout: 'default',
|
||||||
|
hiddenWidgetIds: [],
|
||||||
|
widgetOrder: {
|
||||||
|
left: [],
|
||||||
|
center: [],
|
||||||
|
right: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
const initialState: AdminStoreModel = {
|
const initialState: AdminStoreModel = {
|
||||||
lists: {
|
lists: {
|
||||||
values: undefined,
|
values: undefined,
|
||||||
states: [],
|
states: [],
|
||||||
},
|
},
|
||||||
messages: {
|
dashboard: initialDashboardState,
|
||||||
aiPosts: [],
|
aiConversations: {
|
||||||
|
items: [],
|
||||||
|
aiActiveConversationId: null,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,14 +104,25 @@ export const adminModel: AdminModel = {
|
||||||
actions.setListFormValues(result.data)
|
actions.setListFormValues(result.data)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
messages: {
|
dashboard: {
|
||||||
...initialState.messages,
|
...initialState.dashboard,
|
||||||
addAiPost: action((state, payload) => {
|
setDashboardLayout: action((state, payload) => {
|
||||||
state.aiPosts = [...state.aiPosts, payload]
|
state.dashboardLayout = payload
|
||||||
}),
|
}),
|
||||||
|
setHiddenWidgetIds: action((state, payload) => {
|
||||||
setAiPosts: action((state, payload) => {
|
state.hiddenWidgetIds = payload
|
||||||
state.aiPosts = payload
|
}),
|
||||||
|
setWidgetOrder: action((state, payload) => {
|
||||||
|
state.widgetOrder = payload
|
||||||
|
}),
|
||||||
|
reset: action((state) => {
|
||||||
|
state.dashboardLayout = initialDashboardState.dashboardLayout
|
||||||
|
state.hiddenWidgetIds = initialDashboardState.hiddenWidgetIds
|
||||||
|
state.widgetOrder = initialDashboardState.widgetOrder
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
aiConversations: initialState.aiConversations,
|
||||||
|
setAiConversations: action((state, payload) => {
|
||||||
|
state.aiConversations = payload
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
122
ui/src/store/client.model.ts
Normal file
122
ui/src/store/client.model.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
import type { Action } from 'easy-peasy'
|
||||||
|
import { action } from 'easy-peasy'
|
||||||
|
import type { CustomTenantDto } from '@/proxy/config/models'
|
||||||
|
import type { OrderDto } from '@/proxy/order/models'
|
||||||
|
import type { FileItem } from '@/types/fileManagement'
|
||||||
|
import { initialCartState, type CartState } from '../utils/cartUtils'
|
||||||
|
|
||||||
|
export type TenantFormDraft = Partial<CustomTenantDto> & {
|
||||||
|
isExisting?: boolean
|
||||||
|
foundTenantName?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrderSuccessData = OrderDto & {
|
||||||
|
orderId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrderState {
|
||||||
|
tenantData: CustomTenantDto | null
|
||||||
|
tenantFormDraft: TenantFormDraft | null
|
||||||
|
orderId: string | null
|
||||||
|
orderSuccessData: OrderSuccessData | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileManagerClipboard {
|
||||||
|
operation: 'copy' | 'cut'
|
||||||
|
items: FileItem[]
|
||||||
|
sourceFolder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileManagerState {
|
||||||
|
clipboard: FileManagerClipboard | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DeveloperKitState {
|
||||||
|
lastCreateTableScript: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientStoreModel {
|
||||||
|
cartState: CartState
|
||||||
|
order: OrderState
|
||||||
|
fileManager: FileManagerState
|
||||||
|
developerKit: DeveloperKitState
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClientStoreActions {
|
||||||
|
setCartState: Action<ClientStoreModel, CartState>
|
||||||
|
order: {
|
||||||
|
setTenantData: Action<OrderState, CustomTenantDto>
|
||||||
|
setTenantFormDraft: Action<OrderState, TenantFormDraft>
|
||||||
|
setOrderResult: Action<OrderState, OrderSuccessData>
|
||||||
|
clearTenant: Action<OrderState>
|
||||||
|
clearResult: Action<OrderState>
|
||||||
|
}
|
||||||
|
fileManager: {
|
||||||
|
setClipboard: Action<FileManagerState, FileManagerClipboard>
|
||||||
|
clearClipboard: Action<FileManagerState>
|
||||||
|
}
|
||||||
|
developerKit: {
|
||||||
|
setLastCreateTableScript: Action<DeveloperKitState, string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClientModel = ClientStoreModel & ClientStoreActions
|
||||||
|
|
||||||
|
const initialState: ClientStoreModel = {
|
||||||
|
cartState: initialCartState,
|
||||||
|
order: {
|
||||||
|
tenantData: null,
|
||||||
|
tenantFormDraft: null,
|
||||||
|
orderId: null,
|
||||||
|
orderSuccessData: null,
|
||||||
|
},
|
||||||
|
fileManager: {
|
||||||
|
clipboard: null,
|
||||||
|
},
|
||||||
|
developerKit: {
|
||||||
|
lastCreateTableScript: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clientModel: ClientModel = {
|
||||||
|
...initialState,
|
||||||
|
setCartState: action((state, payload) => {
|
||||||
|
state.cartState = payload
|
||||||
|
}),
|
||||||
|
order: {
|
||||||
|
...initialState.order,
|
||||||
|
setTenantData: action((state, payload) => {
|
||||||
|
state.tenantData = payload
|
||||||
|
}),
|
||||||
|
setTenantFormDraft: action((state, payload) => {
|
||||||
|
state.tenantFormDraft = payload
|
||||||
|
}),
|
||||||
|
setOrderResult: action((state, payload) => {
|
||||||
|
state.orderId = payload.orderId
|
||||||
|
state.orderSuccessData = payload
|
||||||
|
}),
|
||||||
|
clearTenant: action((state) => {
|
||||||
|
state.tenantData = null
|
||||||
|
state.tenantFormDraft = null
|
||||||
|
}),
|
||||||
|
clearResult: action((state) => {
|
||||||
|
state.orderId = null
|
||||||
|
state.orderSuccessData = null
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
fileManager: {
|
||||||
|
...initialState.fileManager,
|
||||||
|
setClipboard: action((state, payload) => {
|
||||||
|
state.clipboard = payload
|
||||||
|
}),
|
||||||
|
clearClipboard: action((state) => {
|
||||||
|
state.clipboard = null
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
developerKit: {
|
||||||
|
...initialState.developerKit,
|
||||||
|
setLastCreateTableScript: action((state, payload) => {
|
||||||
|
state.lastCreateTableScript = payload
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ export type LocaleModel = LocaleStoreModel & LocaleStoreActions
|
||||||
const initialState: LocaleStoreModel = {
|
const initialState: LocaleStoreModel = {
|
||||||
currentLang: appConfig.locale,
|
currentLang: appConfig.locale,
|
||||||
currentUiVersion: appConfig.uiVersion,
|
currentUiVersion: appConfig.uiVersion,
|
||||||
currentTenantName: undefined
|
currentTenantName: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const localeModel: LocaleModel = {
|
export const localeModel: LocaleModel = {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { AbpConfigModel, abpConfigModel } from './abpConfig.model'
|
||||||
import { AdminModel, adminModel } from './admin.model'
|
import { AdminModel, adminModel } from './admin.model'
|
||||||
import { AuthModel, authModel } from './auth.model'
|
import { AuthModel, authModel } from './auth.model'
|
||||||
import { BaseModel, baseModel } from './base.model'
|
import { BaseModel, baseModel } from './base.model'
|
||||||
|
import { ClientModel, clientModel } from './client.model'
|
||||||
import { LocaleModel, localeModel } from './locale.model'
|
import { LocaleModel, localeModel } from './locale.model'
|
||||||
import { ThemeModel, themeModel } from './theme.model'
|
import { ThemeModel, themeModel } from './theme.model'
|
||||||
import { refreshToken } from '../services/auth.service'
|
import { refreshToken } from '../services/auth.service'
|
||||||
|
|
@ -17,6 +18,7 @@ export interface StoreModel {
|
||||||
auth: AuthModel
|
auth: AuthModel
|
||||||
base: BaseModel
|
base: BaseModel
|
||||||
locale: LocaleModel
|
locale: LocaleModel
|
||||||
|
client: ClientModel
|
||||||
admin: AdminModel
|
admin: AdminModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,11 +40,11 @@ export const store = createStore<StoreModel>(
|
||||||
auth: authModel,
|
auth: authModel,
|
||||||
base: baseModel,
|
base: baseModel,
|
||||||
locale: localeModel,
|
locale: localeModel,
|
||||||
|
client: clientModel,
|
||||||
admin: adminModel,
|
admin: adminModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
allow: ['auth', 'theme', 'locale', 'admin'],
|
allow: ['auth', 'theme', 'locale', 'client', 'admin'],
|
||||||
storage: 'localStorage',
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { BillingCycle, BasketItem, ProductDto } from "@/proxy/order/models";
|
import { BillingCycle, BasketItem, ProductDto } from '@/proxy/order/models'
|
||||||
|
|
||||||
export interface CartState {
|
export interface CartState {
|
||||||
items: BasketItem[];
|
items: BasketItem[]
|
||||||
subtotal: number;
|
subtotal: number
|
||||||
vatTotal: number;
|
vatTotal: number
|
||||||
total: number;
|
total: number
|
||||||
globalBillingCycle: BillingCycle;
|
globalBillingCycle: BillingCycle
|
||||||
globalPeriod: number;
|
globalPeriod: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialCartState: CartState = {
|
export const initialCartState: CartState = {
|
||||||
|
|
@ -15,29 +15,24 @@ export const initialCartState: CartState = {
|
||||||
vatTotal: 0,
|
vatTotal: 0,
|
||||||
total: 0,
|
total: 0,
|
||||||
globalBillingCycle: 'yearly',
|
globalBillingCycle: 'yearly',
|
||||||
globalPeriod: 1
|
globalPeriod: 1,
|
||||||
};
|
}
|
||||||
|
|
||||||
// Key for localStorage
|
|
||||||
const CART_STORAGE_KEY = 'cartState';
|
|
||||||
|
|
||||||
export const calculateItemPrice = (
|
export const calculateItemPrice = (
|
||||||
product: ProductDto,
|
product: ProductDto,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
billingCycle: BillingCycle,
|
billingCycle: BillingCycle,
|
||||||
period: number = 1
|
period: number = 1,
|
||||||
): number => {
|
): number => {
|
||||||
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!;
|
const price = billingCycle === 'monthly' ? product.monthlyPrice! : product.yearlyPrice!
|
||||||
const vatRate = product.vatRate ?? 0.2;
|
const vatRate = product.vatRate ?? 0.2
|
||||||
return roundMoney(price * quantity * period * (1 + vatRate));
|
return roundMoney(price * quantity * period * (1 + vatRate))
|
||||||
};
|
}
|
||||||
|
|
||||||
const roundMoney = (value: number): number => Math.round(value * 100) / 100;
|
const roundMoney = (value: number): number => Math.round(value * 100) / 100
|
||||||
|
|
||||||
const calculateItemVatAmount = (
|
const calculateItemVatAmount = (totalPrice: number, vatRate: number = 0.2): number =>
|
||||||
totalPrice: number,
|
vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0
|
||||||
vatRate: number = 0.2,
|
|
||||||
): number => (vatRate > 0 ? roundMoney(totalPrice - totalPrice / (1 + vatRate)) : 0);
|
|
||||||
|
|
||||||
const withItemVat = (
|
const withItemVat = (
|
||||||
product: ProductDto,
|
product: ProductDto,
|
||||||
|
|
@ -45,8 +40,8 @@ const withItemVat = (
|
||||||
billingCycle: BillingCycle,
|
billingCycle: BillingCycle,
|
||||||
period: number,
|
period: number,
|
||||||
): BasketItem => {
|
): BasketItem => {
|
||||||
const vatRate = product.vatRate ?? 0.2;
|
const vatRate = product.vatRate ?? 0.2
|
||||||
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period);
|
const totalPrice = calculateItemPrice(product, quantity, billingCycle, period)
|
||||||
return {
|
return {
|
||||||
product,
|
product,
|
||||||
quantity,
|
quantity,
|
||||||
|
|
@ -54,135 +49,128 @@ const withItemVat = (
|
||||||
vatRate,
|
vatRate,
|
||||||
vatAmount: calculateItemVatAmount(totalPrice, vatRate),
|
vatAmount: calculateItemVatAmount(totalPrice, vatRate),
|
||||||
totalPrice,
|
totalPrice,
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const calculateCartTotals = (items: BasketItem[]) => {
|
const calculateCartTotals = (items: BasketItem[]) => {
|
||||||
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0));
|
const total = roundMoney(items.reduce((sum, item) => sum + item.totalPrice, 0))
|
||||||
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0));
|
const vatTotal = roundMoney(items.reduce((sum, item) => sum + (item.vatAmount ?? 0), 0))
|
||||||
return {
|
return {
|
||||||
subtotal: roundMoney(total - vatTotal),
|
subtotal: roundMoney(total - vatTotal),
|
||||||
vatTotal,
|
vatTotal,
|
||||||
total,
|
total,
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const addItemToCart = (
|
export const addItemToCart = (
|
||||||
cartState: CartState,
|
cartState: CartState,
|
||||||
product: ProductDto,
|
product: ProductDto,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
billingCycle: BillingCycle
|
billingCycle: BillingCycle,
|
||||||
): CartState => {
|
): CartState => {
|
||||||
const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle);
|
const isAlreadyInCart = isItemInCartState(cartState.items, product.id, billingCycle)
|
||||||
|
|
||||||
if (!product.isQuantityBased && isAlreadyInCart) {
|
if (!product.isQuantityBased && isAlreadyInCart) {
|
||||||
return cartState;
|
return cartState
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingItemIndex = cartState.items.findIndex(
|
const existingItemIndex = cartState.items.findIndex(
|
||||||
item => item.product.id === product.id && item.billingCycle === billingCycle
|
(item) => item.product.id === product.id && item.billingCycle === billingCycle,
|
||||||
);
|
)
|
||||||
|
|
||||||
let newItems;
|
let newItems
|
||||||
if (existingItemIndex > -1) {
|
if (existingItemIndex > -1) {
|
||||||
newItems = [...cartState.items];
|
newItems = [...cartState.items]
|
||||||
newItems[existingItemIndex].quantity += quantity;
|
newItems[existingItemIndex].quantity += quantity
|
||||||
newItems[existingItemIndex] = withItemVat(
|
newItems[existingItemIndex] = withItemVat(
|
||||||
product,
|
product,
|
||||||
newItems[existingItemIndex].quantity,
|
newItems[existingItemIndex].quantity,
|
||||||
billingCycle,
|
billingCycle,
|
||||||
cartState.globalPeriod,
|
cartState.globalPeriod,
|
||||||
);
|
)
|
||||||
} else {
|
} else {
|
||||||
const actualQuantity = !product.isQuantityBased ? 1 : quantity;
|
const actualQuantity = !product.isQuantityBased ? 1 : quantity
|
||||||
newItems = [
|
newItems = [
|
||||||
...cartState.items,
|
...cartState.items,
|
||||||
withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod),
|
withItemVat(product, actualQuantity, billingCycle, cartState.globalPeriod),
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
|
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
|
||||||
saveCartToLocalStorage(updatedState);
|
return updatedState
|
||||||
return updatedState;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const removeItemFromCart = (cartState: CartState, id: string): CartState => {
|
export const removeItemFromCart = (cartState: CartState, id: string): CartState => {
|
||||||
const newItems = cartState.items.filter(item => item.product.id !== id);
|
const newItems = cartState.items.filter((item) => item.product.id !== id)
|
||||||
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
|
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
|
||||||
saveCartToLocalStorage(updatedState);
|
return updatedState
|
||||||
return updatedState;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const updateCartItemQuantity = (cartState: CartState, id: string, quantity: number): CartState => {
|
export const updateCartItemQuantity = (
|
||||||
const newItems = cartState.items.map(item => {
|
cartState: CartState,
|
||||||
|
id: string,
|
||||||
|
quantity: number,
|
||||||
|
): CartState => {
|
||||||
|
const newItems = cartState.items
|
||||||
|
.map((item) => {
|
||||||
if (item.product.id === id) {
|
if (item.product.id === id) {
|
||||||
if (quantity <= 0) return null;
|
if (quantity <= 0) return null
|
||||||
if (!item.product.isQuantityBased) return item;
|
if (!item.product.isQuantityBased) return item
|
||||||
|
|
||||||
return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod);
|
return withItemVat(item.product, quantity, item.billingCycle, cartState.globalPeriod)
|
||||||
}
|
}
|
||||||
return item;
|
return item
|
||||||
}).filter(Boolean) as BasketItem[];
|
})
|
||||||
|
.filter(Boolean) as BasketItem[]
|
||||||
|
|
||||||
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) };
|
const updatedState = { ...cartState, items: newItems, ...calculateCartTotals(newItems) }
|
||||||
saveCartToLocalStorage(updatedState);
|
return updatedState
|
||||||
return updatedState;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const clearCart = (): CartState => {
|
export const clearCart = (): CartState => {
|
||||||
localStorage.removeItem(CART_STORAGE_KEY);
|
return initialCartState
|
||||||
return initialCartState;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => {
|
export const setCartGlobalBillingCycle = (cartState: CartState, cycle: BillingCycle): CartState => {
|
||||||
const newItems = cartState.items.map(item =>
|
const newItems = cartState.items.map((item) =>
|
||||||
withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod)
|
withItemVat(item.product, item.quantity, cycle, cartState.globalPeriod),
|
||||||
);
|
)
|
||||||
const updatedState = { ...cartState, globalBillingCycle: cycle, items: newItems, ...calculateCartTotals(newItems) };
|
const updatedState = {
|
||||||
saveCartToLocalStorage(updatedState);
|
...cartState,
|
||||||
return updatedState;
|
globalBillingCycle: cycle,
|
||||||
};
|
items: newItems,
|
||||||
|
...calculateCartTotals(newItems),
|
||||||
|
}
|
||||||
|
return updatedState
|
||||||
|
}
|
||||||
|
|
||||||
export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => {
|
export const setCartGlobalPeriod = (cartState: CartState, period: number): CartState => {
|
||||||
const newItems = cartState.items.map(item =>
|
const newItems = cartState.items.map((item) =>
|
||||||
withItemVat(item.product, item.quantity, item.billingCycle, period)
|
withItemVat(item.product, item.quantity, item.billingCycle, period),
|
||||||
);
|
)
|
||||||
const updatedState = { ...cartState, globalPeriod: period, items: newItems, ...calculateCartTotals(newItems) };
|
const updatedState = {
|
||||||
saveCartToLocalStorage(updatedState);
|
...cartState,
|
||||||
return updatedState;
|
globalPeriod: period,
|
||||||
};
|
items: newItems,
|
||||||
|
...calculateCartTotals(newItems),
|
||||||
|
}
|
||||||
|
return updatedState
|
||||||
|
}
|
||||||
|
|
||||||
export const isItemInCart = (cartState: CartState, productId: string, billingCycle: BillingCycle): boolean => {
|
export const isItemInCart = (
|
||||||
|
cartState: CartState,
|
||||||
|
productId: string,
|
||||||
|
billingCycle: BillingCycle,
|
||||||
|
): boolean => {
|
||||||
return cartState.items.some(
|
return cartState.items.some(
|
||||||
item => item.product.id === productId && item.billingCycle === billingCycle
|
(item) => item.product.id === productId && item.billingCycle === billingCycle,
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export const isItemInCartState = (items: BasketItem[], productId: string, billingCycle: BillingCycle): boolean => {
|
export const isItemInCartState = (
|
||||||
return items.some(
|
items: BasketItem[],
|
||||||
item => item.product.id === productId && item.billingCycle === billingCycle
|
productId: string,
|
||||||
);
|
billingCycle: BillingCycle,
|
||||||
};
|
): boolean => {
|
||||||
|
return items.some((item) => item.product.id === productId && item.billingCycle === billingCycle)
|
||||||
// Utility: Save to localStorage
|
}
|
||||||
const saveCartToLocalStorage = (cartState: CartState) => {
|
|
||||||
localStorage.setItem(CART_STORAGE_KEY, JSON.stringify(cartState));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Utility: Load from localStorage
|
|
||||||
export const loadCartFromLocalStorage = (): CartState => {
|
|
||||||
const saved = localStorage.getItem(CART_STORAGE_KEY);
|
|
||||||
if (!saved) return initialCartState;
|
|
||||||
|
|
||||||
const parsed = JSON.parse(saved) as CartState;
|
|
||||||
const period = parsed.globalPeriod || 1;
|
|
||||||
const items = (parsed.items || []).map(item =>
|
|
||||||
withItemVat(item.product, item.quantity, item.billingCycle, period)
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...initialCartState,
|
|
||||||
...parsed,
|
|
||||||
items,
|
|
||||||
...calculateCartTotals(items),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useState, useEffect, useCallback, useRef, type MouseEvent } from 'react'
|
import { useState, useEffect, useCallback, useRef, type MouseEvent } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
import { Button, Input, Select, toast, Notification, Spinner } from '@/components/ui'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreActions, useStoreState } from '@/store'
|
||||||
import {
|
import {
|
||||||
FaFolder,
|
FaFolder,
|
||||||
FaCloudUploadAlt,
|
FaCloudUploadAlt,
|
||||||
|
|
@ -50,6 +50,13 @@ const FileManager = () => {
|
||||||
|
|
||||||
const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId)
|
const authTenantId = useStoreState((state) => state.auth.tenant?.tenantId)
|
||||||
const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName)
|
const authTenantName = useStoreState((state) => state.auth.tenant?.tenantName)
|
||||||
|
const fileManagerClipboard = useStoreState((state) => state.client.fileManager.clipboard)
|
||||||
|
const setFileManagerClipboard = useStoreActions(
|
||||||
|
(actions) => actions.client.fileManager.setClipboard,
|
||||||
|
)
|
||||||
|
const clearFileManagerClipboard = useStoreActions(
|
||||||
|
(actions) => actions.client.fileManager.clearClipboard,
|
||||||
|
)
|
||||||
const isHostContext = !authTenantId
|
const isHostContext = !authTenantId
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
|
@ -447,22 +454,7 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clipboard state for paste button
|
const hasClipboardData = !!fileManagerClipboard
|
||||||
const [hasClipboardData, setHasClipboardData] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkClipboard = () => {
|
|
||||||
setHasClipboardData(!!localStorage.getItem('fileManager_clipboard'))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check initially
|
|
||||||
checkClipboard()
|
|
||||||
|
|
||||||
// Check periodically (in case another tab changes it)
|
|
||||||
const interval = setInterval(checkClipboard, 1000)
|
|
||||||
|
|
||||||
return () => clearInterval(interval)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Keyboard shortcuts
|
// Keyboard shortcuts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -477,14 +469,12 @@ const FileManager = () => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (selectedItems.length > 0) {
|
if (selectedItems.length > 0) {
|
||||||
copySelectedItems()
|
copySelectedItems()
|
||||||
setHasClipboardData(true)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'x':
|
case 'x':
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (selectedItems.length > 0) {
|
if (selectedItems.length > 0) {
|
||||||
cutSelectedItems()
|
cutSelectedItems()
|
||||||
setHasClipboardData(true)
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'v':
|
case 'v':
|
||||||
|
|
@ -616,16 +606,11 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (copyableItems.length > 0) {
|
if (copyableItems.length > 0) {
|
||||||
// Store in local storage or context for paste operation
|
setFileManagerClipboard({
|
||||||
localStorage.setItem(
|
|
||||||
'fileManager_clipboard',
|
|
||||||
JSON.stringify({
|
|
||||||
operation: 'copy',
|
operation: 'copy',
|
||||||
items: copyableItems,
|
items: copyableItems,
|
||||||
sourceFolder: currentFolderId,
|
sourceFolder: currentFolderId,
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
setHasClipboardData(true)
|
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Copied" type="success">
|
<Notification title="Copied" type="success">
|
||||||
{copyableItems.length} item(s) copied to clipboard
|
{copyableItems.length} item(s) copied to clipboard
|
||||||
|
|
@ -651,16 +636,11 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cuttableItems.length > 0) {
|
if (cuttableItems.length > 0) {
|
||||||
// Store in local storage or context for paste operation
|
setFileManagerClipboard({
|
||||||
localStorage.setItem(
|
|
||||||
'fileManager_clipboard',
|
|
||||||
JSON.stringify({
|
|
||||||
operation: 'cut',
|
operation: 'cut',
|
||||||
items: cuttableItems,
|
items: cuttableItems,
|
||||||
sourceFolder: currentFolderId,
|
sourceFolder: currentFolderId,
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
setHasClipboardData(true)
|
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Cut" type="success">
|
<Notification title="Cut" type="success">
|
||||||
{cuttableItems.length} item(s) cut to clipboard
|
{cuttableItems.length} item(s) cut to clipboard
|
||||||
|
|
@ -671,8 +651,7 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const pasteItems = async () => {
|
const pasteItems = async () => {
|
||||||
const clipboardData = localStorage.getItem('fileManager_clipboard')
|
if (!fileManagerClipboard) {
|
||||||
if (!clipboardData) {
|
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Clipboard Empty" type="info">
|
<Notification title="Clipboard Empty" type="info">
|
||||||
No items in clipboard
|
No items in clipboard
|
||||||
|
|
@ -683,8 +662,8 @@ const FileManager = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const clipboard = JSON.parse(clipboardData)
|
const clipboard = fileManagerClipboard
|
||||||
const itemIds = clipboard.items.map((item: FileItemType) => item.id)
|
const itemIds = clipboard.items.map((item) => item.id)
|
||||||
|
|
||||||
if (clipboard.operation === 'copy') {
|
if (clipboard.operation === 'copy') {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
|
@ -724,9 +703,7 @@ const FileManager = () => {
|
||||||
try {
|
try {
|
||||||
await fileManagementService.moveItems(itemIds, currentFolderId, selectedTenant?.id)
|
await fileManagementService.moveItems(itemIds, currentFolderId, selectedTenant?.id)
|
||||||
await fetchItems(currentFolderId)
|
await fetchItems(currentFolderId)
|
||||||
// Clipboard'ı temizle
|
clearFileManagerClipboard()
|
||||||
localStorage.removeItem('fileManager_clipboard')
|
|
||||||
setHasClipboardData(false)
|
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title={translate('::App.Platform.Success')} type="success">
|
<Notification title={translate('::App.Platform.Success')} type="success">
|
||||||
{itemIds.length} item(s) moved successfully
|
{itemIds.length} item(s) moved successfully
|
||||||
|
|
@ -1129,18 +1106,15 @@ const FileManager = () => {
|
||||||
onRename={openRenameModal}
|
onRename={openRenameModal}
|
||||||
onMove={(item) => {
|
onMove={(item) => {
|
||||||
// Move işlemi için öğeyi cut olarak clipboard'a koy
|
// Move işlemi için öğeyi cut olarak clipboard'a koy
|
||||||
|
if (selectedItems.includes(item.id)) {
|
||||||
cutSelectedItems()
|
cutSelectedItems()
|
||||||
if (!selectedItems.includes(item.id)) {
|
} else {
|
||||||
setSelectedItems([item.id])
|
setSelectedItems([item.id])
|
||||||
localStorage.setItem(
|
setFileManagerClipboard({
|
||||||
'fileManager_clipboard',
|
|
||||||
JSON.stringify({
|
|
||||||
operation: 'cut',
|
operation: 'cut',
|
||||||
items: [item],
|
items: [item],
|
||||||
sourceFolder: currentFolderId,
|
sourceFolder: currentFolderId,
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
setHasClipboardData(true)
|
|
||||||
toast.push(
|
toast.push(
|
||||||
<Notification title="Cut" type="success">
|
<Notification title="Cut" type="success">
|
||||||
Item ready to move. Navigate to target folder and paste.
|
Item ready to move. Navigate to target folder and paste.
|
||||||
|
|
|
||||||
|
|
@ -106,8 +106,6 @@ export const editingOptionNewRowPositionOptions = [
|
||||||
|
|
||||||
export const stateStoringTypeOptions = [
|
export const stateStoringTypeOptions = [
|
||||||
{ value: 'custom', label: 'Database' },
|
{ value: 'custom', label: 'Database' },
|
||||||
{ value: 'localStorage', label: 'Local Storage' },
|
|
||||||
{ value: 'sessionStorage', label: 'Session Storage' },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export const editingOptionStartEditActionOptions = [
|
export const editingOptionStartEditActionOptions = [
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useRef, useEffect } from 'react'
|
import React, { useState, useRef, useEffect } from 'react'
|
||||||
import { FaComment, FaPaperPlane, FaPlus, FaRobot, FaTrash } from 'react-icons/fa'
|
import { FaComment, FaPaperPlane, FaPlus, FaRobot, FaTrash } from 'react-icons/fa'
|
||||||
import { useStoreState } from '@/store'
|
import { useStoreActions, useStoreState } from '@/store'
|
||||||
import { Avatar, Button, Drawer, Dropdown, ScrollBar } from '@/components/ui'
|
import { Avatar, Button, Drawer, Dropdown, ScrollBar } from '@/components/ui'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { aiService } from '@/services/ai.service'
|
import { aiService } from '@/services/ai.service'
|
||||||
|
|
@ -18,9 +18,6 @@ import { APP_NAME } from '@/constants/app.constant'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { AI_ASSISTANT } from '@/constants/permission.constant'
|
import { AI_ASSISTANT } from '@/constants/permission.constant'
|
||||||
|
|
||||||
const CONVERSATIONS_STORAGE_KEY = 'AiConversations'
|
|
||||||
const ACTIVE_CONVERSATION_STORAGE_KEY = 'AiActiveConversationId'
|
|
||||||
|
|
||||||
const getConversationTitle = (message: string) =>
|
const getConversationTitle = (message: string) =>
|
||||||
message.length > 42 ? `${message.slice(0, 42).trimEnd()}…` : message
|
message.length > 42 ? `${message.slice(0, 42).trimEnd()}…` : message
|
||||||
|
|
||||||
|
|
@ -75,12 +72,14 @@ const renderFormattedText = (value: string) => {
|
||||||
// Main Component
|
// Main Component
|
||||||
const Assistant = () => {
|
const Assistant = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const [conversations, setConversations] = useState<Conversation[]>([])
|
const storedAiConversations = useStoreState((state) => state.admin.aiConversations)
|
||||||
const [activeConversationId, setActiveConversationId] = useState<string | null>(null)
|
const { setAiConversations } = useStoreActions((actions) => actions.admin)
|
||||||
|
const [conversations, setConversations] = useState<Conversation[]>(storedAiConversations.items)
|
||||||
|
const [activeConversationId, setActiveConversationId] = useState<string | null>(
|
||||||
|
storedAiConversations.aiActiveConversationId,
|
||||||
|
)
|
||||||
const [isConversationsLoaded, setIsConversationsLoaded] = useState(false)
|
const [isConversationsLoaded, setIsConversationsLoaded] = useState(false)
|
||||||
const [isConversationDrawerOpen, setIsConversationDrawerOpen] = useState(false)
|
const [isConversationDrawerOpen, setIsConversationDrawerOpen] = useState(false)
|
||||||
const [, setMessages] = useState<Message[]>([])
|
|
||||||
const aiPosts: Message[] = []
|
|
||||||
const [input, setInput] = useState('')
|
const [input, setInput] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [bot, setBot] = useState<AiBotDto[]>([])
|
const [bot, setBot] = useState<AiBotDto[]>([])
|
||||||
|
|
@ -104,50 +103,12 @@ const Assistant = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messages.length === 0 && aiPosts.length > 0) {
|
if (conversations.length === 0) {
|
||||||
setMessages(aiPosts) // artık doğrudan Message[]
|
|
||||||
}
|
|
||||||
}, [aiPosts])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem(CONVERSATIONS_STORAGE_KEY)
|
|
||||||
const parsed = saved ? JSON.parse(saved) : []
|
|
||||||
const savedConversations = Array.isArray(parsed)
|
|
||||||
? parsed.filter(
|
|
||||||
(conversation): conversation is Conversation =>
|
|
||||||
conversation &&
|
|
||||||
typeof conversation.id === 'string' &&
|
|
||||||
Array.isArray(conversation.messages),
|
|
||||||
)
|
|
||||||
: []
|
|
||||||
|
|
||||||
if (savedConversations.length > 0) {
|
|
||||||
const savedActiveConversationId = localStorage.getItem(ACTIVE_CONVERSATION_STORAGE_KEY)
|
|
||||||
setConversations(savedConversations)
|
|
||||||
setActiveConversationId(
|
|
||||||
savedConversations.some((conversation) => conversation.id === savedActiveConversationId)
|
|
||||||
? savedActiveConversationId
|
|
||||||
: savedConversations[0].id,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
const legacyPosts = JSON.parse(localStorage.getItem('AiPosts') || '[]')
|
|
||||||
const conversation = createConversation()
|
|
||||||
if (Array.isArray(legacyPosts) && legacyPosts.length > 0) {
|
|
||||||
conversation.messages = legacyPosts
|
|
||||||
const firstQuestion = legacyPosts.find((message) => message.role === 'user')?.content
|
|
||||||
conversation.title = firstQuestion
|
|
||||||
? getConversationTitle(String(firstQuestion))
|
|
||||||
: conversation.title
|
|
||||||
}
|
|
||||||
setConversations([conversation])
|
|
||||||
setActiveConversationId(conversation.id)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('AI conversations localStorage parse error', error)
|
|
||||||
const conversation = createConversation()
|
const conversation = createConversation()
|
||||||
setConversations([conversation])
|
setConversations([conversation])
|
||||||
setActiveConversationId(conversation.id)
|
setActiveConversationId(conversation.id)
|
||||||
|
} else if (!conversations.some((conversation) => conversation.id === activeConversationId)) {
|
||||||
|
setActiveConversationId(conversations[0].id)
|
||||||
}
|
}
|
||||||
setIsConversationsLoaded(true)
|
setIsConversationsLoaded(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -155,11 +116,11 @@ const Assistant = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isConversationsLoaded) return
|
if (!isConversationsLoaded) return
|
||||||
|
|
||||||
localStorage.setItem(CONVERSATIONS_STORAGE_KEY, JSON.stringify(conversations))
|
setAiConversations({
|
||||||
if (activeConversationId) {
|
items: conversations,
|
||||||
localStorage.setItem(ACTIVE_CONVERSATION_STORAGE_KEY, activeConversationId)
|
aiActiveConversationId: activeConversationId,
|
||||||
}
|
})
|
||||||
}, [activeConversationId, conversations, isConversationsLoaded])
|
}, [activeConversationId, conversations, isConversationsLoaded, setAiConversations])
|
||||||
|
|
||||||
const activeConversation = conversations.find(
|
const activeConversation = conversations.find(
|
||||||
(conversation) => conversation.id === activeConversationId,
|
(conversation) => conversation.id === activeConversationId,
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
import { useStoreActions } from '@/store'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
const LoadAiPostsFromLocalStorage = () => {
|
|
||||||
const setAiPosts = useStoreActions((actions) => actions.admin.messages.setAiPosts)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const saved = localStorage.getItem('AiPosts')
|
|
||||||
if (saved) {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(saved)
|
|
||||||
if (Array.isArray(parsed) && parsed.every((p) => 'role' in p && 'content' in p)) {
|
|
||||||
setAiPosts(parsed)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('aiPosts localStorage parse error', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoadAiPostsFromLocalStorage
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useCallback, useMemo, useEffect } from 'react'
|
import React, { useState, useCallback, useMemo, useEffect } from 'react'
|
||||||
import { createPortal } from 'react-dom'
|
import { createPortal } from 'react-dom'
|
||||||
import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
|
import { Button, Dialog, Notification, toast, Checkbox } from '@/components/ui'
|
||||||
import {
|
import {
|
||||||
|
|
@ -29,6 +29,7 @@ import {
|
||||||
filterNonLinkNodes,
|
filterNonLinkNodes,
|
||||||
} from '@/views/admin/listForm/wizard/WizardStep1'
|
} from '@/views/admin/listForm/wizard/WizardStep1'
|
||||||
import { MenuAddDialog } from '../shared/MenuAddDialog'
|
import { MenuAddDialog } from '../shared/MenuAddDialog'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store'
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -269,8 +270,6 @@ const WORKFLOW_COLUMNS: ColumnDefinition[] = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const CREATE_TABLE_SCRIPT_STORAGE_KEY = 'sqlQueryManager.lastCreateTableScript'
|
|
||||||
|
|
||||||
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
// ─── T-SQL Generator ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function colToSqlLine(col: ColumnDefinition, addComma = true): string {
|
function colToSqlLine(col: ColumnDefinition, addComma = true): string {
|
||||||
|
|
@ -998,6 +997,12 @@ const SqlTableDesignerDialog = ({
|
||||||
initialTableData,
|
initialTableData,
|
||||||
}: TableDesignerDialogProps) => {
|
}: TableDesignerDialogProps) => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
const lastCreateTableScript = useStoreState(
|
||||||
|
(state) => state.client.developerKit.lastCreateTableScript,
|
||||||
|
)
|
||||||
|
const setLastCreateTableScript = useStoreActions(
|
||||||
|
(actions) => actions.client.developerKit.setLastCreateTableScript,
|
||||||
|
)
|
||||||
|
|
||||||
const isEditMode = !!initialTableData
|
const isEditMode = !!initialTableData
|
||||||
|
|
||||||
|
|
@ -1283,14 +1288,14 @@ const SqlTableDesignerDialog = ({
|
||||||
const clipboardText = await navigator.clipboard.readText()
|
const clipboardText = await navigator.clipboard.readText()
|
||||||
if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) {
|
if (/\bCREATE\s+TABLE\b/i.test(clipboardText)) {
|
||||||
script = clipboardText
|
script = clipboardText
|
||||||
localStorage.setItem(CREATE_TABLE_SCRIPT_STORAGE_KEY, clipboardText)
|
setLastCreateTableScript(clipboardText)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Clipboard read can fail due to browser permissions; fallback to saved script.
|
// Clipboard read can fail due to browser permissions; fallback to saved script.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!script) {
|
if (!script) {
|
||||||
script = localStorage.getItem(CREATE_TABLE_SCRIPT_STORAGE_KEY) || ''
|
script = lastCreateTableScript
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!script.trim()) {
|
if (!script.trim()) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ import { intranetService } from '@/services/intranet.service'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import useLocale from '@/utils/hooks/useLocale'
|
import useLocale from '@/utils/hooks/useLocale'
|
||||||
import { currentLocalDate } from '@/utils/dateUtils'
|
import { currentLocalDate } from '@/utils/dateUtils'
|
||||||
import { useStoreState } from '@/store/store'
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
|
import type { DashboardLayout } from '@/store/admin.model'
|
||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
import { LuX } from 'react-icons/lu'
|
import { LuX } from 'react-icons/lu'
|
||||||
import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget'
|
import type { DashboardWidgetColumn, DashboardWidgetDefinition } from './dashboardWidget'
|
||||||
|
|
@ -31,9 +32,6 @@ dayjs.extend(relativeTime)
|
||||||
dayjs.extend(isBetween)
|
dayjs.extend(isBetween)
|
||||||
dayjs.extend(localizedFormat)
|
dayjs.extend(localizedFormat)
|
||||||
|
|
||||||
const WIDGET_ORDER_KEY = 'dashboard-widget-order'
|
|
||||||
const WIDGET_VISIBILITY_KEY = 'dashboard-hidden-widgets'
|
|
||||||
const DASHBOARD_LAYOUT_KEY = 'dashboard-layout'
|
|
||||||
const dashboardColumns: DashboardWidgetColumn[] = ['left', 'center', 'right']
|
const dashboardColumns: DashboardWidgetColumn[] = ['left', 'center', 'right']
|
||||||
|
|
||||||
type DashboardWidgetModule = { dashboardWidget?: DashboardWidgetDefinition }
|
type DashboardWidgetModule = { dashboardWidget?: DashboardWidgetDefinition }
|
||||||
|
|
@ -75,7 +73,7 @@ const layoutPresets = [
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
type DashboardLayoutId = (typeof layoutPresets)[number]['id']
|
type DashboardLayoutId = DashboardLayout
|
||||||
|
|
||||||
const columnSpanClasses: Record<number, string> = {
|
const columnSpanClasses: Record<number, string> = {
|
||||||
2: 'lg:col-span-2',
|
2: 'lg:col-span-2',
|
||||||
|
|
@ -92,27 +90,15 @@ const IntranetDashboard: React.FC = () => {
|
||||||
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
|
const [selectedSurvey, setSelectedSurvey] = useState<SurveyDto | null>(null)
|
||||||
const [showSurveyModal, setShowSurveyModal] = useState(false)
|
const [showSurveyModal, setShowSurveyModal] = useState(false)
|
||||||
const [isDesignMode, setIsDesignMode] = useState(false)
|
const [isDesignMode, setIsDesignMode] = useState(false)
|
||||||
const [hiddenWidgetIds, setHiddenWidgetIds] = useState<string[]>(() => {
|
const { dashboardLayout: selectedLayout, hiddenWidgetIds, widgetOrder } = useStoreState(
|
||||||
try {
|
(state) => state.admin.dashboard,
|
||||||
const savedIds: unknown = JSON.parse(localStorage.getItem(WIDGET_VISIBILITY_KEY) || '[]')
|
)
|
||||||
return Array.isArray(savedIds)
|
const {
|
||||||
? savedIds.filter((id): id is string => typeof id === 'string')
|
setDashboardLayout,
|
||||||
: []
|
setHiddenWidgetIds,
|
||||||
} catch {
|
setWidgetOrder,
|
||||||
return []
|
reset: resetDashboard,
|
||||||
}
|
} = useStoreActions((actions) => actions.admin.dashboard)
|
||||||
})
|
|
||||||
const [selectedLayout, setSelectedLayout] = useState<DashboardLayoutId>(() => {
|
|
||||||
const savedLayout = localStorage.getItem(DASHBOARD_LAYOUT_KEY)
|
|
||||||
return layoutPresets.some((layout) => layout.id === savedLayout)
|
|
||||||
? (savedLayout as DashboardLayoutId)
|
|
||||||
: 'default'
|
|
||||||
})
|
|
||||||
const [widgetOrder, setWidgetOrder] = useState<Record<DashboardWidgetColumn, string[]>>({
|
|
||||||
left: [],
|
|
||||||
center: [],
|
|
||||||
right: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const [intranetDashboard, setIntranetDashboard] = useState<IntranetDashboardDto>()
|
const [intranetDashboard, setIntranetDashboard] = useState<IntranetDashboardDto>()
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
@ -212,10 +198,10 @@ const IntranetDashboard: React.FC = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!grantedPolicies) return
|
if (!grantedPolicies) return
|
||||||
|
|
||||||
const savedOrder = localStorage.getItem(WIDGET_ORDER_KEY)
|
const hasSavedOrder = dashboardColumns.some((column) => widgetOrder[column].length > 0)
|
||||||
if (savedOrder) {
|
if (hasSavedOrder) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(savedOrder) as Record<string, unknown[]>
|
const parsed = widgetOrder
|
||||||
const order: Record<DashboardWidgetColumn, string[]> = {
|
const order: Record<DashboardWidgetColumn, string[]> = {
|
||||||
left: [...new Set((parsed.left || []) as string[])],
|
left: [...new Set((parsed.left || []) as string[])],
|
||||||
center: [...new Set((parsed.center || []) as string[])],
|
center: [...new Set((parsed.center || []) as string[])],
|
||||||
|
|
@ -230,8 +216,7 @@ const IntranetDashboard: React.FC = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
setWidgetOrder(order)
|
setWidgetOrder(order)
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error('Widget order parse error:', error)
|
|
||||||
initializeDefaultOrder()
|
initializeDefaultOrder()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -241,22 +226,17 @@ const IntranetDashboard: React.FC = () => {
|
||||||
|
|
||||||
const saveWidgetOrder = (newOrder: Record<DashboardWidgetColumn, string[]>) => {
|
const saveWidgetOrder = (newOrder: Record<DashboardWidgetColumn, string[]>) => {
|
||||||
setWidgetOrder(newOrder)
|
setWidgetOrder(newOrder)
|
||||||
localStorage.setItem(WIDGET_ORDER_KEY, JSON.stringify(newOrder))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setWidgetVisibility = (widgetId: string, visible: boolean) => {
|
const setWidgetVisibility = (widgetId: string, visible: boolean) => {
|
||||||
setHiddenWidgetIds((current) => {
|
|
||||||
const next = visible
|
const next = visible
|
||||||
? current.filter((id) => id !== widgetId)
|
? hiddenWidgetIds.filter((id) => id !== widgetId)
|
||||||
: [...new Set([...current, widgetId])]
|
: [...new Set([...hiddenWidgetIds, widgetId])]
|
||||||
localStorage.setItem(WIDGET_VISIBILITY_KEY, JSON.stringify(next))
|
setHiddenWidgetIds(next)
|
||||||
return next
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectLayout = (layoutId: DashboardLayoutId) => {
|
const selectLayout = (layoutId: DashboardLayoutId) => {
|
||||||
setSelectedLayout(layoutId)
|
setDashboardLayout(layoutId)
|
||||||
localStorage.setItem(DASHBOARD_LAYOUT_KEY, layoutId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeLayout =
|
const activeLayout =
|
||||||
|
|
@ -524,11 +504,7 @@ const IntranetDashboard: React.FC = () => {
|
||||||
{isDesignMode && (
|
{isDesignMode && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
localStorage.removeItem(WIDGET_ORDER_KEY)
|
resetDashboard()
|
||||||
localStorage.removeItem(WIDGET_VISIBILITY_KEY)
|
|
||||||
localStorage.removeItem(DASHBOARD_LAYOUT_KEY)
|
|
||||||
setHiddenWidgetIds([])
|
|
||||||
setSelectedLayout('default')
|
|
||||||
initializeDefaultOrder()
|
initializeDefaultOrder()
|
||||||
}}
|
}}
|
||||||
variant="plain"
|
variant="plain"
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@ import { TenantForm } from '@/components/orders/TenantForm'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
import { CustomTenantDto } from '@/proxy/config/models'
|
import { CustomTenantDto } from '@/proxy/config/models'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
import {
|
import {
|
||||||
CartState,
|
CartState,
|
||||||
clearCart,
|
clearCart,
|
||||||
loadCartFromLocalStorage,
|
|
||||||
removeItemFromCart,
|
removeItemFromCart,
|
||||||
updateCartItemQuantity,
|
updateCartItemQuantity,
|
||||||
} from '@/utils/cartUtils'
|
} from '@/utils/cartUtils'
|
||||||
|
|
@ -15,23 +15,22 @@ import React, { useState, useEffect } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const TENANT_DATA_STORAGE_KEY = 'tenantData'
|
|
||||||
|
|
||||||
const Checkout: React.FC = () => {
|
const Checkout: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [isCartOpen, setIsCartOpen] = useState(false)
|
const [isCartOpen, setIsCartOpen] = useState(false)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const [cartState, setCartState] = useState<CartState>(() => {
|
const storedCartState = useStoreState((state) => state.client.cartState)
|
||||||
return loadCartFromLocalStorage()
|
const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
|
||||||
})
|
const { setTenantData } = useStoreActions((actions) => actions.client.order)
|
||||||
|
const [cartState, setCartState] = useState<CartState>(storedCartState)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('cartState', JSON.stringify(cartState))
|
persistCartState(cartState)
|
||||||
}, [cartState])
|
}, [cartState, persistCartState])
|
||||||
|
|
||||||
const handleTenantSubmit = (tenantData: CustomTenantDto) => {
|
const handleTenantSubmit = (tenantData: CustomTenantDto) => {
|
||||||
localStorage.setItem(TENANT_DATA_STORAGE_KEY, JSON.stringify(tenantData))
|
setTenantData(tenantData)
|
||||||
navigate(ROUTES_ENUM.public.payment)
|
navigate(ROUTES_ENUM.public.payment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ import { CustomTenantDto } from '@/proxy/config/models'
|
||||||
import { OrderDto } from '@/proxy/order/models'
|
import { OrderDto } from '@/proxy/order/models'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
import { OrderService } from '@/services/order.service'
|
import { OrderService } from '@/services/order.service'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
import {
|
import {
|
||||||
CartState,
|
CartState,
|
||||||
clearCart,
|
clearCart,
|
||||||
initialCartState,
|
|
||||||
loadCartFromLocalStorage,
|
|
||||||
removeItemFromCart,
|
removeItemFromCart,
|
||||||
updateCartItemQuantity,
|
updateCartItemQuantity,
|
||||||
} from '@/utils/cartUtils'
|
} from '@/utils/cartUtils'
|
||||||
|
|
@ -19,32 +18,29 @@ import React, { useState, useEffect } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
const TENANT_DATA_STORAGE_KEY = 'tenantData'
|
|
||||||
const TENANT_FORM_DRAFT_STORAGE_KEY = 'tenantFormDraft'
|
|
||||||
|
|
||||||
const Payment: React.FC = () => {
|
const Payment: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [tenant, setTenant] = useState<CustomTenantDto | null>(null)
|
const storedCartState = useStoreState((state) => state.client.cartState)
|
||||||
|
const storedTenantData = useStoreState((state) => state.client.order.tenantData)
|
||||||
|
const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
|
||||||
|
const { setOrderResult } = useStoreActions((actions) => actions.client.order)
|
||||||
|
const [tenant, setTenant] = useState<CustomTenantDto | null>(storedTenantData)
|
||||||
const [isCartOpen, setIsCartOpen] = useState(false)
|
const [isCartOpen, setIsCartOpen] = useState(false)
|
||||||
const [cartState, setCartState] = useState<CartState>(initialCartState)
|
const [cartState, setCartState] = useState<CartState>(storedCartState)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const tenantData = localStorage.getItem(TENANT_DATA_STORAGE_KEY) // ✅ güncellendi
|
if (storedTenantData) {
|
||||||
|
setTenant(storedTenantData)
|
||||||
if (tenantData) {
|
|
||||||
setTenant(JSON.parse(tenantData))
|
|
||||||
} else {
|
} else {
|
||||||
navigate(ROUTES_ENUM.public.checkout)
|
navigate(ROUTES_ENUM.public.checkout)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCartState(loadCartFromLocalStorage())
|
}, [navigate, storedTenantData])
|
||||||
}, [navigate])
|
|
||||||
|
|
||||||
// Save cart state to localStorage whenever it changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('cartState', JSON.stringify(cartState))
|
persistCartState(cartState)
|
||||||
}, [cartState])
|
}, [cartState, persistCartState])
|
||||||
|
|
||||||
const handleCheckout = () => {
|
const handleCheckout = () => {
|
||||||
setIsCartOpen(false)
|
setIsCartOpen(false)
|
||||||
|
|
@ -99,18 +95,11 @@ const Payment: React.FC = () => {
|
||||||
const result = await orderService.createOrder(orderData)
|
const result = await orderService.createOrder(orderData)
|
||||||
|
|
||||||
if (result.success && result.orderId) {
|
if (result.success && result.orderId) {
|
||||||
localStorage.setItem('orderId', result.orderId) // ✅ güncellendi
|
setOrderResult({
|
||||||
localStorage.setItem(
|
|
||||||
'orderSuccessData',
|
|
||||||
JSON.stringify({
|
|
||||||
orderId: result.orderId,
|
orderId: result.orderId,
|
||||||
...orderData,
|
...orderData,
|
||||||
}),
|
})
|
||||||
)
|
|
||||||
setCartState(clearCart())
|
setCartState(clearCart())
|
||||||
localStorage.removeItem(TENANT_DATA_STORAGE_KEY) // ✅ temizlik
|
|
||||||
localStorage.removeItem(TENANT_FORM_DRAFT_STORAGE_KEY)
|
|
||||||
localStorage.removeItem('cartState') // ✅ temizlik
|
|
||||||
navigate(ROUTES_ENUM.public.success)
|
navigate(ROUTES_ENUM.public.success)
|
||||||
} else {
|
} else {
|
||||||
alert('Sipariş oluşturulurken hata oluştu: ' + result.error)
|
alert('Sipariş oluşturulurken hata oluştu: ' + result.error)
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import { ProductCatalog } from '@/components/orders/ProductCatalog'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
import { BillingCycle } from '@/proxy/order/models'
|
import { BillingCycle } from '@/proxy/order/models'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
import {
|
import {
|
||||||
CartState,
|
CartState,
|
||||||
clearCart,
|
clearCart,
|
||||||
loadCartFromLocalStorage,
|
|
||||||
removeItemFromCart,
|
removeItemFromCart,
|
||||||
setCartGlobalBillingCycle,
|
setCartGlobalBillingCycle,
|
||||||
setCartGlobalPeriod,
|
setCartGlobalPeriod,
|
||||||
|
|
@ -23,15 +23,14 @@ const Products: React.FC = () => {
|
||||||
const [isCartOpen, setIsCartOpen] = useState(false)
|
const [isCartOpen, setIsCartOpen] = useState(false)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
const [cartState, setCartState] = useState<CartState>(() => {
|
const storedCartState = useStoreState((state) => state.client.cartState)
|
||||||
return loadCartFromLocalStorage()
|
const { setCartState: persistCartState } = useStoreActions((actions) => actions.client)
|
||||||
})
|
const [cartState, setCartState] = useState<CartState>(storedCartState)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
// Save cart state to localStorage whenever it changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('cartState', JSON.stringify(cartState))
|
persistCartState(cartState)
|
||||||
}, [cartState])
|
}, [cartState, persistCartState])
|
||||||
|
|
||||||
const handleCartClick = () => {
|
const handleCartClick = () => {
|
||||||
setIsCartOpen(true)
|
setIsCartOpen(true)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { OrderSuccess, OrderSuccessData } from '@/components/orders/OrderSuccess'
|
import { OrderSuccess } from '@/components/orders/OrderSuccess'
|
||||||
import { Loading } from '@/components/shared'
|
import { Loading } from '@/components/shared'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||||
|
|
@ -6,29 +6,24 @@ import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import { useStoreActions, useStoreState } from '@/store/store'
|
||||||
|
|
||||||
const Success: React.FC = () => {
|
const Success: React.FC = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [orderId, setOrderId] = useState<string>('')
|
const storedOrder = useStoreState((state) => state.client.order)
|
||||||
const [order, setOrder] = useState<OrderSuccessData | null>(null)
|
const { clearResult, clearTenant } = useStoreActions((actions) => actions.client.order)
|
||||||
|
const [orderId] = useState(storedOrder.orderId || '')
|
||||||
|
const [order] = useState(storedOrder.orderSuccessData)
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get order ID from local storage
|
if (orderId) {
|
||||||
const orderIdData = localStorage.getItem('orderId')
|
clearResult()
|
||||||
const orderSuccessData = localStorage.getItem('orderSuccessData')
|
clearTenant()
|
||||||
if (orderIdData) {
|
|
||||||
setOrderId(orderIdData)
|
|
||||||
if (orderSuccessData) {
|
|
||||||
setOrder(JSON.parse(orderSuccessData))
|
|
||||||
}
|
|
||||||
// Clear order ID from local storage
|
|
||||||
localStorage.removeItem('orderId')
|
|
||||||
localStorage.removeItem('orderSuccessData')
|
|
||||||
} else {
|
} else {
|
||||||
navigate(ROUTES_ENUM.public.home)
|
navigate(ROUTES_ENUM.public.home)
|
||||||
}
|
}
|
||||||
}, [navigate])
|
}, [clearResult, clearTenant, navigate, orderId])
|
||||||
|
|
||||||
const handleBackToShop = () => {
|
const handleBackToShop = () => {
|
||||||
navigate(ROUTES_ENUM.public.home)
|
navigate(ROUTES_ENUM.public.home)
|
||||||
|
|
@ -62,7 +57,11 @@ const Success: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="container mx-auto px-4 pt-12">
|
<div className="container mx-auto px-4 pt-12">
|
||||||
<OrderSuccess orderId={orderId} order={order} onBackToShop={handleBackToShop} />
|
<OrderSuccess
|
||||||
|
orderId={orderId}
|
||||||
|
order={order}
|
||||||
|
onBackToShop={handleBackToShop}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue