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 && (