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