diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260708113003_Initial.Designer.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260709175406_Initial.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260708113003_Initial.Designer.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260709175406_Initial.Designer.cs index 92ea33f..d69c8e9 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260708113003_Initial.Designer.cs +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260709175406_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Sozsoft.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20260708113003_Initial")] + [Migration("20260709175406_Initial")] partial class Initial { /// diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260708113003_Initial.cs b/api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260709175406_Initial.cs similarity index 100% rename from api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260708113003_Initial.cs rename to api/src/Sozsoft.Platform.EntityFrameworkCore/Migrations/20260709175406_Initial.cs diff --git a/ui/src/components/orders/OrderSuccess.tsx b/ui/src/components/orders/OrderSuccess.tsx index 5f5de1b..219e20c 100644 --- a/ui/src/components/orders/OrderSuccess.tsx +++ b/ui/src/components/orders/OrderSuccess.tsx @@ -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 diff --git a/ui/src/components/orders/TenantForm.tsx b/ui/src/components/orders/TenantForm.tsx index 0353b83..9e0e649 100644 --- a/ui/src/components/orders/TenantForm.tsx +++ b/ui/src/components/orders/TenantForm.tsx @@ -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 { label: string @@ -38,8 +40,6 @@ interface LocationOption { } 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 = [ @@ -169,11 +169,6 @@ const getUniqueLocationOptions = ( }, []) } -type TenantFormDraft = Partial & { - isExisting?: boolean - foundTenantName?: string -} - interface TenantFormProps { onSubmit: (tenant: CustomTenantDto) => void onBack: () => void @@ -196,22 +191,25 @@ export const TenantForm: React.FC = ({ 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 = ({ 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 = ({ 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 = ({ onSubmit }) => {
- {translate('::blog.categories.mobile')}: - {formData.mobileNumber} + {translate('::Abp.Identity.User.UserInformation.PhoneNumber')}: + {formData.phoneNumber}
diff --git a/ui/src/store/admin.model.ts b/ui/src/store/admin.model.ts index 550c7c9..2db41be 100644 --- a/ui/src/store/admin.model.ts +++ b/ui/src/store/admin.model.ts @@ -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 } - messages: { - addAiPost: Action - setAiPosts: Action + dashboard: { + setDashboardLayout: Action + setHiddenWidgetIds: Action + setWidgetOrder: Action + reset: Action } + setAiConversations: Action } 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 + }), } diff --git a/ui/src/store/client.model.ts b/ui/src/store/client.model.ts new file mode 100644 index 0000000..87967fe --- /dev/null +++ b/ui/src/store/client.model.ts @@ -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 & { + 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 + order: { + setTenantData: Action + setTenantFormDraft: Action + setOrderResult: Action + clearTenant: Action + clearResult: Action + } + fileManager: { + setClipboard: Action + clearClipboard: Action + } + developerKit: { + setLastCreateTableScript: Action + } +} + +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 + }), + }, +} diff --git a/ui/src/store/locale.model.ts b/ui/src/store/locale.model.ts index 251da39..26cacd3 100644 --- a/ui/src/store/locale.model.ts +++ b/ui/src/store/locale.model.ts @@ -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 - }), + }), } diff --git a/ui/src/store/store.ts b/ui/src/store/store.ts index 2b9e175..c464ac3 100644 --- a/ui/src/store/store.ts +++ b/ui/src/store/store.ts @@ -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( auth: authModel, base: baseModel, locale: localeModel, + client: clientModel, admin: adminModel, }, { - allow: ['auth', 'theme', 'locale', 'admin'], - storage: 'localStorage', + allow: ['auth', 'theme', 'locale', 'client', 'admin'], }, ), { diff --git a/ui/src/utils/cartUtils.ts b/ui/src/utils/cartUtils.ts index 0a56142..1fb0414 100644 --- a/ui/src/utils/cartUtils.ts +++ b/ui/src/utils/cartUtils.ts @@ -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) +} diff --git a/ui/src/views/admin/files/FileManager.tsx b/ui/src/views/admin/files/FileManager.tsx index 2924257..6caca43 100644 --- a/ui/src/views/admin/files/FileManager.tsx +++ b/ui/src/views/admin/files/FileManager.tsx @@ -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( {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( {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( 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( {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( Item ready to move. Navigate to target folder and paste. diff --git a/ui/src/views/admin/listForm/edit/options.ts b/ui/src/views/admin/listForm/edit/options.ts index b1a2bff..af704ee 100644 --- a/ui/src/views/admin/listForm/edit/options.ts +++ b/ui/src/views/admin/listForm/edit/options.ts @@ -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 = [ diff --git a/ui/src/views/ai/Assistant.tsx b/ui/src/views/ai/Assistant.tsx index 60c0285..d839086 100644 --- a/ui/src/views/ai/Assistant.tsx +++ b/ui/src/views/ai/Assistant.tsx @@ -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([]) - const [activeConversationId, setActiveConversationId] = useState(null) + const storedAiConversations = useStoreState((state) => state.admin.aiConversations) + const { setAiConversations } = useStoreActions((actions) => actions.admin) + const [conversations, setConversations] = useState(storedAiConversations.items) + const [activeConversationId, setActiveConversationId] = useState( + storedAiConversations.aiActiveConversationId, + ) const [isConversationsLoaded, setIsConversationsLoaded] = useState(false) const [isConversationDrawerOpen, setIsConversationDrawerOpen] = useState(false) - const [, setMessages] = useState([]) - const aiPosts: Message[] = [] const [input, setInput] = useState('') const [loading, setLoading] = useState(false) const [bot, setBot] = useState([]) @@ -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, diff --git a/ui/src/views/ai/LoadAiPostsFromLocalStorage.tsx b/ui/src/views/ai/LoadAiPostsFromLocalStorage.tsx deleted file mode 100644 index 4edc902..0000000 --- a/ui/src/views/ai/LoadAiPostsFromLocalStorage.tsx +++ /dev/null @@ -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 diff --git a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx index d4ab768..0ffe6a1 100644 --- a/ui/src/views/developerKit/SqlTableDesignerDialog.tsx +++ b/ui/src/views/developerKit/SqlTableDesignerDialog.tsx @@ -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()) { diff --git a/ui/src/views/intranet/Dashboard.tsx b/ui/src/views/intranet/Dashboard.tsx index 30d43b2..bea3535 100644 --- a/ui/src/views/intranet/Dashboard.tsx +++ b/ui/src/views/intranet/Dashboard.tsx @@ -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 = { 2: 'lg:col-span-2', @@ -92,27 +90,15 @@ const IntranetDashboard: React.FC = () => { const [selectedSurvey, setSelectedSurvey] = useState(null) const [showSurveyModal, setShowSurveyModal] = useState(false) const [isDesignMode, setIsDesignMode] = useState(false) - const [hiddenWidgetIds, setHiddenWidgetIds] = useState(() => { - 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(() => { - const savedLayout = localStorage.getItem(DASHBOARD_LAYOUT_KEY) - return layoutPresets.some((layout) => layout.id === savedLayout) - ? (savedLayout as DashboardLayoutId) - : 'default' - }) - const [widgetOrder, setWidgetOrder] = useState>({ - 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() 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 + const parsed = widgetOrder const order: Record = { 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) => { 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 && (
- +
)