@nice2dev/erp-adapter 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ErpAuthAdapter.d.ts.map +1 -1
- package/dist/ErpDataAdapter.d.ts.map +1 -1
- package/dist/ErpExportAdapter.d.ts.map +1 -1
- package/dist/ErpFileAdapter.d.ts.map +1 -1
- package/dist/ErpOptimisticStore.d.ts +4 -4
- package/dist/ErpOptimisticStore.d.ts.map +1 -1
- package/dist/ErpRateLimiter.d.ts.map +1 -1
- package/dist/ErpSignalRAdapter.d.ts +1 -1
- package/dist/ErpSignalRAdapter.d.ts.map +1 -1
- package/dist/finance/useLoanApi.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +605 -555
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/registries/index.d.ts.map +1 -1
- package/dist/useCollaborativeDataGrid.d.ts +8 -8
- package/dist/useCollaborativeDataGrid.d.ts.map +1 -1
- package/dist/useEntityPresence.d.ts +2 -2
- package/dist/useEntityPresence.d.ts.map +1 -1
- package/dist/useSignalRLiveData.d.ts +5 -5
- package/dist/useSignalRLiveData.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/ErpDataAdapter.ts","../src/ErpSignalRAdapter.ts","../src/useSignalRLiveData.ts","../src/useEntityPresence.ts","../src/useCollaborativeDataGrid.ts","../src/ErpAuthAdapter.ts","../src/ErpFileAdapter.ts","../src/ErpExportAdapter.ts","../src/middleware.ts","../src/ErpRateLimiter.ts","../src/ErpOptimisticStore.ts","../src/ErpOfflineQueue.ts","../src/registries/audioControls.ts","../src/registries/graphicControls.ts","../src/registries/threeControls.ts","../src/registries/gamificationControls.ts","../src/registries/businessControls.ts","../src/registries/authControls.ts","../src/registries/socialControls.ts","../src/registries/index.ts","../src/finance/useLoanApi.ts"],"sourcesContent":["/**\r\n * ErpDataAdapter — generic REST adapter connecting NiceDataGrid, NiceList, etc.\r\n * to OmniVerk's CRUD REST API.\r\n *\r\n * Usage:\r\n * const adapter = new ErpDataAdapter('/api/products');\r\n * const result = await adapter.load({ skip: 0, take: 20, sort: [{ field: 'name', dir: 'asc' }] });\r\n */\r\n\r\nimport type { ErpDataRequest, ErpResponse, ErpFilter, ErpSort } from \"./types\";\r\n\r\nexport interface ErpDataAdapterConfig {\r\n /** Base URL for the entity, e.g. \"/api/products\". */\r\n baseUrl: string;\r\n /** Custom fetch implementation (defaults to window.fetch). */\r\n fetch?: typeof fetch;\r\n /** Default headers (e.g. Authorization). */\r\n headers?: Record<string, string>;\r\n /** Token factory — called before each request to get a fresh bearer token. */\r\n tokenFactory?: () => string | Promise<string>;\r\n}\r\n\r\nexport class ErpDataAdapter<T = unknown> {\r\n private cfg: ErpDataAdapterConfig;\r\n\r\n constructor(config: ErpDataAdapterConfig | string) {\r\n this.cfg = typeof config === \"string\" ? { baseUrl: config } : config;\r\n }\r\n\r\n private async request<R>(path: string, init?: RequestInit): Promise<R> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json\",\r\n ...this.cfg.headers,\r\n };\r\n if (this.cfg.tokenFactory) {\r\n const token = await this.cfg.tokenFactory();\r\n headers[\"Authorization\"] = `Bearer ${token}`;\r\n }\r\n const res = await f(`${this.cfg.baseUrl}${path}`, { ...init, headers: { ...headers, ...init?.headers } });\r\n if (!res.ok) throw new Error(`ERP request failed: ${res.status} ${res.statusText}`);\r\n return res.json() as Promise<R>;\r\n }\r\n\r\n /** Load paged/filtered/sorted data. */\r\n async load(req?: ErpDataRequest): Promise<ErpResponse<T[]>> {\r\n const params = new URLSearchParams();\r\n if (req?.skip != null) params.set(\"skip\", String(req.skip));\r\n if (req?.take != null) params.set(\"take\", String(req.take));\r\n if (req?.search) params.set(\"search\", req.search);\r\n if (req?.sort?.length) params.set(\"sort\", JSON.stringify(req.sort));\r\n if (req?.filters?.length) params.set(\"filters\", JSON.stringify(req.filters));\r\n const qs = params.toString();\r\n return this.request<ErpResponse<T[]>>(qs ? `?${qs}` : \"\");\r\n }\r\n\r\n /** Get single entity by id. */\r\n async getById(id: string | number): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(`/${id}`);\r\n }\r\n\r\n /** Create entity. */\r\n async create(item: Partial<T>): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(\"\", { method: \"POST\", body: JSON.stringify(item) });\r\n }\r\n\r\n /** Update entity. */\r\n async update(id: string | number, item: Partial<T>): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(`/${id}`, { method: \"PUT\", body: JSON.stringify(item) });\r\n }\r\n\r\n /** Delete entity. */\r\n async remove(id: string | number): Promise<ErpResponse<void>> {\r\n return this.request<ErpResponse<void>>(`/${id}`, { method: \"DELETE\" });\r\n }\r\n\r\n /** Batch delete. */\r\n async removeBatch(ids: (string | number)[]): Promise<ErpResponse<void>> {\r\n return this.request<ErpResponse<void>>(\"/batch-delete\", { method: \"POST\", body: JSON.stringify({ ids }) });\r\n }\r\n\r\n /** Batch create — send multiple items in a single request. */\r\n async createBatch(items: Partial<T>[]): Promise<ErpResponse<T[]>> {\r\n return this.request<ErpResponse<T[]>>(\"/batch\", { method: \"POST\", body: JSON.stringify({ items }) });\r\n }\r\n\r\n /** Batch update — send multiple items with ids in a single request. */\r\n async updateBatch(items: { id: string | number; data: Partial<T> }[]): Promise<ErpResponse<T[]>> {\r\n return this.request<ErpResponse<T[]>>(\"/batch\", { method: \"PUT\", body: JSON.stringify({ items }) });\r\n }\r\n\r\n /** Partial update (PATCH) a single entity. */\r\n async patch(id: string | number, fields: Partial<T>): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(`/${id}`, { method: \"PATCH\", body: JSON.stringify(fields) });\r\n }\r\n}\r\n","/**\r\n * ErpSignalRAdapter — wraps a SignalR HubConnection for real-time features\r\n * (NiceChat, NiceScheduler live updates, NiceDataGrid auto-refresh, etc.)\r\n */\r\n\r\nimport type { ErpSignalRConfig } from \"./types\";\r\n\r\nexport type SignalRStatus = \"disconnected\" | \"connecting\" | \"connected\" | \"reconnecting\";\r\n\r\nexport interface ErpSignalRAdapter {\r\n readonly status: SignalRStatus;\r\n start(): Promise<void>;\r\n stop(): Promise<void>;\r\n on(event: string, handler: (...args: any[]) => void): void;\r\n off(event: string, handler: (...args: any[]) => void): void;\r\n invoke(method: string, ...args: any[]): Promise<any>;\r\n onStatusChange(cb: (status: SignalRStatus) => void): () => void;\r\n}\r\n\r\n/**\r\n * Create a SignalR adapter using dynamic import of @microsoft/signalr.\r\n * This keeps the dependency optional — consumers must install it themselves.\r\n */\r\nexport async function createSignalRAdapter(config: ErpSignalRConfig): Promise<ErpSignalRAdapter> {\r\n const signalR = await import(\"@microsoft/signalr\");\r\n\r\n let status: SignalRStatus = \"disconnected\";\r\n const listeners = new Set<(s: SignalRStatus) => void>();\r\n\r\n const setStatus = (s: SignalRStatus) => {\r\n status = s;\r\n listeners.forEach((cb) => cb(s));\r\n };\r\n\r\n const builder = new signalR.HubConnectionBuilder()\r\n .withUrl(config.hubUrl, {\r\n accessTokenFactory: config.accessTokenFactory\r\n ? () => config.accessTokenFactory!()\r\n : undefined,\r\n })\r\n .withAutomaticReconnect();\r\n\r\n const connection = builder.build();\r\n\r\n connection.onreconnecting(() => setStatus(\"reconnecting\"));\r\n connection.onreconnected(() => setStatus(\"connected\"));\r\n connection.onclose(() => setStatus(\"disconnected\"));\r\n\r\n const adapter: ErpSignalRAdapter = {\r\n get status() {\r\n return status;\r\n },\r\n async start() {\r\n setStatus(\"connecting\");\r\n await connection.start();\r\n setStatus(\"connected\");\r\n },\r\n async stop() {\r\n await connection.stop();\r\n setStatus(\"disconnected\");\r\n },\r\n on(event, handler) {\r\n connection.on(event, handler);\r\n },\r\n off(event, handler) {\r\n connection.off(event, handler);\r\n },\r\n invoke(method, ...args) {\r\n return connection.invoke(method, ...args);\r\n },\r\n onStatusChange(cb) {\r\n listeners.add(cb);\r\n return () => listeners.delete(cb);\r\n },\r\n };\r\n\r\n return adapter;\r\n}\r\n","/**\r\n * useSignalRLiveData — React hook for real-time data updates via SignalR.\r\n *\r\n * Provides automatic push updates for insert/update/delete operations\r\n * on a data grid or any list-based component.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { data, status, applyRemoteUpdate } = useSignalRLiveData<Employee>({\r\n * adapter: signalRAdapter,\r\n * entityName: 'employees',\r\n * keyField: 'id',\r\n * initialData: employees,\r\n * });\r\n *\r\n * return <NiceDataGrid data={data} keyField=\"id\" columns={columns} />;\r\n * ```\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useRef } from \"react\";\r\nimport type { ErpSignalRAdapter, SignalRStatus } from \"./ErpSignalRAdapter\";\r\n\r\n/* ================================================================\r\n TYPES\r\n ================================================================ */\r\n\r\n/** Type of real-time data operation. */\r\nexport type LiveDataOperation = \"insert\" | \"update\" | \"delete\";\r\n\r\n/** A single real-time data change event. */\r\nexport interface LiveDataChange<T = unknown> {\r\n /** Operation type: insert, update, or delete. */\r\n operation: LiveDataOperation;\r\n /** Entity type name (e.g., \"employees\", \"orders\"). */\r\n entityName: string;\r\n /** Primary key of the affected row. */\r\n key: unknown;\r\n /** Full data for insert/update; undefined for delete. */\r\n data?: T;\r\n /** Partial changes for update (fields that changed). */\r\n changes?: Partial<T>;\r\n /** Server timestamp of the change. */\r\n timestamp?: string;\r\n /** User ID who made the change. */\r\n changedBy?: string;\r\n}\r\n\r\n/** Configuration for useSignalRLiveData hook. */\r\nexport interface UseSignalRLiveDataOptions<T> {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity name to subscribe to (e.g., \"employees\"). */\r\n entityName: string;\r\n /** Primary key field name. */\r\n keyField: keyof T & string;\r\n /** Initial data array. */\r\n initialData?: T[];\r\n /** Called when a remote change is received. Return false to reject the update. */\r\n onBeforeChange?: (change: LiveDataChange<T>) => boolean | Promise<boolean>;\r\n /** Called after a change has been applied locally. */\r\n onAfterChange?: (change: LiveDataChange<T>, data: T[]) => void;\r\n /** Called when there's a conflict (local pending edit vs remote update). */\r\n onConflict?: (local: T, remote: T, change: LiveDataChange<T>) => T | \"local\" | \"remote\" | \"merge\";\r\n /** Hub method name for subscribing. Default: \"SubscribeToEntity\". */\r\n subscribeMethod?: string;\r\n /** Hub method name for unsubscribing. Default: \"UnsubscribeFromEntity\". */\r\n unsubscribeMethod?: string;\r\n /** Hub event name for data changes. Default: \"EntityChanged\". */\r\n changeEventName?: string;\r\n /** Enable flash animation for changed rows. */\r\n flashChanges?: boolean;\r\n /** Duration of flash animation in ms. Default: 500. */\r\n flashDuration?: number;\r\n /** Enable optimistic UI updates. Default: true. */\r\n optimisticUpdates?: boolean;\r\n}\r\n\r\n/** State returned by useSignalRLiveData hook. */\r\nexport interface SignalRLiveDataState<T> {\r\n /** Current data array with real-time updates applied. */\r\n data: T[];\r\n /** SignalR connection status. */\r\n status: SignalRStatus;\r\n /** Set of row keys that were recently changed (for flash animation). */\r\n flashedKeys: Set<unknown>;\r\n /** Set of row keys with pending local changes. */\r\n pendingKeys: Set<unknown>;\r\n /** Last error if any. */\r\n error: Error | null;\r\n /** Manually apply a remote update (for testing or custom handling). */\r\n applyRemoteUpdate: (change: LiveDataChange<T>) => void;\r\n /** Register a local pending change (for optimistic UI). */\r\n registerPendingChange: (key: unknown, change: Partial<T>) => void;\r\n /** Confirm a pending change was saved server-side. */\r\n confirmPendingChange: (key: unknown) => void;\r\n /** Rollback a pending change (server rejected). */\r\n rollbackPendingChange: (key: unknown) => void;\r\n /** Force re-subscribe to the entity. */\r\n resubscribe: () => Promise<void>;\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useSignalRLiveData<T extends Record<string, any>>(\r\n options: UseSignalRLiveDataOptions<T>,\r\n): SignalRLiveDataState<T> {\r\n const {\r\n adapter,\r\n entityName,\r\n keyField,\r\n initialData = [],\r\n onBeforeChange,\r\n onAfterChange,\r\n onConflict,\r\n subscribeMethod = \"SubscribeToEntity\",\r\n unsubscribeMethod = \"UnsubscribeFromEntity\",\r\n changeEventName = \"EntityChanged\",\r\n flashChanges = true,\r\n flashDuration = 500,\r\n optimisticUpdates = true,\r\n } = options;\r\n\r\n const [data, setData] = useState<T[]>(initialData);\r\n const [status, setStatus] = useState<SignalRStatus>(adapter.status);\r\n const [flashedKeys, setFlashedKeys] = useState<Set<unknown>>(new Set());\r\n const [pendingKeys, setPendingKeys] = useState<Set<unknown>>(new Set());\r\n const [error, setError] = useState<Error | null>(null);\r\n\r\n // Keep track of pending local changes for conflict resolution\r\n const pendingChangesRef = useRef<Map<unknown, { original: T; changes: Partial<T> }>>(new Map());\r\n const flashTimeoutsRef = useRef<Map<unknown, ReturnType<typeof setTimeout>>>(new Map());\r\n\r\n // Apply a single change to the data array\r\n const applyChange = useCallback(\r\n (currentData: T[], change: LiveDataChange<T>): T[] => {\r\n const { operation, key, data: newData, changes } = change;\r\n\r\n switch (operation) {\r\n case \"insert\":\r\n if (!newData) return currentData;\r\n // Check if already exists (duplicate event)\r\n if (currentData.some((row) => row[keyField] === key)) {\r\n return currentData;\r\n }\r\n return [...currentData, newData];\r\n\r\n case \"update\":\r\n return currentData.map((row) => {\r\n if (row[keyField] !== key) return row;\r\n\r\n // Check for conflicts with pending local changes\r\n const pending = pendingChangesRef.current.get(key);\r\n if (pending && onConflict) {\r\n const resolved = onConflict(\r\n { ...row, ...pending.changes } as T,\r\n (newData ?? { ...row, ...changes }) as T,\r\n change,\r\n );\r\n if (resolved === \"local\") {\r\n return { ...row, ...pending.changes };\r\n } else if (resolved === \"remote\") {\r\n pendingChangesRef.current.delete(key);\r\n return newData ?? { ...row, ...changes };\r\n } else if (resolved === \"merge\") {\r\n return { ...row, ...changes, ...pending.changes };\r\n } else {\r\n return resolved;\r\n }\r\n }\r\n\r\n // No conflict, apply remote change\r\n if (newData) return newData;\r\n return { ...row, ...changes };\r\n });\r\n\r\n case \"delete\":\r\n return currentData.filter((row) => row[keyField] !== key);\r\n\r\n default:\r\n return currentData;\r\n }\r\n },\r\n [keyField, onConflict],\r\n );\r\n\r\n // Flash a row temporarily to indicate change\r\n const flashRow = useCallback(\r\n (key: unknown) => {\r\n if (!flashChanges) return;\r\n\r\n // Clear existing timeout for this key\r\n const existingTimeout = flashTimeoutsRef.current.get(key);\r\n if (existingTimeout) {\r\n clearTimeout(existingTimeout);\r\n }\r\n\r\n // Add to flashed set\r\n setFlashedKeys((prev) => new Set([...prev, key]));\r\n\r\n // Remove after duration\r\n const timeout = setTimeout(() => {\r\n setFlashedKeys((prev) => {\r\n const next = new Set(prev);\r\n next.delete(key);\r\n return next;\r\n });\r\n flashTimeoutsRef.current.delete(key);\r\n }, flashDuration);\r\n\r\n flashTimeoutsRef.current.set(key, timeout);\r\n },\r\n [flashChanges, flashDuration],\r\n );\r\n\r\n // Handle incoming real-time change\r\n const handleChange = useCallback(\r\n async (change: LiveDataChange<T>) => {\r\n // Filter by entity name\r\n if (change.entityName !== entityName) return;\r\n\r\n // Allow consumer to reject the change\r\n if (onBeforeChange) {\r\n const allowed = await onBeforeChange(change);\r\n if (!allowed) return;\r\n }\r\n\r\n // Apply the change\r\n setData((currentData) => {\r\n const newData = applyChange(currentData, change);\r\n onAfterChange?.(change, newData);\r\n return newData;\r\n });\r\n\r\n // Flash the affected row\r\n flashRow(change.key);\r\n },\r\n [entityName, onBeforeChange, applyChange, onAfterChange, flashRow],\r\n );\r\n\r\n // Public method to manually apply a change\r\n const applyRemoteUpdate = useCallback(\r\n (change: LiveDataChange<T>) => {\r\n handleChange(change);\r\n },\r\n [handleChange],\r\n );\r\n\r\n // Register a pending local change (optimistic UI)\r\n const registerPendingChange = useCallback(\r\n (key: unknown, changes: Partial<T>) => {\r\n if (!optimisticUpdates) return;\r\n\r\n const currentRow = data.find((row) => row[keyField] === key);\r\n if (currentRow) {\r\n pendingChangesRef.current.set(key, { original: currentRow, changes });\r\n setPendingKeys((prev) => new Set([...prev, key]));\r\n }\r\n },\r\n [data, keyField, optimisticUpdates],\r\n );\r\n\r\n // Confirm a pending change was saved\r\n const confirmPendingChange = useCallback((key: unknown) => {\r\n pendingChangesRef.current.delete(key);\r\n setPendingKeys((prev) => {\r\n const next = new Set(prev);\r\n next.delete(key);\r\n return next;\r\n });\r\n }, []);\r\n\r\n // Rollback a pending change\r\n const rollbackPendingChange = useCallback((key: unknown) => {\r\n const pending = pendingChangesRef.current.get(key);\r\n if (pending) {\r\n setData((currentData) =>\r\n currentData.map((row) =>\r\n row[keyField] === key ? pending.original : row,\r\n ),\r\n );\r\n pendingChangesRef.current.delete(key);\r\n setPendingKeys((prev) => {\r\n const next = new Set(prev);\r\n next.delete(key);\r\n return next;\r\n });\r\n }\r\n }, [keyField]);\r\n\r\n // Subscribe/unsubscribe to entity changes\r\n const subscribe = useCallback(async () => {\r\n if (adapter.status !== \"connected\") return;\r\n try {\r\n await adapter.invoke(subscribeMethod, entityName);\r\n setError(null);\r\n } catch (e) {\r\n setError(e instanceof Error ? e : new Error(String(e)));\r\n }\r\n }, [adapter, subscribeMethod, entityName]);\r\n\r\n const unsubscribe = useCallback(async () => {\r\n if (adapter.status !== \"connected\") return;\r\n try {\r\n await adapter.invoke(unsubscribeMethod, entityName);\r\n } catch {\r\n // Ignore unsubscribe errors\r\n }\r\n }, [adapter, unsubscribeMethod, entityName]);\r\n\r\n // Setup event handlers\r\n useEffect(() => {\r\n const unsubStatus = adapter.onStatusChange((newStatus) => {\r\n setStatus(newStatus);\r\n if (newStatus === \"connected\") {\r\n subscribe();\r\n }\r\n });\r\n\r\n adapter.on(changeEventName, handleChange);\r\n\r\n // Initial subscription if already connected\r\n if (adapter.status === \"connected\") {\r\n subscribe();\r\n }\r\n\r\n return () => {\r\n unsubStatus();\r\n adapter.off(changeEventName, handleChange);\r\n unsubscribe();\r\n\r\n // Clear all flash timeouts\r\n flashTimeoutsRef.current.forEach((timeout) => clearTimeout(timeout));\r\n flashTimeoutsRef.current.clear();\r\n };\r\n }, [adapter, changeEventName, handleChange, subscribe, unsubscribe]);\r\n\r\n // Update data when initialData changes\r\n useEffect(() => {\r\n setData(initialData);\r\n }, [initialData]);\r\n\r\n return {\r\n data,\r\n status,\r\n flashedKeys,\r\n pendingKeys,\r\n error,\r\n applyRemoteUpdate,\r\n registerPendingChange,\r\n confirmPendingChange,\r\n rollbackPendingChange,\r\n resubscribe: subscribe,\r\n };\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Create a SignalR-backed DataSource\r\n ================================================================ */\r\n\r\nexport interface SignalRDataSourceConfig<T> {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity name for subscriptions. */\r\n entityName: string;\r\n /** Primary key field. */\r\n keyField: keyof T & string;\r\n /** Hub method name to fetch initial data. Default: \"GetEntities\". */\r\n fetchMethod?: string;\r\n /** Hub method name to insert. Default: \"InsertEntity\". */\r\n insertMethod?: string;\r\n /** Hub method name to update. Default: \"UpdateEntity\". */\r\n updateMethod?: string;\r\n /** Hub method name to delete. Default: \"DeleteEntity\". */\r\n deleteMethod?: string;\r\n}\r\n\r\n/**\r\n * Create callbacks for CRUD operations via SignalR.\r\n * Use with useSignalRLiveData for a complete real-time data solution.\r\n */\r\nexport function createSignalRDataOperations<T extends Record<string, any>>(\r\n config: SignalRDataSourceConfig<T>,\r\n) {\r\n const {\r\n adapter,\r\n entityName,\r\n keyField,\r\n fetchMethod = \"GetEntities\",\r\n insertMethod = \"InsertEntity\",\r\n updateMethod = \"UpdateEntity\",\r\n deleteMethod = \"DeleteEntity\",\r\n } = config;\r\n\r\n return {\r\n /** Fetch all entities from the hub. */\r\n async fetchAll(options?: { skip?: number; take?: number; filter?: unknown }): Promise<T[]> {\r\n return adapter.invoke(fetchMethod, entityName, options);\r\n },\r\n\r\n /** Insert a new entity via the hub. */\r\n async insert(data: Partial<T>): Promise<T> {\r\n return adapter.invoke(insertMethod, entityName, data);\r\n },\r\n\r\n /** Update an existing entity via the hub. */\r\n async update(key: unknown, changes: Partial<T>): Promise<T> {\r\n return adapter.invoke(updateMethod, entityName, key, changes);\r\n },\r\n\r\n /** Delete an entity via the hub. */\r\n async remove(key: unknown): Promise<void> {\r\n return adapter.invoke(deleteMethod, entityName, key);\r\n },\r\n\r\n /** Get the key field name. */\r\n getKeyField(): keyof T & string {\r\n return keyField;\r\n },\r\n };\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Batch update helper\r\n ================================================================ */\r\n\r\nexport interface BatchChange<T> {\r\n operation: LiveDataOperation;\r\n key: unknown;\r\n data?: T;\r\n changes?: Partial<T>;\r\n}\r\n\r\n/**\r\n * Apply multiple changes to a data array efficiently.\r\n * Useful for initial sync or batch operations.\r\n */\r\nexport function applyBatchChanges<T extends Record<string, any>>(\r\n data: T[],\r\n changes: BatchChange<T>[],\r\n keyField: keyof T & string,\r\n): T[] {\r\n const dataMap = new Map<unknown, T>(data.map((row) => [row[keyField], row]));\r\n\r\n for (const change of changes) {\r\n switch (change.operation) {\r\n case \"insert\":\r\n if (change.data && !dataMap.has(change.key)) {\r\n dataMap.set(change.key, change.data);\r\n }\r\n break;\r\n case \"update\":\r\n if (dataMap.has(change.key)) {\r\n const existing = dataMap.get(change.key)!;\r\n dataMap.set(\r\n change.key,\r\n change.data ?? { ...existing, ...change.changes },\r\n );\r\n }\r\n break;\r\n case \"delete\":\r\n dataMap.delete(change.key);\r\n break;\r\n }\r\n }\r\n\r\n return Array.from(dataMap.values());\r\n}\r\n","/**\r\n * useEntityPresence — React hook for tracking who is viewing/editing entities via SignalR.\r\n *\r\n * Shows real-time presence information: which users are viewing or editing\r\n * specific records in a DataGrid, form, or any entity-based view.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { viewers, editors, myStatus, setMyStatus } = useEntityPresence({\r\n * adapter: signalRAdapter,\r\n * entityType: 'orders',\r\n * entityId: orderId,\r\n * currentUser: { id: userId, name: 'John Doe', avatarUrl: '...' },\r\n * });\r\n *\r\n * return (\r\n * <div>\r\n * {viewers.map(u => <UserAvatar key={u.id} user={u} />)}\r\n * {editors.length > 0 && <span>Editing: {editors.map(e => e.name).join(', ')}</span>}\r\n * </div>\r\n * );\r\n * ```\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useRef } from \"react\";\r\nimport type { ErpSignalRAdapter, SignalRStatus } from \"./ErpSignalRAdapter\";\r\n\r\n/* ================================================================\r\n TYPES\r\n ================================================================ */\r\n\r\n/** User presence status for entity tracking. */\r\nexport type EntityPresenceStatus = \"viewing\" | \"editing\" | \"idle\" | \"away\";\r\n\r\n/** User information for presence display. */\r\nexport interface PresenceUser {\r\n /** User ID. */\r\n id: string;\r\n /** Display name. */\r\n name: string;\r\n /** Avatar URL (optional). */\r\n avatarUrl?: string;\r\n /** User's email (optional). */\r\n email?: string;\r\n /** Custom color for cursor/indicator. */\r\n color?: string;\r\n}\r\n\r\n/** Presence data for a single user on an entity. */\r\nexport interface EntityPresenceInfo {\r\n /** The user. */\r\n user: PresenceUser;\r\n /** Current presence status. */\r\n status: EntityPresenceStatus;\r\n /** When they joined this entity view. */\r\n joinedAt: string;\r\n /** Last activity timestamp. */\r\n lastActivityAt: string;\r\n /** Field being edited (if editing). */\r\n editingField?: string;\r\n /** Cursor position (for text fields). */\r\n cursorPosition?: { start: number; end: number };\r\n /** Selection range (for grids). */\r\n selectedRange?: { startRow: number; endRow: number; startCol: number; endCol: number };\r\n}\r\n\r\n/** Configuration for useEntityPresence hook. */\r\nexport interface UseEntityPresenceOptions {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity type name (e.g., \"orders\", \"employees\"). */\r\n entityType: string;\r\n /** Entity ID being viewed/edited. Can be array for bulk selection. */\r\n entityId: string | string[];\r\n /** Current user information. */\r\n currentUser: PresenceUser;\r\n /** Initial presence status. Default: \"viewing\". */\r\n initialStatus?: EntityPresenceStatus;\r\n /** Heartbeat interval in ms. Default: 30000. */\r\n heartbeatInterval?: number;\r\n /** Idle timeout in ms. Default: 60000 (1 min). */\r\n idleTimeout?: number;\r\n /** Away timeout in ms. Default: 300000 (5 min). */\r\n awayTimeout?: number;\r\n /** Hub method for joining. Default: \"JoinEntityView\". */\r\n joinMethod?: string;\r\n /** Hub method for leaving. Default: \"LeaveEntityView\". */\r\n leaveMethod?: string;\r\n /** Hub method for status update. Default: \"UpdateEntityPresence\". */\r\n updateMethod?: string;\r\n /** Hub event for presence changes. Default: \"EntityPresenceChanged\". */\r\n presenceEventName?: string;\r\n /** Hub event for user joined. Default: \"EntityUserJoined\". */\r\n joinedEventName?: string;\r\n /** Hub event for user left. Default: \"EntityUserLeft\". */\r\n leftEventName?: string;\r\n /** Called when presence list changes. */\r\n onPresenceChange?: (presence: EntityPresenceInfo[]) => void;\r\n /** Called when a conflict is detected. */\r\n onEditConflict?: (editor: PresenceUser, field?: string) => void;\r\n}\r\n\r\n/** State returned by useEntityPresence hook. */\r\nexport interface EntityPresenceState {\r\n /** All users currently present on this entity. */\r\n presence: EntityPresenceInfo[];\r\n /** Users only viewing (not editing). */\r\n viewers: PresenceUser[];\r\n /** Users currently editing. */\r\n editors: PresenceUser[];\r\n /** Current user's status. */\r\n myStatus: EntityPresenceStatus;\r\n /** Set current user's status. */\r\n setMyStatus: (status: EntityPresenceStatus, editingField?: string) => void;\r\n /** Start editing a field. */\r\n startEditing: (field: string) => void;\r\n /** Stop editing. */\r\n stopEditing: () => void;\r\n /** Update cursor/selection position. */\r\n updateSelection: (selection: EntityPresenceInfo[\"selectedRange\"] | EntityPresenceInfo[\"cursorPosition\"]) => void;\r\n /** SignalR connection status. */\r\n connectionStatus: SignalRStatus;\r\n /** Check if another user is editing a specific field. */\r\n isFieldLocked: (field: string) => PresenceUser | null;\r\n /** Check if there are any active editors (conflict potential). */\r\n hasActiveEditors: boolean;\r\n /** Get the user editing a field (if any). */\r\n getFieldEditor: (field: string) => PresenceUser | null;\r\n /** Force refresh presence list. */\r\n refresh: () => Promise<void>;\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useEntityPresence(options: UseEntityPresenceOptions): EntityPresenceState {\r\n const {\r\n adapter,\r\n entityType,\r\n entityId,\r\n currentUser,\r\n initialStatus = \"viewing\",\r\n heartbeatInterval = 30000,\r\n idleTimeout = 60000,\r\n awayTimeout = 300000,\r\n joinMethod = \"JoinEntityView\",\r\n leaveMethod = \"LeaveEntityView\",\r\n updateMethod = \"UpdateEntityPresence\",\r\n presenceEventName = \"EntityPresenceChanged\",\r\n joinedEventName = \"EntityUserJoined\",\r\n leftEventName = \"EntityUserLeft\",\r\n onPresenceChange,\r\n onEditConflict,\r\n } = options;\r\n\r\n const [presence, setPresence] = useState<EntityPresenceInfo[]>([]);\r\n const [myStatus, setLocalMyStatus] = useState<EntityPresenceStatus>(initialStatus);\r\n const [editingField, setEditingField] = useState<string | undefined>();\r\n const [connectionStatus, setConnectionStatus] = useState<SignalRStatus>(adapter.status);\r\n\r\n const lastActivityRef = useRef<number>(Date.now());\r\n const heartbeatRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const awayTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n const entityIds = Array.isArray(entityId) ? entityId : [entityId];\r\n const entityKey = `${entityType}:${entityIds.sort().join(\",\")}`;\r\n\r\n // Send presence update to server\r\n const sendUpdate = useCallback(\r\n async (status: EntityPresenceStatus, field?: string, selection?: any) => {\r\n if (adapter.status !== \"connected\") return;\r\n try {\r\n await adapter.invoke(updateMethod, {\r\n entityType,\r\n entityIds,\r\n userId: currentUser.id,\r\n status,\r\n editingField: field,\r\n selection,\r\n timestamp: new Date().toISOString(),\r\n });\r\n } catch {\r\n // Ignore update errors\r\n }\r\n },\r\n [adapter, updateMethod, entityType, entityIds, currentUser.id],\r\n );\r\n\r\n // Set user status\r\n const setMyStatus = useCallback(\r\n (status: EntityPresenceStatus, field?: string) => {\r\n setLocalMyStatus(status);\r\n setEditingField(field);\r\n sendUpdate(status, field);\r\n lastActivityRef.current = Date.now();\r\n },\r\n [sendUpdate],\r\n );\r\n\r\n // Start editing a field\r\n const startEditing = useCallback(\r\n (field: string) => {\r\n // Check if someone else is editing this field\r\n const existingEditor = presence.find(\r\n (p) => p.user.id !== currentUser.id && p.status === \"editing\" && p.editingField === field,\r\n );\r\n if (existingEditor) {\r\n onEditConflict?.(existingEditor.user, field);\r\n }\r\n setMyStatus(\"editing\", field);\r\n },\r\n [presence, currentUser.id, onEditConflict, setMyStatus],\r\n );\r\n\r\n // Stop editing\r\n const stopEditing = useCallback(() => {\r\n setMyStatus(\"viewing\", undefined);\r\n }, [setMyStatus]);\r\n\r\n // Update cursor/selection\r\n const updateSelection = useCallback(\r\n (selection: EntityPresenceInfo[\"selectedRange\"] | EntityPresenceInfo[\"cursorPosition\"]) => {\r\n sendUpdate(myStatus, editingField, selection);\r\n lastActivityRef.current = Date.now();\r\n },\r\n [sendUpdate, myStatus, editingField],\r\n );\r\n\r\n // Check if a field is locked by another user\r\n const isFieldLocked = useCallback(\r\n (field: string): PresenceUser | null => {\r\n const editor = presence.find(\r\n (p) => p.user.id !== currentUser.id && p.status === \"editing\" && p.editingField === field,\r\n );\r\n return editor?.user ?? null;\r\n },\r\n [presence, currentUser.id],\r\n );\r\n\r\n // Get field editor\r\n const getFieldEditor = useCallback(\r\n (field: string): PresenceUser | null => {\r\n const editor = presence.find((p) => p.status === \"editing\" && p.editingField === field);\r\n return editor?.user ?? null;\r\n },\r\n [presence],\r\n );\r\n\r\n // Refresh presence from server\r\n const refresh = useCallback(async () => {\r\n if (adapter.status !== \"connected\") return;\r\n try {\r\n const result = await adapter.invoke(\"GetEntityPresence\", entityType, entityIds);\r\n if (Array.isArray(result)) {\r\n setPresence(result);\r\n onPresenceChange?.(result);\r\n }\r\n } catch {\r\n // Ignore refresh errors\r\n }\r\n }, [adapter, entityType, entityIds, onPresenceChange]);\r\n\r\n // Handle activity (reset idle timers)\r\n const handleActivity = useCallback(() => {\r\n lastActivityRef.current = Date.now();\r\n\r\n // Clear and reset idle timer\r\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current);\r\n if (awayTimerRef.current) clearTimeout(awayTimerRef.current);\r\n\r\n // If currently idle/away, go back to viewing/editing\r\n if (myStatus === \"idle\" || myStatus === \"away\") {\r\n setMyStatus(editingField ? \"editing\" : \"viewing\", editingField);\r\n }\r\n\r\n // Set new idle timer\r\n idleTimerRef.current = setTimeout(() => {\r\n setMyStatus(\"idle\", editingField);\r\n }, idleTimeout);\r\n\r\n // Set new away timer\r\n awayTimerRef.current = setTimeout(() => {\r\n setMyStatus(\"away\", editingField);\r\n }, awayTimeout);\r\n }, [myStatus, editingField, idleTimeout, awayTimeout, setMyStatus]);\r\n\r\n // Handle presence update events\r\n const handlePresenceChange = useCallback(\r\n (data: { entityType: string; entityIds: string[]; presence: EntityPresenceInfo[] }) => {\r\n if (data.entityType !== entityType) return;\r\n const hasCommonEntity = data.entityIds.some((id) => entityIds.includes(id));\r\n if (!hasCommonEntity) return;\r\n\r\n setPresence(data.presence.filter((p) => p.user.id !== currentUser.id));\r\n onPresenceChange?.(data.presence);\r\n },\r\n [entityType, entityIds, currentUser.id, onPresenceChange],\r\n );\r\n\r\n // Handle user joined\r\n const handleUserJoined = useCallback(\r\n (data: { entityType: string; entityIds: string[]; presence: EntityPresenceInfo }) => {\r\n if (data.entityType !== entityType) return;\r\n const hasCommonEntity = data.entityIds.some((id) => entityIds.includes(id));\r\n if (!hasCommonEntity || data.presence.user.id === currentUser.id) return;\r\n\r\n setPresence((prev) => {\r\n const existing = prev.findIndex((p) => p.user.id === data.presence.user.id);\r\n if (existing >= 0) {\r\n const next = [...prev];\r\n next[existing] = data.presence;\r\n return next;\r\n }\r\n return [...prev, data.presence];\r\n });\r\n },\r\n [entityType, entityIds, currentUser.id],\r\n );\r\n\r\n // Handle user left\r\n const handleUserLeft = useCallback(\r\n (data: { entityType: string; entityIds: string[]; userId: string }) => {\r\n if (data.entityType !== entityType) return;\r\n const hasCommonEntity = data.entityIds.some((id) => entityIds.includes(id));\r\n if (!hasCommonEntity) return;\r\n\r\n setPresence((prev) => prev.filter((p) => p.user.id !== data.userId));\r\n },\r\n [entityType, entityIds],\r\n );\r\n\r\n // Setup SignalR connection and events\r\n useEffect(() => {\r\n const unsubStatus = adapter.onStatusChange(setConnectionStatus);\r\n\r\n adapter.on(presenceEventName, handlePresenceChange);\r\n adapter.on(joinedEventName, handleUserJoined);\r\n adapter.on(leftEventName, handleUserLeft);\r\n\r\n // Join when connected\r\n const join = async () => {\r\n if (adapter.status !== \"connected\") return;\r\n try {\r\n await adapter.invoke(joinMethod, {\r\n entityType,\r\n entityIds,\r\n user: currentUser,\r\n status: myStatus,\r\n });\r\n await refresh();\r\n } catch {\r\n // Ignore join errors\r\n }\r\n };\r\n\r\n if (adapter.status === \"connected\") {\r\n join();\r\n }\r\n\r\n return () => {\r\n unsubStatus();\r\n adapter.off(presenceEventName, handlePresenceChange);\r\n adapter.off(joinedEventName, handleUserJoined);\r\n adapter.off(leftEventName, handleUserLeft);\r\n\r\n // Leave on cleanup\r\n if (adapter.status === \"connected\") {\r\n adapter.invoke(leaveMethod, {\r\n entityType,\r\n entityIds,\r\n userId: currentUser.id,\r\n }).catch(() => {});\r\n }\r\n };\r\n }, [\r\n adapter,\r\n entityKey,\r\n joinMethod,\r\n leaveMethod,\r\n presenceEventName,\r\n joinedEventName,\r\n leftEventName,\r\n handlePresenceChange,\r\n handleUserJoined,\r\n handleUserLeft,\r\n refresh,\r\n currentUser,\r\n myStatus,\r\n entityType,\r\n entityIds,\r\n ]);\r\n\r\n // Setup heartbeat\r\n useEffect(() => {\r\n heartbeatRef.current = setInterval(() => {\r\n sendUpdate(myStatus, editingField);\r\n }, heartbeatInterval);\r\n\r\n return () => {\r\n if (heartbeatRef.current) clearInterval(heartbeatRef.current);\r\n };\r\n }, [heartbeatInterval, sendUpdate, myStatus, editingField]);\r\n\r\n // Setup activity tracking\r\n useEffect(() => {\r\n const events = [\"mousemove\", \"keydown\", \"mousedown\", \"touchstart\", \"scroll\"];\r\n events.forEach((event) => window.addEventListener(event, handleActivity, { passive: true }));\r\n\r\n // Initial idle timer\r\n idleTimerRef.current = setTimeout(() => {\r\n setMyStatus(\"idle\", editingField);\r\n }, idleTimeout);\r\n\r\n awayTimerRef.current = setTimeout(() => {\r\n setMyStatus(\"away\", editingField);\r\n }, awayTimeout);\r\n\r\n return () => {\r\n events.forEach((event) => window.removeEventListener(event, handleActivity));\r\n if (idleTimerRef.current) clearTimeout(idleTimerRef.current);\r\n if (awayTimerRef.current) clearTimeout(awayTimerRef.current);\r\n };\r\n }, [handleActivity, idleTimeout, awayTimeout, editingField, setMyStatus]);\r\n\r\n // Derived state\r\n const viewers = presence.filter((p) => p.status === \"viewing\" || p.status === \"idle\").map((p) => p.user);\r\n const editors = presence.filter((p) => p.status === \"editing\").map((p) => p.user);\r\n const hasActiveEditors = editors.length > 0;\r\n\r\n return {\r\n presence,\r\n viewers,\r\n editors,\r\n myStatus,\r\n setMyStatus,\r\n startEditing,\r\n stopEditing,\r\n updateSelection,\r\n connectionStatus,\r\n isFieldLocked,\r\n hasActiveEditors,\r\n getFieldEditor,\r\n refresh,\r\n };\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Presence avatars component helper\r\n ================================================================ */\r\n\r\n/** Generate a consistent color for a user (based on ID hash). */\r\nexport function generateUserColor(userId: string): string {\r\n const colors = [\r\n \"#3b82f6\", // blue\r\n \"#ef4444\", // red\r\n \"#22c55e\", // green\r\n \"#f59e0b\", // amber\r\n \"#8b5cf6\", // violet\r\n \"#ec4899\", // pink\r\n \"#06b6d4\", // cyan\r\n \"#84cc16\", // lime\r\n \"#f97316\", // orange\r\n \"#14b8a6\", // teal\r\n ];\r\n let hash = 0;\r\n for (let i = 0; i < userId.length; i++) {\r\n hash = userId.charCodeAt(i) + ((hash << 5) - hash);\r\n }\r\n return colors[Math.abs(hash) % colors.length];\r\n}\r\n\r\n/** Format presence status for display. */\r\nexport function formatPresenceStatus(status: EntityPresenceStatus): string {\r\n switch (status) {\r\n case \"viewing\":\r\n return \"Viewing\";\r\n case \"editing\":\r\n return \"Editing\";\r\n case \"idle\":\r\n return \"Idle\";\r\n case \"away\":\r\n return \"Away\";\r\n default:\r\n return \"Unknown\";\r\n }\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Multi-entity presence tracking\r\n ================================================================ */\r\n\r\nexport interface MultiEntityPresenceOptions {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity type name. */\r\n entityType: string;\r\n /** Current user information. */\r\n currentUser: PresenceUser;\r\n /** Hub event name for batch presence updates. Default: \"BatchPresenceUpdate\". */\r\n batchEventName?: string;\r\n}\r\n\r\nexport interface MultiEntityPresenceState {\r\n /** Map of entity ID to presence info array. */\r\n presenceByEntity: Map<string, EntityPresenceInfo[]>;\r\n /** Get presence for a specific entity. */\r\n getPresence: (entityId: string) => EntityPresenceInfo[];\r\n /** Check if any user is editing an entity. */\r\n isEntityBeingEdited: (entityId: string) => boolean;\r\n /** Get editors for an entity. */\r\n getEntityEditors: (entityId: string) => PresenceUser[];\r\n}\r\n\r\n/**\r\n * Hook for tracking presence across multiple entities at once.\r\n * Useful for DataGrid row-level presence indicators.\r\n */\r\nexport function useMultiEntityPresence(options: MultiEntityPresenceOptions): MultiEntityPresenceState {\r\n const {\r\n adapter,\r\n entityType,\r\n currentUser,\r\n batchEventName = \"BatchPresenceUpdate\",\r\n } = options;\r\n\r\n const [presenceByEntity, setPresenceByEntity] = useState<Map<string, EntityPresenceInfo[]>>(new Map());\r\n\r\n // Handle batch presence update\r\n const handleBatchUpdate = useCallback(\r\n (data: { entityType: string; presenceByEntity: Record<string, EntityPresenceInfo[]> }) => {\r\n if (data.entityType !== entityType) return;\r\n setPresenceByEntity(new Map(Object.entries(data.presenceByEntity)));\r\n },\r\n [entityType],\r\n );\r\n\r\n useEffect(() => {\r\n adapter.on(batchEventName, handleBatchUpdate);\r\n\r\n return () => {\r\n adapter.off(batchEventName, handleBatchUpdate);\r\n };\r\n }, [adapter, batchEventName, handleBatchUpdate]);\r\n\r\n const getPresence = useCallback(\r\n (entityId: string): EntityPresenceInfo[] => {\r\n return presenceByEntity.get(entityId) ?? [];\r\n },\r\n [presenceByEntity],\r\n );\r\n\r\n const isEntityBeingEdited = useCallback(\r\n (entityId: string): boolean => {\r\n const presence = presenceByEntity.get(entityId);\r\n return presence?.some((p) => p.status === \"editing\" && p.user.id !== currentUser.id) ?? false;\r\n },\r\n [presenceByEntity, currentUser.id],\r\n );\r\n\r\n const getEntityEditors = useCallback(\r\n (entityId: string): PresenceUser[] => {\r\n const presence = presenceByEntity.get(entityId);\r\n return presence?.filter((p) => p.status === \"editing\").map((p) => p.user) ?? [];\r\n },\r\n [presenceByEntity],\r\n );\r\n\r\n return {\r\n presenceByEntity,\r\n getPresence,\r\n isEntityBeingEdited,\r\n getEntityEditors,\r\n };\r\n}\r\n","/**\r\n * useCollaborativeDataGrid — React hook for multi-user collaborative DataGrid editing.\r\n *\r\n * Combines real-time data updates via SignalR with presence tracking to enable\r\n * multiple users to edit the same DataGrid simultaneously with conflict resolution.\r\n *\r\n * Features:\r\n * - Real-time row insert/update/delete across all connected clients\r\n * - Cell-level locking to prevent simultaneous edits\r\n * - Visual presence indicators (who's viewing/editing which row/cell)\r\n * - Optimistic updates with automatic rollback on conflict\r\n * - CRDT-style last-write-wins or custom merge strategies\r\n * - Cursor sharing for selected cells/rows\r\n *\r\n * @example\r\n * ```tsx\r\n * const {\r\n * data, presence, mySelection,\r\n * startCellEdit, commitCellEdit, cancelCellEdit,\r\n * isCellLocked, getCellEditor,\r\n * } = useCollaborativeDataGrid({\r\n * adapter: signalRAdapter,\r\n * entityType: 'orders',\r\n * keyField: 'id',\r\n * currentUser: { id: userId, name: 'John' },\r\n * initialData: orders,\r\n * });\r\n *\r\n * return (\r\n * <NiceDataGrid\r\n * data={data}\r\n * keyField=\"id\"\r\n * columns={columns}\r\n * flashRowKeys={flashedKeys}\r\n * onCellEdit={handleEdit}\r\n * />\r\n * );\r\n * ```\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useRef, useMemo } from \"react\";\r\nimport type { ErpSignalRAdapter, SignalRStatus } from \"./ErpSignalRAdapter\";\r\nimport {\r\n useSignalRLiveData,\r\n type LiveDataChange,\r\n type UseSignalRLiveDataOptions,\r\n} from \"./useSignalRLiveData\";\r\nimport {\r\n useEntityPresence,\r\n type PresenceUser,\r\n type EntityPresenceInfo,\r\n type EntityPresenceStatus,\r\n generateUserColor,\r\n} from \"./useEntityPresence\";\r\n\r\n/* ================================================================\r\n TYPES\r\n ================================================================ */\r\n\r\n/** Cell position in the grid. */\r\nexport interface CellPosition {\r\n rowKey: unknown;\r\n field: string;\r\n}\r\n\r\n/** Range of cells. */\r\nexport interface CellRange {\r\n startRowKey: unknown;\r\n endRowKey: unknown;\r\n startField: string;\r\n endField: string;\r\n}\r\n\r\n/** Pending cell edit. */\r\nexport interface PendingEdit<T = unknown> {\r\n cell: CellPosition;\r\n originalValue: T;\r\n newValue: T;\r\n timestamp: number;\r\n}\r\n\r\n/** Conflict resolution strategy. */\r\nexport type ConflictStrategy = \"last-write-wins\" | \"first-write-wins\" | \"merge\" | \"ask-user\" | \"reject\";\r\n\r\n/** Edit conflict information. */\r\nexport interface EditConflict<T = unknown> {\r\n cell: CellPosition;\r\n localValue: T;\r\n remoteValue: T;\r\n localUser: PresenceUser;\r\n remoteUser: PresenceUser;\r\n localTimestamp: number;\r\n remoteTimestamp: number;\r\n}\r\n\r\n/** User cursor/selection in the grid. */\r\nexport interface UserSelection {\r\n user: PresenceUser;\r\n selectedCells: CellPosition[];\r\n selectedRows: unknown[];\r\n focusedCell: CellPosition | null;\r\n editingCell: CellPosition | null;\r\n color: string;\r\n}\r\n\r\n/** Configuration for useCollaborativeDataGrid hook. */\r\nexport interface UseCollaborativeDataGridOptions<T> {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity type name (e.g., \"orders\"). */\r\n entityType: string;\r\n /** Primary key field name. */\r\n keyField: keyof T & string;\r\n /** Current user information. */\r\n currentUser: PresenceUser;\r\n /** Initial data array. */\r\n initialData?: T[];\r\n /** Conflict resolution strategy. Default: \"last-write-wins\". */\r\n conflictStrategy?: ConflictStrategy;\r\n /** Custom merge function for \"merge\" strategy. */\r\n mergeFunction?: (local: T, remote: T, field: string) => T;\r\n /** Called when a conflict needs user resolution (for \"ask-user\" strategy). */\r\n onConflict?: (conflict: EditConflict<any>) => Promise<\"local\" | \"remote\" | \"merge\">;\r\n /** Called before a cell edit is committed. Return false to cancel. */\r\n onBeforeCommit?: (cell: CellPosition, oldValue: any, newValue: any) => boolean | Promise<boolean>;\r\n /** Called after a cell edit is committed. */\r\n onAfterCommit?: (cell: CellPosition, newValue: any) => void;\r\n /** Enable cell-level locking. Default: true. */\r\n enableCellLocking?: boolean;\r\n /** Lock timeout in ms. Default: 60000 (1 min). */\r\n lockTimeout?: number;\r\n /** Enable cursor sharing. Default: true. */\r\n enableCursorSharing?: boolean;\r\n /** Cursor update debounce in ms. Default: 50. */\r\n cursorDebounce?: number;\r\n /** Enable optimistic updates. Default: true. */\r\n optimisticUpdates?: boolean;\r\n /** Flash updated rows. Default: true. */\r\n flashChanges?: boolean;\r\n /** Flash duration in ms. Default: 500. */\r\n flashDuration?: number;\r\n /** Additional options for SignalR live data. */\r\n liveDataOptions?: Partial<UseSignalRLiveDataOptions<T>>;\r\n}\r\n\r\n/** State returned by useCollaborativeDataGrid hook. */\r\nexport interface CollaborativeDataGridState<T> {\r\n /** Current data array with real-time updates. */\r\n data: T[];\r\n /** All user presence information. */\r\n presence: EntityPresenceInfo[];\r\n /** Other users' selections and cursors. */\r\n userSelections: UserSelection[];\r\n /** Current user's selection. */\r\n mySelection: UserSelection;\r\n /** Set of row keys that were recently changed (for flash animation). */\r\n flashedKeys: Set<unknown>;\r\n /** Pending local edits. */\r\n pendingEdits: Map<string, PendingEdit>;\r\n /** Connection status. */\r\n connectionStatus: SignalRStatus;\r\n /** Current user's presence status. */\r\n myPresenceStatus: EntityPresenceStatus;\r\n\r\n // Selection methods\r\n /** Set focused cell. */\r\n setFocusedCell: (cell: CellPosition | null) => void;\r\n /** Set selected cells. */\r\n setSelectedCells: (cells: CellPosition[]) => void;\r\n /** Set selected rows. */\r\n setSelectedRows: (rowKeys: unknown[]) => void;\r\n\r\n // Edit methods\r\n /** Start editing a cell. Returns false if locked by another user. */\r\n startCellEdit: (cell: CellPosition) => boolean;\r\n /** Commit a cell edit. */\r\n commitCellEdit: (cell: CellPosition, newValue: any) => Promise<boolean>;\r\n /** Cancel a cell edit. */\r\n cancelCellEdit: (cell: CellPosition) => void;\r\n /** Check if a cell is locked by another user. */\r\n isCellLocked: (cell: CellPosition) => boolean;\r\n /** Get the user editing a cell (if any). */\r\n getCellEditor: (cell: CellPosition) => PresenceUser | null;\r\n /** Get the user's cursor color. */\r\n getUserColor: (userId: string) => string;\r\n\r\n // Row methods\r\n /** Insert a new row. */\r\n insertRow: (data: Partial<T>) => Promise<void>;\r\n /** Update a row. */\r\n updateRow: (key: unknown, changes: Partial<T>) => Promise<void>;\r\n /** Delete a row. */\r\n deleteRow: (key: unknown) => Promise<void>;\r\n\r\n // Conflict handling\r\n /** Resolve a pending conflict. */\r\n resolveConflict: (cell: CellPosition, resolution: \"local\" | \"remote\" | \"merge\") => void;\r\n /** Get current conflicts. */\r\n conflicts: EditConflict[];\r\n}\r\n\r\n/* ================================================================\r\n HELPER FUNCTIONS\r\n ================================================================ */\r\n\r\nfunction cellKey(cell: CellPosition): string {\r\n return `${String(cell.rowKey)}:${cell.field}`;\r\n}\r\n\r\nfunction parseTimestamp(ts: string | number | undefined): number {\r\n if (!ts) return Date.now();\r\n if (typeof ts === \"number\") return ts;\r\n return new Date(ts).getTime();\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useCollaborativeDataGrid<T extends Record<string, any>>(\r\n options: UseCollaborativeDataGridOptions<T>,\r\n): CollaborativeDataGridState<T> {\r\n const {\r\n adapter,\r\n entityType,\r\n keyField,\r\n currentUser,\r\n initialData = [],\r\n conflictStrategy = \"last-write-wins\",\r\n mergeFunction,\r\n onConflict,\r\n onBeforeCommit,\r\n onAfterCommit,\r\n enableCellLocking = true,\r\n lockTimeout = 60000,\r\n enableCursorSharing = true,\r\n cursorDebounce = 50,\r\n optimisticUpdates = true,\r\n flashChanges = true,\r\n flashDuration = 500,\r\n liveDataOptions = {},\r\n } = options;\r\n\r\n // Cell locks and pending edits\r\n const [cellLocks, setCellLocks] = useState<Map<string, { user: PresenceUser; timestamp: number }>>(new Map());\r\n const [pendingEdits, setPendingEdits] = useState<Map<string, PendingEdit>>(new Map());\r\n const [conflicts, setConflicts] = useState<EditConflict[]>([]);\r\n\r\n // Selection state\r\n const [focusedCell, setFocusedCell] = useState<CellPosition | null>(null);\r\n const [selectedCells, setSelectedCells] = useState<CellPosition[]>([]);\r\n const [selectedRows, setSelectedRows] = useState<unknown[]>([]);\r\n const [editingCell, setEditingCell] = useState<CellPosition | null>(null);\r\n\r\n // Other users' selections\r\n const [userSelections, setUserSelections] = useState<UserSelection[]>([]);\r\n\r\n const cursorDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const userColorCache = useRef<Map<string, string>>(new Map());\r\n\r\n // Get consistent color for a user\r\n const getUserColor = useCallback((userId: string): string => {\r\n if (!userColorCache.current.has(userId)) {\r\n userColorCache.current.set(userId, generateUserColor(userId));\r\n }\r\n return userColorCache.current.get(userId)!;\r\n }, []);\r\n\r\n // Conflict handler for live data\r\n const handleDataConflict = useCallback(\r\n (local: T, remote: T, change: LiveDataChange<T>): T | \"local\" | \"remote\" | \"merge\" => {\r\n if (!change.changes) return remote;\r\n\r\n // Check if this is related to a pending edit\r\n const pendingEdit = pendingEdits.get(cellKey({ rowKey: change.key, field: Object.keys(change.changes)[0] }));\r\n if (!pendingEdit) return remote;\r\n\r\n const localTimestamp = pendingEdit.timestamp;\r\n const remoteTimestamp = parseTimestamp(change.timestamp);\r\n\r\n switch (conflictStrategy) {\r\n case \"last-write-wins\":\r\n return localTimestamp > remoteTimestamp ? \"local\" : \"remote\";\r\n\r\n case \"first-write-wins\":\r\n return localTimestamp < remoteTimestamp ? \"local\" : \"remote\";\r\n\r\n case \"merge\":\r\n if (mergeFunction) {\r\n return mergeFunction(local, remote, Object.keys(change.changes)[0]);\r\n }\r\n return \"merge\";\r\n\r\n case \"ask-user\":\r\n // Add to conflicts queue\r\n const conflict: EditConflict = {\r\n cell: { rowKey: change.key, field: Object.keys(change.changes)[0] },\r\n localValue: pendingEdit.newValue,\r\n remoteValue: change.changes[Object.keys(change.changes)[0] as keyof T],\r\n localUser: currentUser,\r\n remoteUser: change.changedBy\r\n ? { id: change.changedBy, name: change.changedBy }\r\n : { id: \"unknown\", name: \"Unknown\" },\r\n localTimestamp,\r\n remoteTimestamp,\r\n };\r\n setConflicts((prev) => [...prev, conflict]);\r\n return \"local\"; // Keep local until resolved\r\n\r\n case \"reject\":\r\n return \"remote\";\r\n\r\n default:\r\n return \"remote\";\r\n }\r\n },\r\n [pendingEdits, conflictStrategy, mergeFunction, currentUser],\r\n );\r\n\r\n // Use SignalR live data hook\r\n const liveData = useSignalRLiveData<T>({\r\n adapter,\r\n entityName: entityType,\r\n keyField,\r\n initialData,\r\n flashChanges,\r\n flashDuration,\r\n optimisticUpdates,\r\n onConflict: handleDataConflict,\r\n ...liveDataOptions,\r\n });\r\n\r\n // Use entity presence hook\r\n const presence = useEntityPresence({\r\n adapter,\r\n entityType,\r\n entityId: \"grid\", // Special ID for grid-level presence\r\n currentUser,\r\n initialStatus: \"viewing\",\r\n });\r\n\r\n // Send cursor update\r\n const sendCursorUpdate = useCallback(() => {\r\n if (!enableCursorSharing || adapter.status !== \"connected\") return;\r\n\r\n adapter.invoke(\"UpdateGridSelection\", {\r\n entityType,\r\n userId: currentUser.id,\r\n focusedCell,\r\n selectedCells,\r\n selectedRows,\r\n editingCell,\r\n }).catch(() => {});\r\n }, [adapter, enableCursorSharing, entityType, currentUser.id, focusedCell, selectedCells, selectedRows, editingCell]);\r\n\r\n // Debounced cursor sharing\r\n useEffect(() => {\r\n if (!enableCursorSharing) return;\r\n\r\n if (cursorDebounceRef.current) {\r\n clearTimeout(cursorDebounceRef.current);\r\n }\r\n\r\n cursorDebounceRef.current = setTimeout(sendCursorUpdate, cursorDebounce);\r\n\r\n return () => {\r\n if (cursorDebounceRef.current) {\r\n clearTimeout(cursorDebounceRef.current);\r\n }\r\n };\r\n }, [enableCursorSharing, sendCursorUpdate, cursorDebounce, focusedCell, selectedCells, selectedRows, editingCell]);\r\n\r\n // Handle selection updates from other users\r\n useEffect(() => {\r\n if (!enableCursorSharing) return;\r\n\r\n const handler = (data: {\r\n entityType: string;\r\n userId: string;\r\n userName: string;\r\n avatarUrl?: string;\r\n focusedCell: CellPosition | null;\r\n selectedCells: CellPosition[];\r\n selectedRows: unknown[];\r\n editingCell: CellPosition | null;\r\n }) => {\r\n if (data.entityType !== entityType || data.userId === currentUser.id) return;\r\n\r\n setUserSelections((prev) => {\r\n const existing = prev.findIndex((s) => s.user.id === data.userId);\r\n const selection: UserSelection = {\r\n user: { id: data.userId, name: data.userName, avatarUrl: data.avatarUrl },\r\n focusedCell: data.focusedCell,\r\n selectedCells: data.selectedCells,\r\n selectedRows: data.selectedRows,\r\n editingCell: data.editingCell,\r\n color: getUserColor(data.userId),\r\n };\r\n\r\n if (existing >= 0) {\r\n const next = [...prev];\r\n next[existing] = selection;\r\n return next;\r\n }\r\n return [...prev, selection];\r\n });\r\n };\r\n\r\n adapter.on(\"GridSelectionUpdated\", handler);\r\n\r\n return () => {\r\n adapter.off(\"GridSelectionUpdated\", handler);\r\n };\r\n }, [adapter, enableCursorSharing, entityType, currentUser.id, getUserColor]);\r\n\r\n // Handle cell lock updates\r\n useEffect(() => {\r\n if (!enableCellLocking) return;\r\n\r\n const lockHandler = (data: { entityType: string; cell: CellPosition; user: PresenceUser; timestamp: number }) => {\r\n if (data.entityType !== entityType) return;\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.set(cellKey(data.cell), { user: data.user, timestamp: data.timestamp });\r\n return next;\r\n });\r\n };\r\n\r\n const unlockHandler = (data: { entityType: string; cell: CellPosition }) => {\r\n if (data.entityType !== entityType) return;\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(data.cell));\r\n return next;\r\n });\r\n };\r\n\r\n adapter.on(\"CellLocked\", lockHandler);\r\n adapter.on(\"CellUnlocked\", unlockHandler);\r\n\r\n return () => {\r\n adapter.off(\"CellLocked\", lockHandler);\r\n adapter.off(\"CellUnlocked\", unlockHandler);\r\n };\r\n }, [adapter, enableCellLocking, entityType]);\r\n\r\n // Clean up expired locks\r\n useEffect(() => {\r\n if (!enableCellLocking) return;\r\n\r\n const interval = setInterval(() => {\r\n const now = Date.now();\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n let changed = false;\r\n for (const [key, lock] of next) {\r\n if (now - lock.timestamp > lockTimeout) {\r\n next.delete(key);\r\n changed = true;\r\n }\r\n }\r\n return changed ? next : prev;\r\n });\r\n }, 10000);\r\n\r\n return () => clearInterval(interval);\r\n }, [enableCellLocking, lockTimeout]);\r\n\r\n // Check if a cell is locked\r\n const isCellLocked = useCallback(\r\n (cell: CellPosition): boolean => {\r\n if (!enableCellLocking) return false;\r\n const lock = cellLocks.get(cellKey(cell));\r\n if (!lock) return false;\r\n return lock.user.id !== currentUser.id && Date.now() - lock.timestamp < lockTimeout;\r\n },\r\n [enableCellLocking, cellLocks, currentUser.id, lockTimeout],\r\n );\r\n\r\n // Get the user editing a cell\r\n const getCellEditor = useCallback(\r\n (cell: CellPosition): PresenceUser | null => {\r\n const lock = cellLocks.get(cellKey(cell));\r\n if (!lock) return null;\r\n if (Date.now() - lock.timestamp > lockTimeout) return null;\r\n return lock.user;\r\n },\r\n [cellLocks, lockTimeout],\r\n );\r\n\r\n // Start editing a cell\r\n const startCellEdit = useCallback(\r\n (cell: CellPosition): boolean => {\r\n if (isCellLocked(cell)) return false;\r\n\r\n setEditingCell(cell);\r\n presence.startEditing(`${cell.rowKey}:${cell.field}`);\r\n\r\n // Acquire lock\r\n if (enableCellLocking && adapter.status === \"connected\") {\r\n adapter.invoke(\"AcquireCellLock\", {\r\n entityType,\r\n cell,\r\n user: currentUser,\r\n timestamp: Date.now(),\r\n }).catch(() => {});\r\n\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.set(cellKey(cell), { user: currentUser, timestamp: Date.now() });\r\n return next;\r\n });\r\n }\r\n\r\n return true;\r\n },\r\n [isCellLocked, presence, enableCellLocking, adapter, entityType, currentUser],\r\n );\r\n\r\n // Commit a cell edit\r\n const commitCellEdit = useCallback(\r\n async (cell: CellPosition, newValue: any): Promise<boolean> => {\r\n // Find current row\r\n const row = liveData.data.find((r) => r[keyField] === cell.rowKey);\r\n if (!row) return false;\r\n\r\n const oldValue = row[cell.field as keyof T];\r\n\r\n // Check before commit callback\r\n if (onBeforeCommit) {\r\n const allowed = await onBeforeCommit(cell, oldValue, newValue);\r\n if (!allowed) {\r\n cancelCellEdit(cell);\r\n return false;\r\n }\r\n }\r\n\r\n // Store pending edit\r\n const edit: PendingEdit = {\r\n cell,\r\n originalValue: oldValue,\r\n newValue,\r\n timestamp: Date.now(),\r\n };\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.set(cellKey(cell), edit);\r\n return next;\r\n });\r\n\r\n // Optimistic update\r\n if (optimisticUpdates) {\r\n liveData.registerPendingChange(cell.rowKey, { [cell.field]: newValue } as Partial<T>);\r\n }\r\n\r\n // Send to server\r\n try {\r\n await adapter.invoke(\"UpdateEntityField\", {\r\n entityType,\r\n entityId: cell.rowKey,\r\n field: cell.field,\r\n value: newValue,\r\n userId: currentUser.id,\r\n timestamp: edit.timestamp,\r\n });\r\n\r\n liveData.confirmPendingChange(cell.rowKey);\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n\r\n onAfterCommit?.(cell, newValue);\r\n\r\n // Release lock\r\n if (enableCellLocking) {\r\n adapter.invoke(\"ReleaseCellLock\", { entityType, cell }).catch(() => {});\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n }\r\n\r\n setEditingCell(null);\r\n presence.stopEditing();\r\n\r\n return true;\r\n } catch (error) {\r\n // Rollback on error\r\n liveData.rollbackPendingChange(cell.rowKey);\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n\r\n throw error;\r\n }\r\n },\r\n [\r\n liveData,\r\n keyField,\r\n onBeforeCommit,\r\n optimisticUpdates,\r\n adapter,\r\n entityType,\r\n currentUser.id,\r\n onAfterCommit,\r\n enableCellLocking,\r\n presence,\r\n ],\r\n );\r\n\r\n // Cancel a cell edit\r\n const cancelCellEdit = useCallback(\r\n (cell: CellPosition) => {\r\n setEditingCell(null);\r\n presence.stopEditing();\r\n\r\n // Release lock\r\n if (enableCellLocking && adapter.status === \"connected\") {\r\n adapter.invoke(\"ReleaseCellLock\", { entityType, cell }).catch(() => {});\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n }\r\n\r\n // Remove pending edit\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n },\r\n [presence, enableCellLocking, adapter, entityType],\r\n );\r\n\r\n // Resolve a conflict\r\n const resolveConflict = useCallback(\r\n (cell: CellPosition, resolution: \"local\" | \"remote\" | \"merge\") => {\r\n const conflict = conflicts.find((c) => cellKey(c.cell) === cellKey(cell));\r\n if (!conflict) return;\r\n\r\n if (resolution === \"remote\") {\r\n // Apply remote value\r\n liveData.applyRemoteUpdate({\r\n operation: \"update\",\r\n entityName: entityType,\r\n key: cell.rowKey,\r\n changes: { [cell.field]: conflict.remoteValue } as Partial<T>,\r\n });\r\n }\r\n // For \"local\" and \"merge\", keep current value\r\n\r\n // Remove from conflicts\r\n setConflicts((prev) => prev.filter((c) => cellKey(c.cell) !== cellKey(cell)));\r\n },\r\n [conflicts, liveData, entityType],\r\n );\r\n\r\n // Row operations\r\n const insertRow = useCallback(\r\n async (data: Partial<T>): Promise<void> => {\r\n await adapter.invoke(\"InsertEntity\", entityType, data);\r\n },\r\n [adapter, entityType],\r\n );\r\n\r\n const updateRow = useCallback(\r\n async (key: unknown, changes: Partial<T>): Promise<void> => {\r\n if (optimisticUpdates) {\r\n liveData.registerPendingChange(key, changes);\r\n }\r\n try {\r\n await adapter.invoke(\"UpdateEntity\", entityType, key, changes);\r\n liveData.confirmPendingChange(key);\r\n } catch (error) {\r\n liveData.rollbackPendingChange(key);\r\n throw error;\r\n }\r\n },\r\n [adapter, entityType, optimisticUpdates, liveData],\r\n );\r\n\r\n const deleteRow = useCallback(\r\n async (key: unknown): Promise<void> => {\r\n await adapter.invoke(\"DeleteEntity\", entityType, key);\r\n },\r\n [adapter, entityType],\r\n );\r\n\r\n // Build my selection\r\n const mySelection: UserSelection = useMemo(\r\n () => ({\r\n user: currentUser,\r\n focusedCell,\r\n selectedCells,\r\n selectedRows,\r\n editingCell,\r\n color: getUserColor(currentUser.id),\r\n }),\r\n [currentUser, focusedCell, selectedCells, selectedRows, editingCell, getUserColor],\r\n );\r\n\r\n return {\r\n data: liveData.data,\r\n presence: presence.presence,\r\n userSelections,\r\n mySelection,\r\n flashedKeys: liveData.flashedKeys,\r\n pendingEdits,\r\n connectionStatus: liveData.status,\r\n myPresenceStatus: presence.myStatus,\r\n\r\n // Selection methods\r\n setFocusedCell,\r\n setSelectedCells,\r\n setSelectedRows,\r\n\r\n // Edit methods\r\n startCellEdit,\r\n commitCellEdit,\r\n cancelCellEdit,\r\n isCellLocked,\r\n getCellEditor,\r\n getUserColor,\r\n\r\n // Row methods\r\n insertRow,\r\n updateRow,\r\n deleteRow,\r\n\r\n // Conflict handling\r\n resolveConflict,\r\n conflicts,\r\n };\r\n}\r\n\r\nexport default useCollaborativeDataGrid;\r\n","/**\r\n * ErpAuthAdapter — connects @nice2dev/auth components (LoginForm, RoleGuard, etc.)\r\n * to OmniVerk's JWT-based authentication API.\r\n */\r\n\r\nimport type { ErpLoginRequest, ErpLoginResponse, ErpUser, ErpResponse } from \"./types\";\r\n\r\nexport interface ErpAuthAdapterConfig {\r\n /** Auth API base URL, e.g. \"/api/auth\". */\r\n baseUrl: string;\r\n fetch?: typeof fetch;\r\n /** Called when a new token pair is obtained. */\r\n onTokens?: (access: string, refresh: string) => void;\r\n}\r\n\r\nexport class ErpAuthAdapter {\r\n private cfg: ErpAuthAdapterConfig;\r\n private accessToken: string | null = null;\r\n private refreshToken: string | null = null;\r\n\r\n constructor(config: ErpAuthAdapterConfig) {\r\n this.cfg = config;\r\n }\r\n\r\n /** Current access token (or null). */\r\n getAccessToken(): string | null {\r\n return this.accessToken;\r\n }\r\n\r\n /** Token factory for use with ErpDataAdapter.tokenFactory. */\r\n tokenFactory = async (): Promise<string> => {\r\n if (!this.accessToken) throw new Error(\"Not authenticated\");\r\n return this.accessToken;\r\n };\r\n\r\n private async post<R>(path: string, body: unknown): Promise<R> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}${path}`, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(body),\r\n });\r\n if (!res.ok) throw new Error(`Auth request failed: ${res.status}`);\r\n return res.json() as Promise<R>;\r\n }\r\n\r\n /** Login with username/password. */\r\n async login(req: ErpLoginRequest): Promise<ErpLoginResponse> {\r\n const result = await this.post<ErpLoginResponse>(\"/login\", req);\r\n if (result.token) {\r\n this.accessToken = result.token;\r\n this.refreshToken = result.refreshToken ?? null;\r\n this.cfg.onTokens?.(result.token, result.refreshToken ?? \"\");\r\n }\r\n return result;\r\n }\r\n\r\n /** Refresh the access token. */\r\n async refresh(): Promise<ErpLoginResponse> {\r\n if (!this.refreshToken) throw new Error(\"No refresh token\");\r\n const result = await this.post<ErpLoginResponse>(\"/refresh\", { refreshToken: this.refreshToken });\r\n if (result.token) {\r\n this.accessToken = result.token;\r\n this.refreshToken = result.refreshToken ?? this.refreshToken;\r\n this.cfg.onTokens?.(result.token, this.refreshToken!);\r\n }\r\n return result;\r\n }\r\n\r\n /** Get current user info. */\r\n async me(): Promise<ErpResponse<ErpUser>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/me`, {\r\n headers: { Authorization: `Bearer ${this.accessToken}` },\r\n });\r\n if (!res.ok) throw new Error(`Auth /me failed: ${res.status}`);\r\n return res.json() as Promise<ErpResponse<ErpUser>>;\r\n }\r\n\r\n /** Logout (invalidate tokens on server). */\r\n async logout(): Promise<void> {\r\n try {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n await f(`${this.cfg.baseUrl}/logout`, {\r\n method: \"POST\",\r\n headers: { Authorization: `Bearer ${this.accessToken}` },\r\n });\r\n } finally {\r\n this.accessToken = null;\r\n this.refreshToken = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * ErpFileAdapter — handles file uploads / downloads / management\r\n * against OmniVerk's blob-storage API.\r\n */\r\n\r\nimport type { ErpResponse, ErpFileInfo } from \"./types\";\r\n\r\nexport interface ErpFileAdapterConfig {\r\n /** File API base URL, e.g. \"/api/files\". */\r\n baseUrl: string;\r\n fetch?: typeof fetch;\r\n tokenFactory?: () => string | Promise<string>;\r\n}\r\n\r\n/** Progress callback for upload tracking. */\r\nexport type UploadProgressCallback = (event: UploadProgressEvent) => void;\r\n\r\nexport interface UploadProgressEvent {\r\n /** Bytes uploaded so far. */\r\n loaded: number;\r\n /** Total bytes (0 if unknown). */\r\n total: number;\r\n /** Progress as 0–1 fraction (NaN if total unknown). */\r\n percent: number;\r\n}\r\n\r\nexport class ErpFileAdapter {\r\n private cfg: ErpFileAdapterConfig;\r\n\r\n constructor(config: ErpFileAdapterConfig) {\r\n this.cfg = config;\r\n }\r\n\r\n private async headers(): Promise<Record<string, string>> {\r\n const h: Record<string, string> = {};\r\n if (this.cfg.tokenFactory) {\r\n h[\"Authorization\"] = `Bearer ${await this.cfg.tokenFactory()}`;\r\n }\r\n return h;\r\n }\r\n\r\n /** Upload a file. Returns file metadata on success. */\r\n async upload(file: File, folder?: string): Promise<ErpResponse<ErpFileInfo>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const form = new FormData();\r\n form.append(\"file\", file);\r\n if (folder) form.append(\"folder\", folder);\r\n const res = await f(this.cfg.baseUrl, {\r\n method: \"POST\",\r\n headers: await this.headers(),\r\n body: form,\r\n });\r\n if (!res.ok) throw new Error(`File upload failed: ${res.status}`);\r\n return res.json() as Promise<ErpResponse<ErpFileInfo>>;\r\n }\r\n\r\n /** Upload multiple files. */\r\n async uploadMultiple(files: File[], folder?: string): Promise<ErpResponse<ErpFileInfo[]>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const form = new FormData();\r\n files.forEach((file) => form.append(\"files\", file));\r\n if (folder) form.append(\"folder\", folder);\r\n const res = await f(`${this.cfg.baseUrl}/batch`, {\r\n method: \"POST\",\r\n headers: await this.headers(),\r\n body: form,\r\n });\r\n if (!res.ok) throw new Error(`Batch upload failed: ${res.status}`);\r\n return res.json() as Promise<ErpResponse<ErpFileInfo[]>>;\r\n }\r\n\r\n /** Get file metadata by id. */\r\n async getInfo(id: string): Promise<ErpResponse<ErpFileInfo>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/${id}/info`, {\r\n headers: { ...(await this.headers()), \"Content-Type\": \"application/json\" },\r\n });\r\n if (!res.ok) throw new Error(`File info failed: ${res.status}`);\r\n return res.json() as Promise<ErpResponse<ErpFileInfo>>;\r\n }\r\n\r\n /** Get a download URL (or blob URL) for a file. */\r\n async getDownloadUrl(id: string): Promise<string> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/${id}`, {\r\n headers: await this.headers(),\r\n });\r\n if (!res.ok) throw new Error(`File download failed: ${res.status}`);\r\n const blob = await res.blob();\r\n return URL.createObjectURL(blob);\r\n }\r\n\r\n /** Delete file by id. */\r\n async remove(id: string): Promise<ErpResponse<void>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/${id}`, {\r\n method: \"DELETE\",\r\n headers: { ...(await this.headers()), \"Content-Type\": \"application/json\" },\r\n });\r\n if (!res.ok) throw new Error(`File delete failed: ${res.status}`);\r\n return res.json() as Promise<ErpResponse<void>>;\r\n }\r\n\r\n /**\r\n * Upload a file with progress tracking via XMLHttpRequest.\r\n * Falls back to standard fetch-based upload if XHR is unavailable.\r\n */\r\n uploadWithProgress(\r\n file: File,\r\n onProgress: UploadProgressCallback,\r\n folder?: string,\r\n signal?: AbortSignal,\r\n ): Promise<ErpResponse<ErpFileInfo>> {\r\n if (typeof XMLHttpRequest === \"undefined\") {\r\n return this.upload(file, folder);\r\n }\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n const form = new FormData();\r\n form.append(\"file\", file);\r\n if (folder) form.append(\"folder\", folder);\r\n\r\n // Abort support\r\n if (signal) {\r\n if (signal.aborted) {\r\n reject(new DOMException(\"Aborted\", \"AbortError\"));\r\n return;\r\n }\r\n signal.addEventListener(\"abort\", () => {\r\n xhr.abort();\r\n reject(new DOMException(\"Aborted\", \"AbortError\"));\r\n });\r\n }\r\n\r\n xhr.upload.addEventListener(\"progress\", (e) => {\r\n onProgress({\r\n loaded: e.loaded,\r\n total: e.total,\r\n percent: e.lengthComputable ? e.loaded / e.total : NaN,\r\n });\r\n });\r\n\r\n xhr.addEventListener(\"load\", () => {\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n try {\r\n resolve(JSON.parse(xhr.responseText));\r\n } catch {\r\n reject(new Error(\"Invalid JSON response\"));\r\n }\r\n } else {\r\n reject(new Error(`File upload failed: ${xhr.status}`));\r\n }\r\n });\r\n\r\n xhr.addEventListener(\"error\", () => reject(new Error(\"Network error during upload\")));\r\n xhr.addEventListener(\"abort\", () => reject(new DOMException(\"Aborted\", \"AbortError\")));\r\n\r\n xhr.open(\"POST\", this.cfg.baseUrl);\r\n const h = await this.headers();\r\n Object.entries(h).forEach(([k, v]) => xhr.setRequestHeader(k, v));\r\n xhr.send(form);\r\n });\r\n }\r\n\r\n /**\r\n * Upload multiple files with per-file progress tracking.\r\n * Returns progress for each file individually.\r\n */\r\n async uploadMultipleWithProgress(\r\n files: File[],\r\n onProgress: (fileIndex: number, event: UploadProgressEvent) => void,\r\n folder?: string,\r\n signal?: AbortSignal,\r\n ): Promise<ErpResponse<ErpFileInfo>[]> {\r\n const results: ErpResponse<ErpFileInfo>[] = [];\r\n for (let i = 0; i < files.length; i++) {\r\n if (signal?.aborted) throw new DOMException(\"Aborted\", \"AbortError\");\r\n const res = await this.uploadWithProgress(\r\n files[i],\r\n (evt) => onProgress(i, evt),\r\n folder,\r\n signal,\r\n );\r\n results.push(res);\r\n }\r\n return results;\r\n }\r\n}\r\n","/**\r\n * ErpExportAdapter — connects NiceDataGrid export actions to OmniVerk's\r\n * server-side report engine (xlsx, pdf, csv, json, xml).\r\n */\r\n\r\nimport type { ErpExportRequest, ErpExportResponse, ErpResponse } from \"./types\";\r\n\r\nexport interface ErpExportAdapterConfig {\r\n /** Export API base URL, e.g. \"/api/export\". */\r\n baseUrl: string;\r\n fetch?: typeof fetch;\r\n tokenFactory?: () => string | Promise<string>;\r\n}\r\n\r\nexport class ErpExportAdapter {\r\n private cfg: ErpExportAdapterConfig;\r\n\r\n constructor(config: ErpExportAdapterConfig) {\r\n this.cfg = config;\r\n }\r\n\r\n private async headers(): Promise<Record<string, string>> {\r\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\r\n if (this.cfg.tokenFactory) {\r\n h[\"Authorization\"] = `Bearer ${await this.cfg.tokenFactory()}`;\r\n }\r\n return h;\r\n }\r\n\r\n /** Request a server-side export. Returns a download URL / job id. */\r\n async requestExport(req: ErpExportRequest): Promise<ErpResponse<ErpExportResponse>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(this.cfg.baseUrl, {\r\n method: \"POST\",\r\n headers: await this.headers(),\r\n body: JSON.stringify(req),\r\n });\r\n if (!res.ok) throw new Error(`Export request failed: ${res.status}`);\r\n return res.json() as Promise<ErpResponse<ErpExportResponse>>;\r\n }\r\n\r\n /** Download an exported file as a Blob. */\r\n async download(downloadUrl: string): Promise<Blob> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(downloadUrl, {\r\n headers: await this.headers(),\r\n });\r\n if (!res.ok) throw new Error(`Export download failed: ${res.status}`);\r\n return res.blob();\r\n }\r\n\r\n /** Trigger browser download for an export. */\r\n async downloadToFile(req: ErpExportRequest, filename?: string): Promise<void> {\r\n const result = await this.requestExport(req);\r\n if (!result.data?.downloadUrl) throw new Error(\"No download URL returned\");\r\n const blob = await this.download(result.data.downloadUrl);\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement(\"a\");\r\n a.href = url;\r\n a.download = filename ?? `export.${req.format}`;\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n }\r\n}\r\n","/**\r\n * ErpMiddleware — composable request/response interceptor pipeline.\r\n *\r\n * Middleware functions wrap `fetch` calls, enabling logging, retry,\r\n * header injection, metrics, caching, etc. without modifying adapters.\r\n *\r\n * Usage:\r\n * const pipeline = createMiddlewarePipeline([\r\n * loggingMiddleware(),\r\n * retryMiddleware({ maxRetries: 3 }),\r\n * ]);\r\n * const adapter = new ErpDataAdapter({ baseUrl: '/api/products', fetch: pipeline(fetch) });\r\n */\r\n\r\n/** Request context available to middleware. */\r\nexport interface ErpRequestContext {\r\n url: string;\r\n init: RequestInit;\r\n /** Custom metadata bag — middleware can attach/read arbitrary keys. */\r\n meta: Record<string, unknown>;\r\n}\r\n\r\n/** A middleware function: receives context + next handler, returns Response. */\r\nexport type ErpMiddleware = (\r\n ctx: ErpRequestContext,\r\n next: (ctx: ErpRequestContext) => Promise<Response>,\r\n) => Promise<Response>;\r\n\r\n/**\r\n * Compose an array of middleware into a single `fetch`-compatible function.\r\n *\r\n * Middleware executes in order: first in the array wraps outermost.\r\n * The innermost call delegates to the real `fetch`.\r\n */\r\nexport function createMiddlewarePipeline(\r\n middlewares: ErpMiddleware[],\r\n): (baseFetch?: typeof fetch) => typeof fetch {\r\n return (baseFetch?: typeof fetch) => {\r\n const realFetch = baseFetch ?? globalThis.fetch;\r\n\r\n const wrappedFetch: typeof fetch = (input, init?) => {\r\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.toString() : (input as Request).url;\r\n const mergedInit: RequestInit = init ?? {};\r\n const ctx: ErpRequestContext = { url, init: mergedInit, meta: {} };\r\n\r\n // Build chain from right (innermost) to left (outermost)\r\n let handler = (c: ErpRequestContext): Promise<Response> =>\r\n realFetch(c.url, c.init);\r\n\r\n for (let i = middlewares.length - 1; i >= 0; i--) {\r\n const mw = middlewares[i];\r\n const nextHandler = handler;\r\n handler = (c) => mw(c, nextHandler);\r\n }\r\n\r\n return handler(ctx);\r\n };\r\n\r\n return wrappedFetch;\r\n };\r\n}\r\n\r\n/* ── Built-in middleware ──────────────────────────────────────── */\r\n\r\n/** Logging middleware — logs request/response timing to console. */\r\nexport function loggingMiddleware(): ErpMiddleware {\r\n return async (ctx, next) => {\r\n const start = performance.now();\r\n const method = (ctx.init.method ?? \"GET\").toUpperCase();\r\n try {\r\n const res = await next(ctx);\r\n const ms = (performance.now() - start).toFixed(1);\r\n console.debug(`[ERP] ${method} ${ctx.url} → ${res.status} (${ms}ms)`);\r\n return res;\r\n } catch (err) {\r\n const ms = (performance.now() - start).toFixed(1);\r\n console.error(`[ERP] ${method} ${ctx.url} FAILED (${ms}ms)`, err);\r\n throw err;\r\n }\r\n };\r\n}\r\n\r\nexport interface RetryOptions {\r\n /** Maximum number of retry attempts (default: 3). */\r\n maxRetries?: number;\r\n /** Base delay in ms for exponential backoff (default: 500). */\r\n baseDelay?: number;\r\n /** HTTP status codes that trigger retry (default: [408, 429, 500, 502, 503, 504]). */\r\n retryOn?: number[];\r\n}\r\n\r\n/** Retry middleware with exponential backoff for transient failures. */\r\nexport function retryMiddleware(opts?: RetryOptions): ErpMiddleware {\r\n const maxRetries = opts?.maxRetries ?? 3;\r\n const baseDelay = opts?.baseDelay ?? 500;\r\n const retryOn = new Set(opts?.retryOn ?? [408, 429, 500, 502, 503, 504]);\r\n\r\n return async (ctx, next) => {\r\n let lastError: unknown;\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n const res = await next(ctx);\r\n if (res.ok || !retryOn.has(res.status) || attempt === maxRetries) {\r\n return res;\r\n }\r\n // Transient error — will retry\r\n lastError = new Error(`HTTP ${res.status}`);\r\n } catch (err) {\r\n lastError = err;\r\n if (attempt === maxRetries) throw err;\r\n }\r\n // Exponential backoff with jitter\r\n const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);\r\n await new Promise((r) => setTimeout(r, delay));\r\n }\r\n throw lastError;\r\n };\r\n}\r\n\r\n/** Header injection middleware — merges extra headers into every request. */\r\nexport function headerMiddleware(\r\n headers: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>),\r\n): ErpMiddleware {\r\n return async (ctx, next) => {\r\n const extra = typeof headers === \"function\" ? await headers() : headers;\r\n ctx.init.headers = { ...(ctx.init.headers as Record<string, string>), ...extra };\r\n return next(ctx);\r\n };\r\n}\r\n","/**\r\n * ErpRateLimiter — client-side rate limiting / throttling for ERP requests.\r\n *\r\n * Wraps fetch to enforce:\r\n * - Maximum concurrent requests\r\n * - Sliding window request rate\r\n * - Automatic 429 back-off\r\n *\r\n * Usage:\r\n * const limiter = new ErpRateLimiter({ maxConcurrent: 4, maxPerSecond: 10 });\r\n * const adapter = new ErpDataAdapter({ baseUrl: '/api/products', fetch: limiter.fetch });\r\n */\r\n\r\nexport interface ErpRateLimiterConfig {\r\n /** Maximum concurrent in-flight requests (default: 6). */\r\n maxConcurrent?: number;\r\n /** Maximum requests per second (default: 20). */\r\n maxPerSecond?: number;\r\n /** Delay (ms) to wait after receiving a 429 response (default: 2000). */\r\n backoffDelay?: number;\r\n /** Base fetch implementation (default: globalThis.fetch). */\r\n baseFetch?: typeof fetch;\r\n}\r\n\r\ninterface QueueEntry {\r\n resolve: (value: Response | PromiseLike<Response>) => void;\r\n reject: (reason?: unknown) => void;\r\n input: RequestInfo | URL;\r\n init?: RequestInit;\r\n}\r\n\r\nexport class ErpRateLimiter {\r\n private maxConcurrent: number;\r\n private maxPerSecond: number;\r\n private backoffDelay: number;\r\n private baseFetch: typeof fetch;\r\n\r\n private inFlight = 0;\r\n private timestamps: number[] = [];\r\n private queue: QueueEntry[] = [];\r\n private processing = false;\r\n private backingOff = false;\r\n\r\n constructor(config?: ErpRateLimiterConfig) {\r\n this.maxConcurrent = config?.maxConcurrent ?? 6;\r\n this.maxPerSecond = config?.maxPerSecond ?? 20;\r\n this.backoffDelay = config?.backoffDelay ?? 2000;\r\n this.baseFetch = config?.baseFetch ?? globalThis.fetch.bind(globalThis);\r\n\r\n // Bind fetch so it can be passed as a plain function\r\n this.fetch = this.fetch.bind(this);\r\n }\r\n\r\n /** A fetch-compatible function that respects rate limits. */\r\n fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\r\n return new Promise<Response>((resolve, reject) => {\r\n this.queue.push({ resolve, reject, input, init });\r\n this.drain();\r\n });\r\n }\r\n\r\n /** Number of requests currently waiting in the queue. */\r\n get pending(): number {\r\n return this.queue.length;\r\n }\r\n\r\n /** Number of requests currently in flight. */\r\n get active(): number {\r\n return this.inFlight;\r\n }\r\n\r\n private drain(): void {\r\n if (this.processing) return;\r\n this.processing = true;\r\n\r\n const tryNext = () => {\r\n if (this.queue.length === 0 || this.backingOff) {\r\n this.processing = false;\r\n return;\r\n }\r\n\r\n // Enforce concurrent limit\r\n if (this.inFlight >= this.maxConcurrent) {\r\n this.processing = false;\r\n return;\r\n }\r\n\r\n // Enforce rate limit (sliding window of 1 second)\r\n const now = Date.now();\r\n this.timestamps = this.timestamps.filter((t) => now - t < 1000);\r\n if (this.timestamps.length >= this.maxPerSecond) {\r\n const oldestInWindow = this.timestamps[0];\r\n const waitMs = 1000 - (now - oldestInWindow) + 1;\r\n setTimeout(() => {\r\n this.processing = false;\r\n this.drain();\r\n }, waitMs);\r\n return;\r\n }\r\n\r\n const entry = this.queue.shift()!;\r\n this.inFlight++;\r\n this.timestamps.push(now);\r\n\r\n this.baseFetch(entry.input, entry.init)\r\n .then((res) => {\r\n this.inFlight--;\r\n if (res.status === 429) {\r\n // Re-queue and back off\r\n this.queue.unshift(entry);\r\n this.inFlight++; // undo: we didn't really complete\r\n this.inFlight--;\r\n this.backingOff = true;\r\n setTimeout(() => {\r\n this.backingOff = false;\r\n this.drain();\r\n }, this.backoffDelay);\r\n return;\r\n }\r\n entry.resolve(res);\r\n // Immediately attempt next\r\n queueMicrotask(tryNext);\r\n })\r\n .catch((err) => {\r\n this.inFlight--;\r\n entry.reject(err);\r\n queueMicrotask(tryNext);\r\n });\r\n\r\n // Try to launch more concurrent requests\r\n queueMicrotask(tryNext);\r\n };\r\n\r\n tryNext();\r\n }\r\n}\r\n","/**\r\n * ErpOptimisticStore — client-side optimistic mutation store.\r\n *\r\n * Applies mutations optimistically (instant UI update) and rolls back\r\n * if the server request fails. Works with any ErpDataAdapter.\r\n *\r\n * Usage:\r\n * const store = new ErpOptimisticStore(adapter);\r\n * const items = store.getItems(); // reactive snapshot\r\n * await store.create({ name: 'New' }); // instant add, confirmed by server\r\n * await store.update('42', { name: 'Edit' }); // instant change, rolled back on error\r\n */\r\n\r\nimport type { ErpResponse } from \"./types\";\r\nimport type { ErpDataAdapter } from \"./ErpDataAdapter\";\r\n\r\n/** Pending mutation waiting for server confirmation. */\r\nexport interface PendingMutation<T = unknown> {\r\n id: string;\r\n type: \"create\" | \"update\" | \"delete\";\r\n entityId?: string | number;\r\n optimisticData?: T;\r\n previousData?: T;\r\n timestamp: number;\r\n status: \"pending\" | \"confirmed\" | \"failed\";\r\n error?: string;\r\n}\r\n\r\nexport type OptimisticListener<T> = (items: T[], pending: PendingMutation<T>[]) => void;\r\n\r\nexport class ErpOptimisticStore<T extends Record<string, unknown> = Record<string, unknown>> {\r\n private adapter: ErpDataAdapter<T>;\r\n private items: T[] = [];\r\n private keyField: string;\r\n private mutations: PendingMutation<T>[] = [];\r\n private listeners = new Set<OptimisticListener<T>>();\r\n private nextMutationId = 1;\r\n\r\n constructor(adapter: ErpDataAdapter<T>, keyField = \"id\") {\r\n this.adapter = adapter;\r\n this.keyField = keyField;\r\n }\r\n\r\n /** Subscribe to state changes. Returns unsubscribe function. */\r\n subscribe(listener: OptimisticListener<T>): () => void {\r\n this.listeners.add(listener);\r\n return () => this.listeners.delete(listener);\r\n }\r\n\r\n private notify(): void {\r\n const snapshot = [...this.items];\r\n const pending = [...this.mutations];\r\n this.listeners.forEach((fn) => fn(snapshot, pending));\r\n }\r\n\r\n /** Current items including optimistic (unconfirmed) changes. */\r\n getItems(): T[] {\r\n return [...this.items];\r\n }\r\n\r\n /** Pending mutations not yet confirmed by server. */\r\n getPending(): PendingMutation<T>[] {\r\n return this.mutations.filter((m) => m.status === \"pending\");\r\n }\r\n\r\n /** Set initial data (e.g. after load()). */\r\n setItems(items: T[]): void {\r\n this.items = [...items];\r\n this.notify();\r\n }\r\n\r\n /** Load data from server and replace local state. */\r\n async load(...args: Parameters<ErpDataAdapter<T>[\"load\"]>): Promise<ErpResponse<T[]>> {\r\n const res = await this.adapter.load(...args);\r\n if (res.success) {\r\n this.items = res.data;\r\n this.mutations = [];\r\n this.notify();\r\n }\r\n return res;\r\n }\r\n\r\n /** Optimistically create an item. */\r\n async create(item: Partial<T>): Promise<ErpResponse<T>> {\r\n const tempKey = `__temp_${this.nextMutationId}`;\r\n const optimistic = { ...item, [this.keyField]: tempKey } as T;\r\n const mutId = String(this.nextMutationId++);\r\n const mutation: PendingMutation<T> = {\r\n id: mutId,\r\n type: \"create\",\r\n optimisticData: optimistic,\r\n timestamp: Date.now(),\r\n status: \"pending\",\r\n };\r\n\r\n // Apply optimistically\r\n this.items = [...this.items, optimistic];\r\n this.mutations.push(mutation);\r\n this.notify();\r\n\r\n try {\r\n const res = await this.adapter.create(item);\r\n if (res.success) {\r\n // Replace temp item with server-confirmed item\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === tempKey ? res.data : it,\r\n );\r\n mutation.status = \"confirmed\";\r\n mutation.entityId = (res.data as Record<string, unknown>)[this.keyField] as string;\r\n } else {\r\n // Rollback\r\n this.items = this.items.filter(\r\n (it) => (it as Record<string, unknown>)[this.keyField] !== tempKey,\r\n );\r\n mutation.status = \"failed\";\r\n mutation.error = res.error;\r\n }\r\n this.notify();\r\n return res;\r\n } catch (err) {\r\n // Rollback on network error\r\n this.items = this.items.filter(\r\n (it) => (it as Record<string, unknown>)[this.keyField] !== tempKey,\r\n );\r\n mutation.status = \"failed\";\r\n mutation.error = err instanceof Error ? err.message : String(err);\r\n this.notify();\r\n throw err;\r\n }\r\n }\r\n\r\n /** Optimistically update an item. */\r\n async update(id: string | number, changes: Partial<T>): Promise<ErpResponse<T>> {\r\n const idx = this.items.findIndex(\r\n (it) => (it as Record<string, unknown>)[this.keyField] === id,\r\n );\r\n const previous = idx >= 0 ? { ...this.items[idx] } : undefined;\r\n const mutId = String(this.nextMutationId++);\r\n const mutation: PendingMutation<T> = {\r\n id: mutId,\r\n type: \"update\",\r\n entityId: id,\r\n previousData: previous,\r\n timestamp: Date.now(),\r\n status: \"pending\",\r\n };\r\n\r\n // Apply optimistically\r\n if (idx >= 0) {\r\n this.items = this.items.map((it, i) =>\r\n i === idx ? { ...it, ...changes } : it,\r\n );\r\n }\r\n this.mutations.push(mutation);\r\n this.notify();\r\n\r\n try {\r\n const res = await this.adapter.update(id, changes);\r\n if (res.success) {\r\n // Replace with server version\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === id ? res.data : it,\r\n );\r\n mutation.status = \"confirmed\";\r\n } else {\r\n // Rollback\r\n if (previous && idx >= 0) {\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === id ? previous : it,\r\n );\r\n }\r\n mutation.status = \"failed\";\r\n mutation.error = res.error;\r\n }\r\n this.notify();\r\n return res;\r\n } catch (err) {\r\n // Rollback\r\n if (previous && idx >= 0) {\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === id ? previous : it,\r\n );\r\n }\r\n mutation.status = \"failed\";\r\n mutation.error = err instanceof Error ? err.message : String(err);\r\n this.notify();\r\n throw err;\r\n }\r\n }\r\n\r\n /** Optimistically delete an item. */\r\n async remove(id: string | number): Promise<ErpResponse<void>> {\r\n const idx = this.items.findIndex(\r\n (it) => (it as Record<string, unknown>)[this.keyField] === id,\r\n );\r\n const previous = idx >= 0 ? { ...this.items[idx] } : undefined;\r\n const mutId = String(this.nextMutationId++);\r\n const mutation: PendingMutation<T> = {\r\n id: mutId,\r\n type: \"delete\",\r\n entityId: id,\r\n previousData: previous,\r\n timestamp: Date.now(),\r\n status: \"pending\",\r\n };\r\n\r\n // Apply optimistically — remove immediately\r\n this.items = this.items.filter(\r\n (it) => (it as Record<string, unknown>)[this.keyField] !== id,\r\n );\r\n this.mutations.push(mutation);\r\n this.notify();\r\n\r\n try {\r\n const res = await this.adapter.remove(id);\r\n if (res.success) {\r\n mutation.status = \"confirmed\";\r\n } else {\r\n // Rollback — re-insert\r\n if (previous) this.items.splice(idx, 0, previous);\r\n mutation.status = \"failed\";\r\n mutation.error = res.error;\r\n }\r\n this.notify();\r\n return res;\r\n } catch (err) {\r\n if (previous) this.items.splice(idx, 0, previous);\r\n mutation.status = \"failed\";\r\n mutation.error = err instanceof Error ? err.message : String(err);\r\n this.notify();\r\n throw err;\r\n }\r\n }\r\n}\r\n","/**\r\n * ErpOfflineQueue — queues requests when offline and replays them on reconnect.\r\n *\r\n * Wraps fetch to detect navigator.onLine. When offline, mutations (POST/PUT/PATCH/DELETE)\r\n * are stored in an IndexedDB-backed queue and replayed in order when connectivity returns.\r\n *\r\n * GET requests while offline reject immediately (or return cached data if cacheGets is true).\r\n *\r\n * Usage:\r\n * const queue = new ErpOfflineQueue({ onSync: (results) => console.log('Synced', results) });\r\n * const adapter = new ErpDataAdapter({ baseUrl: '/api/products', fetch: queue.fetch });\r\n */\r\n\r\nexport interface ErpOfflineQueueConfig {\r\n /** Base fetch to use when online (default: globalThis.fetch). */\r\n baseFetch?: typeof fetch;\r\n /** Called when the queue finishes replaying after reconnect. */\r\n onSync?: (results: SyncResult[]) => void;\r\n /** Called when a request is queued while offline. */\r\n onQueued?: (entry: QueueEntry) => void;\r\n /** Called on online/offline status change. */\r\n onStatusChange?: (online: boolean) => void;\r\n /** Storage key prefix for persistence (default: \"erp_offline_\"). */\r\n storageKey?: string;\r\n}\r\n\r\nexport interface QueueEntry {\r\n id: string;\r\n url: string;\r\n method: string;\r\n headers: Record<string, string>;\r\n body?: string;\r\n timestamp: number;\r\n}\r\n\r\nexport interface SyncResult {\r\n entry: QueueEntry;\r\n success: boolean;\r\n status?: number;\r\n error?: string;\r\n}\r\n\r\nexport class ErpOfflineQueue {\r\n private baseFetch: typeof fetch;\r\n private queue: QueueEntry[] = [];\r\n private config: ErpOfflineQueueConfig;\r\n private syncing = false;\r\n private storageKey: string;\r\n private nextId = 1;\r\n private boundOnline: () => void;\r\n private boundOffline: () => void;\r\n\r\n constructor(config?: ErpOfflineQueueConfig) {\r\n this.config = config ?? {};\r\n this.baseFetch = config?.baseFetch ?? globalThis.fetch.bind(globalThis);\r\n this.storageKey = config?.storageKey ?? \"erp_offline_\";\r\n\r\n this.fetch = this.fetch.bind(this);\r\n\r\n // Restore persisted queue\r\n this.restoreQueue();\r\n\r\n // Listen for online/offline events\r\n this.boundOnline = () => {\r\n this.config.onStatusChange?.(true);\r\n this.sync();\r\n };\r\n this.boundOffline = () => {\r\n this.config.onStatusChange?.(false);\r\n };\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.addEventListener(\"online\", this.boundOnline);\r\n window.addEventListener(\"offline\", this.boundOffline);\r\n }\r\n }\r\n\r\n /** A fetch-compatible function that queues mutations when offline. */\r\n fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\r\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.toString() : (input as Request).url;\r\n const method = (init?.method ?? \"GET\").toUpperCase();\r\n\r\n // If online, use real fetch\r\n if (this.isOnline()) {\r\n return this.baseFetch(input, init);\r\n }\r\n\r\n // Offline: GET requests fail immediately\r\n if (method === \"GET\" || method === \"HEAD\") {\r\n return Promise.reject(new Error(\"Offline: GET requests are not queued\"));\r\n }\r\n\r\n // Queue the mutation\r\n const entry: QueueEntry = {\r\n id: `q_${this.nextId++}`,\r\n url,\r\n method,\r\n headers: { ...(init?.headers as Record<string, string>) },\r\n body: typeof init?.body === \"string\" ? init.body : undefined,\r\n timestamp: Date.now(),\r\n };\r\n\r\n this.queue.push(entry);\r\n this.persistQueue();\r\n this.config.onQueued?.(entry);\r\n\r\n // Return a synthetic 202 Accepted response\r\n return Promise.resolve(new Response(\r\n JSON.stringify({ queued: true, queueId: entry.id }),\r\n { status: 202, headers: { \"Content-Type\": \"application/json\" } },\r\n ));\r\n }\r\n\r\n /** Is the browser currently online? */\r\n isOnline(): boolean {\r\n return typeof navigator === \"undefined\" || navigator.onLine;\r\n }\r\n\r\n /** Number of entries waiting to be synced. */\r\n get pendingCount(): number {\r\n return this.queue.length;\r\n }\r\n\r\n /** Get a snapshot of the current queue. */\r\n getQueue(): QueueEntry[] {\r\n return [...this.queue];\r\n }\r\n\r\n /** Clear all pending entries without syncing. */\r\n clear(): void {\r\n this.queue = [];\r\n this.persistQueue();\r\n }\r\n\r\n /** Manually trigger sync (e.g. when you detect connectivity). */\r\n async sync(): Promise<SyncResult[]> {\r\n if (this.syncing || this.queue.length === 0 || !this.isOnline()) {\r\n return [];\r\n }\r\n this.syncing = true;\r\n\r\n const results: SyncResult[] = [];\r\n // Process in FIFO order\r\n while (this.queue.length > 0 && this.isOnline()) {\r\n const entry = this.queue[0];\r\n try {\r\n const res = await this.baseFetch(entry.url, {\r\n method: entry.method,\r\n headers: entry.headers,\r\n body: entry.body,\r\n });\r\n results.push({\r\n entry,\r\n success: res.ok,\r\n status: res.status,\r\n error: res.ok ? undefined : `HTTP ${res.status}`,\r\n });\r\n // Remove from queue regardless of status (don't retry 4xx)\r\n this.queue.shift();\r\n } catch (err) {\r\n results.push({\r\n entry,\r\n success: false,\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n // Network error — stop syncing, keep entry for later retry\r\n break;\r\n }\r\n }\r\n\r\n this.persistQueue();\r\n this.syncing = false;\r\n if (results.length > 0) {\r\n this.config.onSync?.(results);\r\n }\r\n return results;\r\n }\r\n\r\n /** Remove event listeners. */\r\n destroy(): void {\r\n if (typeof window !== \"undefined\") {\r\n window.removeEventListener(\"online\", this.boundOnline);\r\n window.removeEventListener(\"offline\", this.boundOffline);\r\n }\r\n }\r\n\r\n private persistQueue(): void {\r\n try {\r\n if (typeof localStorage !== \"undefined\") {\r\n localStorage.setItem(\r\n `${this.storageKey}queue`,\r\n JSON.stringify(this.queue),\r\n );\r\n }\r\n } catch {\r\n // Storage full or unavailable — silently continue\r\n }\r\n }\r\n\r\n private restoreQueue(): void {\r\n try {\r\n if (typeof localStorage !== \"undefined\") {\r\n const raw = localStorage.getItem(`${this.storageKey}queue`);\r\n if (raw) {\r\n this.queue = JSON.parse(raw);\r\n this.nextId = this.queue.reduce(\r\n (max, e) => Math.max(max, parseInt(e.id.replace(\"q_\", \"\"), 10) || 0),\r\n 0,\r\n ) + 1;\r\n }\r\n }\r\n } catch {\r\n this.queue = [];\r\n }\r\n }\r\n}\r\n","/**\r\n * 5.1.1 — Audio control registrations for NiceViewBuilder.\r\n * Controls: player, waveform, piano-roll, karaoke\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const audioControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceAudioPlayer\",\r\n label: \"Audio Player\",\r\n category: \"Audio\",\r\n icon: \"music\",\r\n defaultProps: { src: \"\", autoplay: false, showWaveform: true },\r\n propDescriptors: [\r\n { name: \"src\", type: \"string\", label: \"Audio source URL\" },\r\n { name: \"autoplay\", type: \"boolean\", label: \"Auto-play\", defaultValue: false },\r\n { name: \"loop\", type: \"boolean\", label: \"Loop\", defaultValue: false },\r\n { name: \"showWaveform\", type: \"boolean\", label: \"Show waveform\", defaultValue: true },\r\n { name: \"showPlaylist\", type: \"boolean\", label: \"Show playlist\", defaultValue: false },\r\n { name: \"visualizerMode\", type: \"select\", label: \"Visualizer mode\", options: [\r\n { label: \"None\", value: \"none\" },\r\n { label: \"Bars\", value: \"bars\" },\r\n { label: \"Wave\", value: \"wave\" },\r\n { label: \"Circle\", value: \"circle\" },\r\n ], defaultValue: \"bars\" },\r\n { name: \"height\", type: \"size\", label: \"Height\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceWaveform\",\r\n label: \"Waveform\",\r\n category: \"Audio\",\r\n icon: \"waveform\",\r\n defaultProps: { peaks: [], color: \"#4a90d9\" },\r\n propDescriptors: [\r\n { name: \"peaks\", type: \"json\", label: \"Peak data (JSON array)\" },\r\n { name: \"color\", type: \"color\", label: \"Waveform color\", defaultValue: \"#4a90d9\" },\r\n { name: \"progressColor\", type: \"color\", label: \"Progress color\", defaultValue: \"#1b5e20\" },\r\n { name: \"barWidth\", type: \"number\", label: \"Bar width (px)\", min: 1, max: 10, step: 1, defaultValue: 2 },\r\n { name: \"height\", type: \"size\", label: \"Height\", group: \"Layout\" },\r\n { name: \"interactive\", type: \"boolean\", label: \"Allow seeking\", defaultValue: true },\r\n ],\r\n },\r\n {\r\n type: \"NicePianoRoll\",\r\n label: \"Piano Roll\",\r\n category: \"Audio\",\r\n icon: \"piano\",\r\n defaultProps: { notes: [], octaves: 4, startOctave: 3 },\r\n propDescriptors: [\r\n { name: \"notes\", type: \"json\", label: \"MIDI notes (JSON)\" },\r\n { name: \"octaves\", type: \"number\", label: \"Visible octaves\", min: 1, max: 8, defaultValue: 4 },\r\n { name: \"startOctave\", type: \"number\", label: \"Start octave\", min: 0, max: 8, defaultValue: 3 },\r\n { name: \"quantize\", type: \"select\", label: \"Quantize\", options: [\r\n { label: \"1/4\", value: \"4\" },\r\n { label: \"1/8\", value: \"8\" },\r\n { label: \"1/16\", value: \"16\" },\r\n { label: \"1/32\", value: \"32\" },\r\n ], defaultValue: \"16\" },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n { name: \"height\", type: \"size\", label: \"Height\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceKaraoke\",\r\n label: \"Karaoke\",\r\n category: \"Audio\",\r\n icon: \"microphone\",\r\n defaultProps: { lyrics: [], audioSrc: \"\" },\r\n propDescriptors: [\r\n { name: \"audioSrc\", type: \"string\", label: \"Audio source URL\" },\r\n { name: \"lyrics\", type: \"json\", label: \"Lyrics (JSON array of {time, text})\" },\r\n { name: \"showScore\", type: \"boolean\", label: \"Show score\", defaultValue: true },\r\n { name: \"pitchDetection\", type: \"boolean\", label: \"Pitch detection\", defaultValue: true },\r\n { name: \"highlightColor\", type: \"color\", label: \"Highlight color\", defaultValue: \"#ffd700\" },\r\n { name: \"fontSize\", type: \"size\", label: \"Font size\", group: \"Typography\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.2 — Graphic control registrations for NiceViewBuilder.\r\n * Controls: pixel-editor, vector-editor, photo-editor\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const graphicControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NicePixelEditor\",\r\n label: \"Pixel Editor\",\r\n category: \"Graphics\",\r\n icon: \"grid\",\r\n defaultProps: { width: 32, height: 32, palette: [] },\r\n propDescriptors: [\r\n { name: \"width\", type: \"number\", label: \"Canvas width (px)\", min: 1, max: 256, defaultValue: 32 },\r\n { name: \"height\", type: \"number\", label: \"Canvas height (px)\", min: 1, max: 256, defaultValue: 32 },\r\n { name: \"palette\", type: \"json\", label: \"Color palette (JSON array)\" },\r\n { name: \"gridVisible\", type: \"boolean\", label: \"Show grid\", defaultValue: true },\r\n { name: \"zoom\", type: \"number\", label: \"Zoom level\", min: 1, max: 32, step: 1, defaultValue: 8 },\r\n { name: \"layers\", type: \"boolean\", label: \"Enable layers\", defaultValue: true },\r\n { name: \"onion\", type: \"boolean\", label: \"Onion skin (animation)\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceVectorEditor\",\r\n label: \"Vector Editor\",\r\n category: \"Graphics\",\r\n icon: \"pen-tool\",\r\n defaultProps: { width: 800, height: 600 },\r\n propDescriptors: [\r\n { name: \"width\", type: \"number\", label: \"Canvas width\", min: 100, max: 4096, defaultValue: 800, group: \"Layout\" },\r\n { name: \"height\", type: \"number\", label: \"Canvas height\", min: 100, max: 4096, defaultValue: 600, group: \"Layout\" },\r\n { name: \"showRulers\", type: \"boolean\", label: \"Show rulers\", defaultValue: true },\r\n { name: \"snapToGrid\", type: \"boolean\", label: \"Snap to grid\", defaultValue: true },\r\n { name: \"gridSize\", type: \"number\", label: \"Grid size\", min: 1, max: 100, defaultValue: 10 },\r\n { name: \"fillColor\", type: \"color\", label: \"Default fill\", defaultValue: \"#ffffff\" },\r\n { name: \"strokeColor\", type: \"color\", label: \"Default stroke\", defaultValue: \"#000000\" },\r\n { name: \"strokeWidth\", type: \"number\", label: \"Stroke width\", min: 0, max: 20, step: 0.5, defaultValue: 1 },\r\n ],\r\n },\r\n {\r\n type: \"NicePhotoEditor\",\r\n label: \"Photo Editor\",\r\n category: \"Graphics\",\r\n icon: \"image\",\r\n defaultProps: { src: \"\" },\r\n propDescriptors: [\r\n { name: \"src\", type: \"string\", label: \"Image source URL\" },\r\n { name: \"crop\", type: \"boolean\", label: \"Allow crop\", defaultValue: true },\r\n { name: \"rotate\", type: \"boolean\", label: \"Allow rotate\", defaultValue: true },\r\n { name: \"filters\", type: \"boolean\", label: \"Show filters\", defaultValue: true },\r\n { name: \"adjustments\", type: \"boolean\", label: \"Show adjustments\", defaultValue: true },\r\n { name: \"text\", type: \"boolean\", label: \"Allow text overlay\", defaultValue: true },\r\n { name: \"outputFormat\", type: \"select\", label: \"Output format\", options: [\r\n { label: \"PNG\", value: \"png\" },\r\n { label: \"JPEG\", value: \"jpeg\" },\r\n { label: \"WebP\", value: \"webp\" },\r\n ], defaultValue: \"png\" },\r\n { name: \"maxWidth\", type: \"number\", label: \"Max output width\", min: 100, max: 8192, group: \"Output\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.3 — 3D control registrations for NiceViewBuilder.\r\n * Controls: model-editor, model-viewer\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const threeControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceModelEditor\",\r\n label: \"3D Model Editor\",\r\n category: \"3D\",\r\n icon: \"cube\",\r\n defaultProps: { modelUrl: \"\" },\r\n propDescriptors: [\r\n { name: \"modelUrl\", type: \"string\", label: \"Model URL (.glb / .gltf)\" },\r\n { name: \"environmentMap\", type: \"select\", label: \"Environment\", options: [\r\n { label: \"Studio\", value: \"studio\" },\r\n { label: \"Outdoor\", value: \"outdoor\" },\r\n { label: \"Warehouse\", value: \"warehouse\" },\r\n { label: \"None\", value: \"none\" },\r\n ], defaultValue: \"studio\" },\r\n { name: \"showGrid\", type: \"boolean\", label: \"Show grid\", defaultValue: true },\r\n { name: \"showAxes\", type: \"boolean\", label: \"Show axes\", defaultValue: true },\r\n { name: \"enablePhysics\", type: \"boolean\", label: \"Enable physics\", defaultValue: false },\r\n { name: \"wireframe\", type: \"boolean\", label: \"Wireframe mode\", defaultValue: false },\r\n { name: \"backgroundColor\", type: \"color\", label: \"Background\", defaultValue: \"#1a1a2e\" },\r\n { name: \"height\", type: \"size\", label: \"Viewport height\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceModelViewer\",\r\n label: \"3D Model Viewer\",\r\n category: \"3D\",\r\n icon: \"eye\",\r\n defaultProps: { modelUrl: \"\", autoRotate: true },\r\n propDescriptors: [\r\n { name: \"modelUrl\", type: \"string\", label: \"Model URL (.glb / .gltf)\" },\r\n { name: \"autoRotate\", type: \"boolean\", label: \"Auto-rotate\", defaultValue: true },\r\n { name: \"rotateSpeed\", type: \"number\", label: \"Rotate speed\", min: 0.1, max: 10, step: 0.1, defaultValue: 1 },\r\n { name: \"zoom\", type: \"boolean\", label: \"Allow zoom\", defaultValue: true },\r\n { name: \"pan\", type: \"boolean\", label: \"Allow pan\", defaultValue: true },\r\n { name: \"environmentMap\", type: \"select\", label: \"Environment\", options: [\r\n { label: \"Studio\", value: \"studio\" },\r\n { label: \"Outdoor\", value: \"outdoor\" },\r\n { label: \"Warehouse\", value: \"warehouse\" },\r\n { label: \"None\", value: \"none\" },\r\n ], defaultValue: \"studio\" },\r\n { name: \"backgroundColor\", type: \"color\", label: \"Background\", defaultValue: \"#1a1a2e\" },\r\n { name: \"showAnnotations\", type: \"boolean\", label: \"Show annotations\", defaultValue: false },\r\n { name: \"height\", type: \"size\", label: \"Viewport height\", group: \"Layout\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.4 — Gamification control registrations for NiceViewBuilder.\r\n * Controls: leaderboard, achievement, quest, xp-bar\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const gamificationControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceLeaderboard\",\r\n label: \"Leaderboard\",\r\n category: \"Gamification\",\r\n icon: \"trophy\",\r\n defaultProps: { dataSource: [], maxRows: 10 },\r\n propDescriptors: [\r\n { name: \"dataSource\", type: \"json\", label: \"Data source (JSON)\" },\r\n { name: \"maxRows\", type: \"number\", label: \"Max visible rows\", min: 3, max: 100, defaultValue: 10 },\r\n { name: \"showRank\", type: \"boolean\", label: \"Show rank column\", defaultValue: true },\r\n { name: \"showAvatar\", type: \"boolean\", label: \"Show avatar\", defaultValue: true },\r\n { name: \"highlightCurrentUser\", type: \"boolean\", label: \"Highlight current user\", defaultValue: true },\r\n { name: \"scoreField\", type: \"string\", label: \"Score field name\", defaultValue: \"score\" },\r\n { name: \"nameField\", type: \"string\", label: \"Name field name\", defaultValue: \"name\" },\r\n { name: \"variant\", type: \"variant\", label: \"Visual style\", defaultValue: \"default\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceAchievement\",\r\n label: \"Achievement Badge\",\r\n category: \"Gamification\",\r\n icon: \"award\",\r\n defaultProps: { title: \"Achievement\", unlocked: false },\r\n propDescriptors: [\r\n { name: \"title\", type: \"string\", label: \"Achievement title\", defaultValue: \"Achievement\" },\r\n { name: \"description\", type: \"string\", label: \"Description\" },\r\n { name: \"icon\", type: \"string\", label: \"Icon URL\" },\r\n { name: \"unlocked\", type: \"boolean\", label: \"Unlocked\", defaultValue: false },\r\n { name: \"progress\", type: \"number\", label: \"Progress (0-100)\", min: 0, max: 100, defaultValue: 0 },\r\n { name: \"rarity\", type: \"select\", label: \"Rarity\", options: [\r\n { label: \"Common\", value: \"common\" },\r\n { label: \"Rare\", value: \"rare\" },\r\n { label: \"Epic\", value: \"epic\" },\r\n { label: \"Legendary\", value: \"legendary\" },\r\n ], defaultValue: \"common\" },\r\n { name: \"size\", type: \"size\", label: \"Badge size\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceQuest\",\r\n label: \"Quest Tracker\",\r\n category: \"Gamification\",\r\n icon: \"map\",\r\n defaultProps: { title: \"Quest\", steps: [] },\r\n propDescriptors: [\r\n { name: \"title\", type: \"string\", label: \"Quest title\", defaultValue: \"Quest\" },\r\n { name: \"description\", type: \"string\", label: \"Quest description\" },\r\n { name: \"steps\", type: \"json\", label: \"Steps (JSON array of {title, completed})\" },\r\n { name: \"reward\", type: \"string\", label: \"Reward description\" },\r\n { name: \"rewardXP\", type: \"number\", label: \"Reward XP\", min: 0, defaultValue: 100 },\r\n { name: \"showProgress\", type: \"boolean\", label: \"Show progress bar\", defaultValue: true },\r\n { name: \"variant\", type: \"variant\", label: \"Visual style\", defaultValue: \"default\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceXPBar\",\r\n label: \"XP Progress Bar\",\r\n category: \"Gamification\",\r\n icon: \"zap\",\r\n defaultProps: { currentXP: 0, maxXP: 1000, level: 1 },\r\n propDescriptors: [\r\n { name: \"currentXP\", type: \"number\", label: \"Current XP\", min: 0, defaultValue: 0 },\r\n { name: \"maxXP\", type: \"number\", label: \"Max XP (next level)\", min: 1, defaultValue: 1000 },\r\n { name: \"level\", type: \"number\", label: \"Current level\", min: 1, defaultValue: 1 },\r\n { name: \"showLabel\", type: \"boolean\", label: \"Show XP label\", defaultValue: true },\r\n { name: \"showLevel\", type: \"boolean\", label: \"Show level badge\", defaultValue: true },\r\n { name: \"color\", type: \"color\", label: \"Bar color\", defaultValue: \"#4caf50\" },\r\n { name: \"animated\", type: \"boolean\", label: \"Animate changes\", defaultValue: true },\r\n { name: \"height\", type: \"size\", label: \"Bar height\", group: \"Layout\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.5 — Business control registrations for NiceViewBuilder.\r\n * Controls: address, phone, currency, vat, invoice-lines\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const businessControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceAddress\",\r\n label: \"Address Editor\",\r\n category: \"Business\",\r\n icon: \"map-pin\",\r\n defaultProps: { country: \"PL\" },\r\n propDescriptors: [\r\n { name: \"street\", type: \"string\", label: \"Street\", group: \"Address\" },\r\n { name: \"city\", type: \"string\", label: \"City\", group: \"Address\" },\r\n { name: \"postalCode\", type: \"string\", label: \"Postal code\", group: \"Address\" },\r\n { name: \"country\", type: \"select\", label: \"Country\", options: [\r\n { label: \"Poland\", value: \"PL\" },\r\n { label: \"Germany\", value: \"DE\" },\r\n { label: \"USA\", value: \"US\" },\r\n { label: \"UK\", value: \"GB\" },\r\n { label: \"France\", value: \"FR\" },\r\n ], defaultValue: \"PL\", group: \"Address\" },\r\n { name: \"showMap\", type: \"boolean\", label: \"Show map preview\", defaultValue: false },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NicePhone\",\r\n label: \"Phone Input\",\r\n category: \"Business\",\r\n icon: \"phone\",\r\n defaultProps: { countryCode: \"+48\" },\r\n propDescriptors: [\r\n { name: \"value\", type: \"string\", label: \"Phone number\" },\r\n { name: \"countryCode\", type: \"string\", label: \"Default country code\", defaultValue: \"+48\" },\r\n { name: \"showFlag\", type: \"boolean\", label: \"Show country flag\", defaultValue: true },\r\n { name: \"format\", type: \"select\", label: \"Format\", options: [\r\n { label: \"International\", value: \"international\" },\r\n { label: \"National\", value: \"national\" },\r\n { label: \"E.164\", value: \"e164\" },\r\n ], defaultValue: \"international\" },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceCurrency\",\r\n label: \"Currency Input\",\r\n category: \"Business\",\r\n icon: \"dollar-sign\",\r\n defaultProps: { currency: \"PLN\", decimals: 2 },\r\n propDescriptors: [\r\n { name: \"value\", type: \"number\", label: \"Amount\", defaultValue: 0 },\r\n { name: \"currency\", type: \"select\", label: \"Currency\", options: [\r\n { label: \"PLN\", value: \"PLN\" },\r\n { label: \"EUR\", value: \"EUR\" },\r\n { label: \"USD\", value: \"USD\" },\r\n { label: \"GBP\", value: \"GBP\" },\r\n ], defaultValue: \"PLN\" },\r\n { name: \"decimals\", type: \"number\", label: \"Decimal places\", min: 0, max: 4, defaultValue: 2 },\r\n { name: \"showCurrencySymbol\", type: \"boolean\", label: \"Show symbol\", defaultValue: true },\r\n { name: \"thousandSeparator\", type: \"boolean\", label: \"Thousand separator\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceVAT\",\r\n label: \"VAT Input\",\r\n category: \"Business\",\r\n icon: \"percent\",\r\n defaultProps: { rate: 23, country: \"PL\" },\r\n propDescriptors: [\r\n { name: \"netValue\", type: \"number\", label: \"Net value\", defaultValue: 0, group: \"Values\" },\r\n { name: \"rate\", type: \"number\", label: \"VAT rate (%)\", min: 0, max: 100, defaultValue: 23, group: \"Values\" },\r\n { name: \"country\", type: \"select\", label: \"Country\", options: [\r\n { label: \"Poland\", value: \"PL\" },\r\n { label: \"Germany\", value: \"DE\" },\r\n { label: \"France\", value: \"FR\" },\r\n ], defaultValue: \"PL\" },\r\n { name: \"showGross\", type: \"boolean\", label: \"Show gross value\", defaultValue: true },\r\n { name: \"showVatAmount\", type: \"boolean\", label: \"Show VAT amount\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceInvoiceLines\",\r\n label: \"Invoice Lines\",\r\n category: \"Business\",\r\n icon: \"file-text\",\r\n defaultProps: { lines: [], currency: \"PLN\" },\r\n propDescriptors: [\r\n { name: \"lines\", type: \"json\", label: \"Invoice lines (JSON)\" },\r\n { name: \"currency\", type: \"select\", label: \"Currency\", options: [\r\n { label: \"PLN\", value: \"PLN\" },\r\n { label: \"EUR\", value: \"EUR\" },\r\n { label: \"USD\", value: \"USD\" },\r\n ], defaultValue: \"PLN\" },\r\n { name: \"showVAT\", type: \"boolean\", label: \"Show VAT column\", defaultValue: true },\r\n { name: \"showDiscount\", type: \"boolean\", label: \"Show discount column\", defaultValue: false },\r\n { name: \"showTotal\", type: \"boolean\", label: \"Show totals row\", defaultValue: true },\r\n { name: \"editable\", type: \"boolean\", label: \"Editable\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.6 — Auth control registrations for NiceViewBuilder.\r\n * Controls: login-form, captcha, 2fa-setup\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const authControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceLoginForm\",\r\n label: \"Login Form\",\r\n category: \"Auth\",\r\n icon: \"log-in\",\r\n defaultProps: { showRemember: true, showForgotPassword: true },\r\n propDescriptors: [\r\n { name: \"title\", type: \"string\", label: \"Form title\", defaultValue: \"Sign In\" },\r\n { name: \"showRemember\", type: \"boolean\", label: \"Show 'Remember me'\", defaultValue: true },\r\n { name: \"showForgotPassword\", type: \"boolean\", label: \"Show 'Forgot password'\", defaultValue: true },\r\n { name: \"showRegisterLink\", type: \"boolean\", label: \"Show register link\", defaultValue: false },\r\n { name: \"showLogo\", type: \"boolean\", label: \"Show logo\", defaultValue: true },\r\n { name: \"logoUrl\", type: \"string\", label: \"Logo URL\", group: \"Branding\" },\r\n { name: \"primaryColor\", type: \"color\", label: \"Primary color\", defaultValue: \"#1976d2\", group: \"Branding\" },\r\n { name: \"variant\", type: \"variant\", label: \"Visual style\", defaultValue: \"card\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceCaptcha\",\r\n label: \"Captcha\",\r\n category: \"Auth\",\r\n icon: \"shield\",\r\n defaultProps: { provider: \"recaptcha\", size: \"normal\" },\r\n propDescriptors: [\r\n { name: \"provider\", type: \"select\", label: \"Provider\", options: [\r\n { label: \"reCAPTCHA v2\", value: \"recaptcha\" },\r\n { label: \"reCAPTCHA v3\", value: \"recaptcha-v3\" },\r\n { label: \"hCaptcha\", value: \"hcaptcha\" },\r\n { label: \"Turnstile\", value: \"turnstile\" },\r\n ], defaultValue: \"recaptcha\" },\r\n { name: \"siteKey\", type: \"string\", label: \"Site key\" },\r\n { name: \"size\", type: \"select\", label: \"Size\", options: [\r\n { label: \"Normal\", value: \"normal\" },\r\n { label: \"Compact\", value: \"compact\" },\r\n { label: \"Invisible\", value: \"invisible\" },\r\n ], defaultValue: \"normal\" },\r\n { name: \"theme\", type: \"select\", label: \"Theme\", options: [\r\n { label: \"Light\", value: \"light\" },\r\n { label: \"Dark\", value: \"dark\" },\r\n ], defaultValue: \"light\" },\r\n ],\r\n },\r\n {\r\n type: \"Nice2FASetup\",\r\n label: \"2FA Setup\",\r\n category: \"Auth\",\r\n icon: \"key\",\r\n defaultProps: { method: \"totp\" },\r\n propDescriptors: [\r\n { name: \"method\", type: \"select\", label: \"2FA method\", options: [\r\n { label: \"TOTP (Authenticator)\", value: \"totp\" },\r\n { label: \"SMS\", value: \"sms\" },\r\n { label: \"Email\", value: \"email\" },\r\n ], defaultValue: \"totp\" },\r\n { name: \"issuer\", type: \"string\", label: \"Issuer name\", defaultValue: \"OmniVerk\" },\r\n { name: \"showBackupCodes\", type: \"boolean\", label: \"Show backup codes\", defaultValue: true },\r\n { name: \"showQR\", type: \"boolean\", label: \"Show QR code\", defaultValue: true },\r\n { name: \"codeLength\", type: \"number\", label: \"Code length\", min: 4, max: 8, defaultValue: 6 },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.7 — Social control registrations for NiceViewBuilder.\r\n * Controls: comments, ratings, wiki\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const socialControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceComments\",\r\n label: \"Comments\",\r\n category: \"Social\",\r\n icon: \"message-circle\",\r\n defaultProps: { threadId: \"\", allowReplies: true },\r\n propDescriptors: [\r\n { name: \"threadId\", type: \"string\", label: \"Thread ID / entity ID\" },\r\n { name: \"allowReplies\", type: \"boolean\", label: \"Allow replies (nested)\", defaultValue: true },\r\n { name: \"maxDepth\", type: \"number\", label: \"Max nesting depth\", min: 1, max: 10, defaultValue: 3 },\r\n { name: \"sortOrder\", type: \"select\", label: \"Default sort\", options: [\r\n { label: \"Newest first\", value: \"newest\" },\r\n { label: \"Oldest first\", value: \"oldest\" },\r\n { label: \"Most liked\", value: \"popular\" },\r\n ], defaultValue: \"newest\" },\r\n { name: \"allowReactions\", type: \"boolean\", label: \"Allow reactions\", defaultValue: true },\r\n { name: \"showAvatar\", type: \"boolean\", label: \"Show avatars\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceRatings\",\r\n label: \"Ratings\",\r\n category: \"Social\",\r\n icon: \"star\",\r\n defaultProps: { maxStars: 5, allowHalf: true },\r\n propDescriptors: [\r\n { name: \"value\", type: \"number\", label: \"Current rating\", min: 0, max: 10, step: 0.5, defaultValue: 0 },\r\n { name: \"maxStars\", type: \"number\", label: \"Maximum stars\", min: 3, max: 10, defaultValue: 5 },\r\n { name: \"allowHalf\", type: \"boolean\", label: \"Allow half stars\", defaultValue: true },\r\n { name: \"showCount\", type: \"boolean\", label: \"Show vote count\", defaultValue: true },\r\n { name: \"showAverage\", type: \"boolean\", label: \"Show average\", defaultValue: true },\r\n { name: \"color\", type: \"color\", label: \"Star color\", defaultValue: \"#ffc107\" },\r\n { name: \"size\", type: \"size\", label: \"Star size\", group: \"Layout\" },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceWiki\",\r\n label: \"Wiki Editor\",\r\n category: \"Social\",\r\n icon: \"book\",\r\n defaultProps: { content: \"\", showTOC: true },\r\n propDescriptors: [\r\n { name: \"content\", type: \"string\", label: \"Initial content (Markdown)\" },\r\n { name: \"showTOC\", type: \"boolean\", label: \"Show table of contents\", defaultValue: true },\r\n { name: \"showHistory\", type: \"boolean\", label: \"Show version history\", defaultValue: true },\r\n { name: \"allowEdit\", type: \"boolean\", label: \"Allow editing\", defaultValue: true },\r\n { name: \"showToolbar\", type: \"boolean\", label: \"Show Markdown toolbar\", defaultValue: true },\r\n { name: \"previewMode\", type: \"select\", label: \"Preview mode\", options: [\r\n { label: \"Side by side\", value: \"split\" },\r\n { label: \"Tab switch\", value: \"tabs\" },\r\n { label: \"Live preview\", value: \"live\" },\r\n ], defaultValue: \"split\" },\r\n { name: \"maxLength\", type: \"number\", label: \"Max content length\", min: 0, defaultValue: 0 },\r\n ],\r\n },\r\n];\r\n","/**\r\n * Combined control registry — all @nice2dev/* control registrations in one place.\r\n * Use `allControlRegistries` to register everything with NiceViewBuilder.\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\nimport { audioControlRegistry } from \"./audioControls\";\r\nimport { graphicControlRegistry } from \"./graphicControls\";\r\nimport { threeControlRegistry } from \"./threeControls\";\r\nimport { gamificationControlRegistry } from \"./gamificationControls\";\r\nimport { businessControlRegistry } from \"./businessControls\";\r\nimport { authControlRegistry } from \"./authControls\";\r\nimport { socialControlRegistry } from \"./socialControls\";\r\n\r\nexport { audioControlRegistry } from \"./audioControls\";\r\nexport { graphicControlRegistry } from \"./graphicControls\";\r\nexport { threeControlRegistry } from \"./threeControls\";\r\nexport { gamificationControlRegistry } from \"./gamificationControls\";\r\nexport { businessControlRegistry } from \"./businessControls\";\r\nexport { authControlRegistry } from \"./authControls\";\r\nexport { socialControlRegistry } from \"./socialControls\";\r\n\r\n/** All control registries combined into a single flat array. */\r\nexport const allControlRegistries: ControlRegistryEntry[] = [\r\n ...audioControlRegistry,\r\n ...graphicControlRegistry,\r\n ...threeControlRegistry,\r\n ...gamificationControlRegistry,\r\n ...businessControlRegistry,\r\n ...authControlRegistry,\r\n ...socialControlRegistry,\r\n];\r\n","/**\r\n * useLoanApi — React hook for LoanManagement API integration.\r\n *\r\n * Provides full integration between NiceLoanCalculator and OmniVerk backend:\r\n * - Fetch loan products/offers from banks\r\n * - Calculate amortization schedules\r\n * - Compare multiple loan scenarios\r\n * - Submit loan applications\r\n * - Track application status\r\n * - Save/load calculations\r\n *\r\n * @example\r\n * ```tsx\r\n * const {\r\n * offers,\r\n * loading,\r\n * fetchOffers,\r\n * calculateAmortization,\r\n * compareLoanScenarios,\r\n * submitApplication,\r\n * } = useLoanApi({\r\n * baseUrl: '/api/loans',\r\n * currency: 'PLN',\r\n * });\r\n *\r\n * // Use with NiceLoanCalculator\r\n * <NiceLoanCalculator\r\n * loanOffers={offers}\r\n * onCalculate={calculateAmortization}\r\n * onApply={submitApplication}\r\n * />\r\n * ```\r\n */\r\n\r\nimport { useState, useCallback, useRef, useEffect } from 'react';\r\nimport type {\r\n LoanOffer,\r\n LoanType,\r\n AmortizationConfig,\r\n AmortizationResult,\r\n CreditAnalysisResult,\r\n LoanApplicationState,\r\n ActiveLoan,\r\n RefinancingAnalysis,\r\n ConsolidationSimulation,\r\n DebtItem,\r\n} from './loans';\r\n\r\n/* ================================================================\r\n API TYPES\r\n ================================================================ */\r\n\r\n/** API endpoint configuration. */\r\nexport interface LoanApiConfig {\r\n /** Base URL for loan API endpoints. */\r\n baseUrl: string;\r\n /** Default currency. */\r\n currency?: string;\r\n /** Request timeout in ms. */\r\n timeout?: number;\r\n /** Custom fetch function (for testing or middleware). */\r\n fetcher?: <T>(url: string, options?: RequestInit) => Promise<T>;\r\n /** Auth token getter. */\r\n getAuthToken?: () => string | null;\r\n /** Error handler. */\r\n onError?: (error: LoanApiError) => void;\r\n}\r\n\r\n/** API error with details. */\r\nexport interface LoanApiError {\r\n code: string;\r\n message: string;\r\n details?: Record<string, unknown>;\r\n endpoint: string;\r\n statusCode?: number;\r\n}\r\n\r\n/** Loan search filters. */\r\nexport interface LoanOfferFilters {\r\n loanType?: LoanType;\r\n minAmount?: number;\r\n maxAmount?: number;\r\n minTerm?: number;\r\n maxTerm?: number;\r\n maxApr?: number;\r\n banks?: string[];\r\n features?: string[];\r\n currency?: string;\r\n}\r\n\r\n/** Amortization calculation request. */\r\nexport interface AmortizationRequest {\r\n principal: number;\r\n annualRate: number;\r\n termMonths: number;\r\n type?: 'annuity' | 'linear' | 'balloon';\r\n startDate?: string;\r\n extraPayments?: Array<{\r\n month: number;\r\n amount: number;\r\n }>;\r\n gracePeriodMonths?: number;\r\n currency?: string;\r\n}\r\n\r\n/** Loan scenario for comparison. */\r\nexport interface LoanScenarioRequest {\r\n name: string;\r\n offerId?: string;\r\n principal: number;\r\n annualRate: number;\r\n termMonths: number;\r\n downPayment?: number;\r\n extraMonthlyPayment?: number;\r\n oneTimePayments?: Array<{\r\n month: number;\r\n amount: number;\r\n }>;\r\n}\r\n\r\n/** Loan comparison result. */\r\nexport interface LoanComparisonResult {\r\n scenarios: Array<{\r\n name: string;\r\n offer?: LoanOffer;\r\n amortization: AmortizationResult;\r\n totalCost: number;\r\n totalInterest: number;\r\n effectiveRate: number;\r\n monthlyPayment: number;\r\n }>;\r\n recommendedIndex: number;\r\n savingsVsWorst: number;\r\n comparisonDate: string;\r\n}\r\n\r\n/** Loan application submission. */\r\nexport interface LoanApplicationRequest {\r\n offerId: string;\r\n principal: number;\r\n termMonths: number;\r\n purpose: string;\r\n applicant: {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone?: string;\r\n nationalId?: string;\r\n };\r\n employment?: {\r\n type: 'employed' | 'self-employed' | 'retired' | 'other';\r\n employer?: string;\r\n monthlyIncome: number;\r\n yearsEmployed?: number;\r\n };\r\n documents?: Array<{\r\n type: string;\r\n fileId: string;\r\n }>;\r\n consent: {\r\n creditCheck: boolean;\r\n marketing?: boolean;\r\n dataProcessing: boolean;\r\n };\r\n}\r\n\r\n/** Saved calculation for later retrieval. */\r\nexport interface SavedCalculation {\r\n id: string;\r\n name: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n config: AmortizationConfig;\r\n result: AmortizationResult;\r\n scenarios?: LoanScenarioRequest[];\r\n notes?: string;\r\n}\r\n\r\n/* ================================================================\r\n HOOK RESULT TYPES\r\n ================================================================ */\r\n\r\n/** Loading state for multiple operations. */\r\nexport interface LoanApiLoadingState {\r\n offers: boolean;\r\n calculate: boolean;\r\n compare: boolean;\r\n apply: boolean;\r\n creditCheck: boolean;\r\n refinancing: boolean;\r\n consolidation: boolean;\r\n savedCalculations: boolean;\r\n}\r\n\r\n/** Hook return type. */\r\nexport interface UseLoanApiResult {\r\n /* State */\r\n offers: LoanOffer[];\r\n selectedOffer: LoanOffer | null;\r\n latestCalculation: AmortizationResult | null;\r\n latestComparison: LoanComparisonResult | null;\r\n applicationState: LoanApplicationState | null;\r\n creditAnalysis: CreditAnalysisResult | null;\r\n savedCalculations: SavedCalculation[];\r\n activeLoans: ActiveLoan[];\r\n loading: LoanApiLoadingState;\r\n error: LoanApiError | null;\r\n\r\n /* Offer operations */\r\n fetchOffers: (filters?: LoanOfferFilters) => Promise<LoanOffer[]>;\r\n selectOffer: (offer: LoanOffer | null) => void;\r\n getOfferDetails: (offerId: string) => Promise<LoanOffer>;\r\n\r\n /* Calculation operations */\r\n calculateAmortization: (request: AmortizationRequest) => Promise<AmortizationResult>;\r\n compareLoanScenarios: (scenarios: LoanScenarioRequest[]) => Promise<LoanComparisonResult>;\r\n\r\n /* Credit analysis */\r\n runCreditAnalysis: (\r\n monthlyIncome: number,\r\n monthlyDebts: number,\r\n requestedAmount: number,\r\n ) => Promise<CreditAnalysisResult>;\r\n\r\n /* Application operations */\r\n submitApplication: (application: LoanApplicationRequest) => Promise<LoanApplicationState>;\r\n getApplicationStatus: (applicationId: string) => Promise<LoanApplicationState>;\r\n cancelApplication: (applicationId: string) => Promise<void>;\r\n\r\n /* Saved calculations */\r\n saveCalculation: (\r\n name: string,\r\n config: AmortizationConfig,\r\n result: AmortizationResult,\r\n notes?: string,\r\n ) => Promise<SavedCalculation>;\r\n loadCalculation: (id: string) => Promise<SavedCalculation>;\r\n deleteSavedCalculation: (id: string) => Promise<void>;\r\n fetchSavedCalculations: () => Promise<SavedCalculation[]>;\r\n\r\n /* Refinancing & Consolidation */\r\n analyzeRefinancing: (\r\n currentLoanId: string,\r\n newOfferIds?: string[],\r\n ) => Promise<RefinancingAnalysis[]>;\r\n simulateConsolidation: (\r\n debts: DebtItem[],\r\n targetRate?: number,\r\n ) => Promise<ConsolidationSimulation>;\r\n\r\n /* Active loans */\r\n fetchActiveLoans: () => Promise<ActiveLoan[]>;\r\n\r\n /* Utilities */\r\n clearError: () => void;\r\n reset: () => void;\r\n}\r\n\r\n/* ================================================================\r\n DEFAULT FETCHER\r\n ================================================================ */\r\n\r\nasync function defaultFetcher<T>(url: string, options?: RequestInit): Promise<T> {\r\n const response = await fetch(url, {\r\n ...options,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...options?.headers,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n const errorBody = await response.text();\r\n let parsed: { code?: string; message?: string } = {};\r\n try {\r\n parsed = JSON.parse(errorBody);\r\n } catch {\r\n // Not JSON error response\r\n }\r\n throw {\r\n code: parsed.code || `HTTP_${response.status}`,\r\n message: parsed.message || response.statusText,\r\n statusCode: response.status,\r\n };\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useLoanApi(config: LoanApiConfig): UseLoanApiResult {\r\n const {\r\n baseUrl,\r\n currency = 'PLN',\r\n timeout = 30000,\r\n fetcher = defaultFetcher,\r\n getAuthToken,\r\n onError,\r\n } = config;\r\n\r\n /* ── State ─────────────────────────────────────────────────────── */\r\n const [offers, setOffers] = useState<LoanOffer[]>([]);\r\n const [selectedOffer, setSelectedOffer] = useState<LoanOffer | null>(null);\r\n const [latestCalculation, setLatestCalculation] = useState<AmortizationResult | null>(null);\r\n const [latestComparison, setLatestComparison] = useState<LoanComparisonResult | null>(null);\r\n const [applicationState, setApplicationState] = useState<LoanApplicationState | null>(null);\r\n const [creditAnalysis, setCreditAnalysis] = useState<CreditAnalysisResult | null>(null);\r\n const [savedCalculations, setSavedCalculations] = useState<SavedCalculation[]>([]);\r\n const [activeLoans, setActiveLoans] = useState<ActiveLoan[]>([]);\r\n const [error, setError] = useState<LoanApiError | null>(null);\r\n const [loading, setLoading] = useState<LoanApiLoadingState>({\r\n offers: false,\r\n calculate: false,\r\n compare: false,\r\n apply: false,\r\n creditCheck: false,\r\n refinancing: false,\r\n consolidation: false,\r\n savedCalculations: false,\r\n });\r\n\r\n const abortControllers = useRef<Map<string, AbortController>>(new Map());\r\n\r\n /* ── Helpers ───────────────────────────────────────────────────── */\r\n const getHeaders = useCallback((): Record<string, string> => {\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n const token = getAuthToken?.();\r\n if (token) {\r\n headers['Authorization'] = `Bearer ${token}`;\r\n }\r\n return headers;\r\n }, [getAuthToken]);\r\n\r\n const makeRequest = useCallback(\r\n async <T>(\r\n endpoint: string,\r\n options: RequestInit = {},\r\n loadingKey: keyof LoanApiLoadingState,\r\n ): Promise<T> => {\r\n // Cancel previous request to same endpoint\r\n const existingController = abortControllers.current.get(endpoint);\r\n if (existingController) {\r\n existingController.abort();\r\n }\r\n\r\n const controller = new AbortController();\r\n abortControllers.current.set(endpoint, controller);\r\n\r\n // Timeout\r\n const timeoutId = setTimeout(() => controller.abort(), timeout);\r\n\r\n setLoading((prev) => ({ ...prev, [loadingKey]: true }));\r\n setError(null);\r\n\r\n try {\r\n const url = `${baseUrl}${endpoint}`;\r\n const result = await fetcher<T>(url, {\r\n ...options,\r\n headers: {\r\n ...getHeaders(),\r\n ...(options.headers as Record<string, string> | undefined),\r\n },\r\n signal: controller.signal,\r\n });\r\n\r\n return result;\r\n } catch (err) {\r\n const apiError: LoanApiError = {\r\n code: (err as LoanApiError).code || 'UNKNOWN_ERROR',\r\n message: (err as Error).message || 'An unknown error occurred',\r\n details: (err as LoanApiError).details,\r\n endpoint,\r\n statusCode: (err as LoanApiError).statusCode,\r\n };\r\n\r\n if ((err as Error).name !== 'AbortError') {\r\n setError(apiError);\r\n onError?.(apiError);\r\n }\r\n\r\n throw apiError;\r\n } finally {\r\n clearTimeout(timeoutId);\r\n abortControllers.current.delete(endpoint);\r\n setLoading((prev) => ({ ...prev, [loadingKey]: false }));\r\n }\r\n },\r\n [baseUrl, timeout, fetcher, getHeaders, onError],\r\n );\r\n\r\n /* ── Offer Operations ──────────────────────────────────────────── */\r\n const fetchOffers = useCallback(\r\n async (filters?: LoanOfferFilters): Promise<LoanOffer[]> => {\r\n const params = new URLSearchParams();\r\n if (filters?.loanType) params.set('loanType', filters.loanType);\r\n if (filters?.minAmount) params.set('minAmount', String(filters.minAmount));\r\n if (filters?.maxAmount) params.set('maxAmount', String(filters.maxAmount));\r\n if (filters?.minTerm) params.set('minTerm', String(filters.minTerm));\r\n if (filters?.maxTerm) params.set('maxTerm', String(filters.maxTerm));\r\n if (filters?.maxApr) params.set('maxApr', String(filters.maxApr));\r\n if (filters?.banks?.length) params.set('banks', filters.banks.join(','));\r\n if (filters?.features?.length) params.set('features', filters.features.join(','));\r\n params.set('currency', filters?.currency || currency);\r\n\r\n const queryString = params.toString();\r\n const endpoint = `/offers${queryString ? `?${queryString}` : ''}`;\r\n\r\n const result = await makeRequest<LoanOffer[]>(endpoint, { method: 'GET' }, 'offers');\r\n setOffers(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n const selectOffer = useCallback((offer: LoanOffer | null) => {\r\n setSelectedOffer(offer);\r\n }, []);\r\n\r\n const getOfferDetails = useCallback(\r\n async (offerId: string): Promise<LoanOffer> => {\r\n return makeRequest<LoanOffer>(`/offers/${offerId}`, { method: 'GET' }, 'offers');\r\n },\r\n [makeRequest],\r\n );\r\n\r\n /* ── Calculation Operations ────────────────────────────────────── */\r\n const calculateAmortization = useCallback(\r\n async (request: AmortizationRequest): Promise<AmortizationResult> => {\r\n const result = await makeRequest<AmortizationResult>(\r\n '/calculate/amortization',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n ...request,\r\n currency: request.currency || currency,\r\n }),\r\n },\r\n 'calculate',\r\n );\r\n setLatestCalculation(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n const compareLoanScenarios = useCallback(\r\n async (scenarios: LoanScenarioRequest[]): Promise<LoanComparisonResult> => {\r\n const result = await makeRequest<LoanComparisonResult>(\r\n '/calculate/compare',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ scenarios, currency }),\r\n },\r\n 'compare',\r\n );\r\n setLatestComparison(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n /* ── Credit Analysis ───────────────────────────────────────────── */\r\n const runCreditAnalysis = useCallback(\r\n async (\r\n monthlyIncome: number,\r\n monthlyDebts: number,\r\n requestedAmount: number,\r\n ): Promise<CreditAnalysisResult> => {\r\n const result = await makeRequest<CreditAnalysisResult>(\r\n '/credit/analyze',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n monthlyIncome,\r\n monthlyDebts,\r\n requestedAmount,\r\n currency,\r\n }),\r\n },\r\n 'creditCheck',\r\n );\r\n setCreditAnalysis(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n /* ── Application Operations ────────────────────────────────────── */\r\n const submitApplication = useCallback(\r\n async (application: LoanApplicationRequest): Promise<LoanApplicationState> => {\r\n const result = await makeRequest<LoanApplicationState>(\r\n '/applications',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(application),\r\n },\r\n 'apply',\r\n );\r\n setApplicationState(result);\r\n return result;\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const getApplicationStatus = useCallback(\r\n async (applicationId: string): Promise<LoanApplicationState> => {\r\n const result = await makeRequest<LoanApplicationState>(\r\n `/applications/${applicationId}`,\r\n { method: 'GET' },\r\n 'apply',\r\n );\r\n setApplicationState(result);\r\n return result;\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const cancelApplication = useCallback(\r\n async (applicationId: string): Promise<void> => {\r\n await makeRequest<void>(`/applications/${applicationId}/cancel`, { method: 'POST' }, 'apply');\r\n setApplicationState(null);\r\n },\r\n [makeRequest],\r\n );\r\n\r\n /* ── Saved Calculations ────────────────────────────────────────── */\r\n const saveCalculation = useCallback(\r\n async (\r\n name: string,\r\n savedConfig: AmortizationConfig,\r\n result: AmortizationResult,\r\n notes?: string,\r\n ): Promise<SavedCalculation> => {\r\n const savedCalc = await makeRequest<SavedCalculation>(\r\n '/calculations',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ name, config: savedConfig, result, notes }),\r\n },\r\n 'savedCalculations',\r\n );\r\n setSavedCalculations((prev) => [...prev, savedCalc]);\r\n return savedCalc;\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const loadCalculation = useCallback(\r\n async (id: string): Promise<SavedCalculation> => {\r\n return makeRequest<SavedCalculation>(\r\n `/calculations/${id}`,\r\n { method: 'GET' },\r\n 'savedCalculations',\r\n );\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const deleteSavedCalculation = useCallback(\r\n async (id: string): Promise<void> => {\r\n await makeRequest<void>(`/calculations/${id}`, { method: 'DELETE' }, 'savedCalculations');\r\n setSavedCalculations((prev) => prev.filter((c) => c.id !== id));\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const fetchSavedCalculations = useCallback(async (): Promise<SavedCalculation[]> => {\r\n const result = await makeRequest<SavedCalculation[]>(\r\n '/calculations',\r\n { method: 'GET' },\r\n 'savedCalculations',\r\n );\r\n setSavedCalculations(result);\r\n return result;\r\n }, [makeRequest]);\r\n\r\n /* ── Refinancing & Consolidation ───────────────────────────────── */\r\n const analyzeRefinancing = useCallback(\r\n async (currentLoanId: string, newOfferIds?: string[]): Promise<RefinancingAnalysis[]> => {\r\n return makeRequest<RefinancingAnalysis[]>(\r\n '/refinancing/analyze',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ currentLoanId, newOfferIds }),\r\n },\r\n 'refinancing',\r\n );\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const simulateConsolidation = useCallback(\r\n async (debts: DebtItem[], targetRate?: number): Promise<ConsolidationSimulation> => {\r\n return makeRequest<ConsolidationSimulation>(\r\n '/consolidation/simulate',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ debts, targetRate, currency }),\r\n },\r\n 'consolidation',\r\n );\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n /* ── Active Loans ──────────────────────────────────────────────── */\r\n const fetchActiveLoans = useCallback(async (): Promise<ActiveLoan[]> => {\r\n const result = await makeRequest<ActiveLoan[]>('/active', { method: 'GET' }, 'offers');\r\n setActiveLoans(result);\r\n return result;\r\n }, [makeRequest]);\r\n\r\n /* ── Utilities ─────────────────────────────────────────────────── */\r\n const clearError = useCallback(() => {\r\n setError(null);\r\n }, []);\r\n\r\n const reset = useCallback(() => {\r\n setOffers([]);\r\n setSelectedOffer(null);\r\n setLatestCalculation(null);\r\n setLatestComparison(null);\r\n setApplicationState(null);\r\n setCreditAnalysis(null);\r\n setSavedCalculations([]);\r\n setActiveLoans([]);\r\n setError(null);\r\n setLoading({\r\n offers: false,\r\n calculate: false,\r\n compare: false,\r\n apply: false,\r\n creditCheck: false,\r\n refinancing: false,\r\n consolidation: false,\r\n savedCalculations: false,\r\n });\r\n }, []);\r\n\r\n /* ── Cleanup ───────────────────────────────────────────────────── */\r\n useEffect(() => {\r\n return () => {\r\n abortControllers.current.forEach((controller) => controller.abort());\r\n abortControllers.current.clear();\r\n };\r\n }, []);\r\n\r\n return {\r\n // State\r\n offers,\r\n selectedOffer,\r\n latestCalculation,\r\n latestComparison,\r\n applicationState,\r\n creditAnalysis,\r\n savedCalculations,\r\n activeLoans,\r\n loading,\r\n error,\r\n\r\n // Offer operations\r\n fetchOffers,\r\n selectOffer,\r\n getOfferDetails,\r\n\r\n // Calculation operations\r\n calculateAmortization,\r\n compareLoanScenarios,\r\n\r\n // Credit analysis\r\n runCreditAnalysis,\r\n\r\n // Application operations\r\n submitApplication,\r\n getApplicationStatus,\r\n cancelApplication,\r\n\r\n // Saved calculations\r\n saveCalculation,\r\n loadCalculation,\r\n deleteSavedCalculation,\r\n fetchSavedCalculations,\r\n\r\n // Refinancing & Consolidation\r\n analyzeRefinancing,\r\n simulateConsolidation,\r\n\r\n // Active loans\r\n fetchActiveLoans,\r\n\r\n // Utilities\r\n clearError,\r\n reset,\r\n };\r\n}\r\n\r\n/* ================================================================\r\n LOAN SERVICE CLASS (For non-hook contexts)\r\n ================================================================ */\r\n\r\n/**\r\n * LoanService — standalone class for LoanManagement API calls.\r\n * Use in non-React contexts or for server-side operations.\r\n */\r\nexport class LoanService {\r\n private baseUrl: string;\r\n private currency: string;\r\n private timeout: number;\r\n private getAuthToken?: () => string | null;\r\n\r\n constructor(config: LoanApiConfig) {\r\n this.baseUrl = config.baseUrl;\r\n this.currency = config.currency || 'PLN';\r\n this.timeout = config.timeout || 30000;\r\n this.getAuthToken = config.getAuthToken;\r\n }\r\n\r\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n const token = this.getAuthToken?.();\r\n if (token) {\r\n headers['Authorization'] = `Bearer ${token}`;\r\n }\r\n\r\n try {\r\n const response = await fetch(`${this.baseUrl}${endpoint}`, {\r\n ...options,\r\n headers: { ...headers, ...(options.headers as Record<string, string> | undefined) },\r\n signal: controller.signal,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n } finally {\r\n clearTimeout(timeoutId);\r\n }\r\n }\r\n\r\n async getOffers(filters?: LoanOfferFilters): Promise<LoanOffer[]> {\r\n const params = new URLSearchParams();\r\n if (filters?.loanType) params.set('loanType', filters.loanType);\r\n if (filters?.minAmount) params.set('minAmount', String(filters.minAmount));\r\n if (filters?.maxAmount) params.set('maxAmount', String(filters.maxAmount));\r\n params.set('currency', filters?.currency || this.currency);\r\n\r\n const queryString = params.toString();\r\n return this.request<LoanOffer[]>(`/offers${queryString ? `?${queryString}` : ''}`);\r\n }\r\n\r\n async getOfferById(id: string): Promise<LoanOffer> {\r\n return this.request<LoanOffer>(`/offers/${id}`);\r\n }\r\n\r\n async calculateAmortization(request: AmortizationRequest): Promise<AmortizationResult> {\r\n return this.request<AmortizationResult>('/calculate/amortization', {\r\n method: 'POST',\r\n body: JSON.stringify({ ...request, currency: request.currency || this.currency }),\r\n });\r\n }\r\n\r\n async compareScenarios(scenarios: LoanScenarioRequest[]): Promise<LoanComparisonResult> {\r\n return this.request<LoanComparisonResult>('/calculate/compare', {\r\n method: 'POST',\r\n body: JSON.stringify({ scenarios, currency: this.currency }),\r\n });\r\n }\r\n\r\n async submitApplication(application: LoanApplicationRequest): Promise<LoanApplicationState> {\r\n return this.request<LoanApplicationState>('/applications', {\r\n method: 'POST',\r\n body: JSON.stringify(application),\r\n });\r\n }\r\n\r\n async getApplicationStatus(applicationId: string): Promise<LoanApplicationState> {\r\n return this.request<LoanApplicationState>(`/applications/${applicationId}`);\r\n }\r\n\r\n async analyzeRefinancing(\r\n currentLoanId: string,\r\n newOfferIds?: string[],\r\n ): Promise<RefinancingAnalysis[]> {\r\n return this.request<RefinancingAnalysis[]>('/refinancing/analyze', {\r\n method: 'POST',\r\n body: JSON.stringify({ currentLoanId, newOfferIds }),\r\n });\r\n }\r\n\r\n async simulateConsolidation(\r\n debts: DebtItem[],\r\n targetRate?: number,\r\n ): Promise<ConsolidationSimulation> {\r\n return this.request<ConsolidationSimulation>('/consolidation/simulate', {\r\n method: 'POST',\r\n body: JSON.stringify({ debts, targetRate, currency: this.currency }),\r\n });\r\n }\r\n\r\n async getActiveLoans(): Promise<ActiveLoan[]> {\r\n return this.request<ActiveLoan[]>('/active');\r\n }\r\n}\r\n\r\nexport default useLoanApi;\r\n"],"names":["ErpDataAdapter","config","path","init","f","headers","token","res","req","params","_a","_b","qs","id","item","ids","items","fields","createSignalRAdapter","signalR","status","listeners","setStatus","s","cb","connection","event","handler","method","args","useSignalRLiveData","options","adapter","entityName","keyField","initialData","onBeforeChange","onAfterChange","onConflict","subscribeMethod","unsubscribeMethod","changeEventName","flashChanges","flashDuration","optimisticUpdates","data","setData","useState","flashedKeys","setFlashedKeys","pendingKeys","setPendingKeys","error","setError","pendingChangesRef","useRef","flashTimeoutsRef","applyChange","useCallback","currentData","change","operation","key","newData","changes","row","pending","resolved","flashRow","existingTimeout","prev","timeout","next","handleChange","applyRemoteUpdate","registerPendingChange","currentRow","confirmPendingChange","rollbackPendingChange","subscribe","e","unsubscribe","useEffect","unsubStatus","newStatus","createSignalRDataOperations","fetchMethod","insertMethod","updateMethod","deleteMethod","applyBatchChanges","dataMap","existing","useEntityPresence","entityType","entityId","currentUser","initialStatus","heartbeatInterval","idleTimeout","awayTimeout","joinMethod","leaveMethod","presenceEventName","joinedEventName","leftEventName","onPresenceChange","onEditConflict","presence","setPresence","myStatus","setLocalMyStatus","editingField","setEditingField","connectionStatus","setConnectionStatus","lastActivityRef","heartbeatRef","idleTimerRef","awayTimerRef","entityIds","entityKey","sendUpdate","field","selection","setMyStatus","startEditing","existingEditor","p","stopEditing","updateSelection","isFieldLocked","editor","getFieldEditor","refresh","result","handleActivity","handlePresenceChange","handleUserJoined","handleUserLeft","join","events","viewers","editors","hasActiveEditors","generateUserColor","userId","colors","hash","i","formatPresenceStatus","useMultiEntityPresence","batchEventName","presenceByEntity","setPresenceByEntity","handleBatchUpdate","getPresence","isEntityBeingEdited","getEntityEditors","cellKey","cell","parseTimestamp","ts","useCollaborativeDataGrid","conflictStrategy","mergeFunction","onBeforeCommit","onAfterCommit","enableCellLocking","lockTimeout","enableCursorSharing","cursorDebounce","liveDataOptions","cellLocks","setCellLocks","pendingEdits","setPendingEdits","conflicts","setConflicts","focusedCell","setFocusedCell","selectedCells","setSelectedCells","selectedRows","setSelectedRows","editingCell","setEditingCell","userSelections","setUserSelections","cursorDebounceRef","userColorCache","getUserColor","handleDataConflict","local","remote","pendingEdit","localTimestamp","remoteTimestamp","conflict","liveData","sendCursorUpdate","lockHandler","unlockHandler","interval","now","changed","lock","isCellLocked","getCellEditor","startCellEdit","commitCellEdit","newValue","r","oldValue","cancelCellEdit","edit","resolveConflict","resolution","c","insertRow","updateRow","deleteRow","mySelection","useMemo","ErpAuthAdapter","body","ErpFileAdapter","h","file","folder","form","files","blob","onProgress","signal","resolve","reject","xhr","k","v","results","evt","ErpExportAdapter","downloadUrl","filename","url","a","createMiddlewarePipeline","middlewares","baseFetch","realFetch","input","ctx","mw","nextHandler","loggingMiddleware","start","ms","err","retryMiddleware","opts","maxRetries","baseDelay","retryOn","lastError","attempt","delay","headerMiddleware","extra","ErpRateLimiter","tryNext","t","oldestInWindow","waitMs","entry","ErpOptimisticStore","listener","snapshot","fn","m","tempKey","optimistic","mutation","it","idx","previous","ErpOfflineQueue","raw","max","audioControlRegistry","graphicControlRegistry","threeControlRegistry","gamificationControlRegistry","businessControlRegistry","authControlRegistry","socialControlRegistry","allControlRegistries","defaultFetcher","response","errorBody","parsed","useLoanApi","baseUrl","currency","fetcher","getAuthToken","onError","offers","setOffers","selectedOffer","setSelectedOffer","latestCalculation","setLatestCalculation","latestComparison","setLatestComparison","applicationState","setApplicationState","creditAnalysis","setCreditAnalysis","savedCalculations","setSavedCalculations","activeLoans","setActiveLoans","loading","setLoading","abortControllers","getHeaders","makeRequest","endpoint","loadingKey","existingController","controller","timeoutId","apiError","fetchOffers","filters","queryString","selectOffer","offer","getOfferDetails","offerId","calculateAmortization","request","compareLoanScenarios","scenarios","runCreditAnalysis","monthlyIncome","monthlyDebts","requestedAmount","submitApplication","application","getApplicationStatus","applicationId","cancelApplication","saveCalculation","name","savedConfig","notes","savedCalc","loadCalculation","deleteSavedCalculation","fetchSavedCalculations","analyzeRefinancing","currentLoanId","newOfferIds","simulateConsolidation","debts","targetRate","fetchActiveLoans","clearError","reset","LoanService"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/ErpDataAdapter.ts","../src/ErpSignalRAdapter.ts","../src/useSignalRLiveData.ts","../src/useEntityPresence.ts","../src/useCollaborativeDataGrid.ts","../src/ErpAuthAdapter.ts","../src/ErpFileAdapter.ts","../src/ErpExportAdapter.ts","../src/middleware.ts","../src/ErpRateLimiter.ts","../src/ErpOptimisticStore.ts","../src/ErpOfflineQueue.ts","../src/registries/audioControls.ts","../src/registries/authControls.ts","../src/registries/businessControls.ts","../src/registries/gamificationControls.ts","../src/registries/graphicControls.ts","../src/registries/socialControls.ts","../src/registries/threeControls.ts","../src/registries/index.ts","../src/finance/useLoanApi.ts"],"sourcesContent":["/**\r\n * ErpDataAdapter — generic REST adapter connecting NiceDataGrid, NiceList, etc.\r\n * to OmniVerk's CRUD REST API.\r\n *\r\n * Usage:\r\n * const adapter = new ErpDataAdapter('/api/products');\r\n * const result = await adapter.load({ skip: 0, take: 20, sort: [{ field: 'name', dir: 'asc' }] });\r\n */\r\n\r\nimport type { ErpDataRequest, ErpResponse, ErpFilter, ErpSort } from './types';\r\n\r\nexport interface ErpDataAdapterConfig {\r\n /** Base URL for the entity, e.g. \"/api/products\". */\r\n baseUrl: string;\r\n /** Custom fetch implementation (defaults to window.fetch). */\r\n fetch?: typeof fetch;\r\n /** Default headers (e.g. Authorization). */\r\n headers?: Record<string, string>;\r\n /** Token factory — called before each request to get a fresh bearer token. */\r\n tokenFactory?: () => string | Promise<string>;\r\n}\r\n\r\nexport class ErpDataAdapter<T = unknown> {\r\n private cfg: ErpDataAdapterConfig;\r\n\r\n constructor(config: ErpDataAdapterConfig | string) {\r\n this.cfg = typeof config === 'string' ? { baseUrl: config } : config;\r\n }\r\n\r\n private async request<R>(path: string, init?: RequestInit): Promise<R> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n ...this.cfg.headers,\r\n };\r\n if (this.cfg.tokenFactory) {\r\n const token = await this.cfg.tokenFactory();\r\n headers['Authorization'] = `Bearer ${token}`;\r\n }\r\n const res = await f(`${this.cfg.baseUrl}${path}`, {\r\n ...init,\r\n headers: { ...headers, ...init?.headers },\r\n });\r\n if (!res.ok) {\r\n throw new Error(`ERP request failed: ${res.status} ${res.statusText}`);\r\n }\r\n return res.json() as Promise<R>;\r\n }\r\n\r\n /** Load paged/filtered/sorted data. */\r\n async load(req?: ErpDataRequest): Promise<ErpResponse<T[]>> {\r\n const params = new URLSearchParams();\r\n if (req?.skip != null) {\r\n params.set('skip', String(req.skip));\r\n }\r\n if (req?.take != null) {\r\n params.set('take', String(req.take));\r\n }\r\n if (req?.search) {\r\n params.set('search', req.search);\r\n }\r\n if (req?.sort?.length) {\r\n params.set('sort', JSON.stringify(req.sort));\r\n }\r\n if (req?.filters?.length) {\r\n params.set('filters', JSON.stringify(req.filters));\r\n }\r\n const qs = params.toString();\r\n return this.request<ErpResponse<T[]>>(qs ? `?${qs}` : '');\r\n }\r\n\r\n /** Get single entity by id. */\r\n async getById(id: string | number): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(`/${id}`);\r\n }\r\n\r\n /** Create entity. */\r\n async create(item: Partial<T>): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>('', { method: 'POST', body: JSON.stringify(item) });\r\n }\r\n\r\n /** Update entity. */\r\n async update(id: string | number, item: Partial<T>): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(`/${id}`, { method: 'PUT', body: JSON.stringify(item) });\r\n }\r\n\r\n /** Delete entity. */\r\n async remove(id: string | number): Promise<ErpResponse<void>> {\r\n return this.request<ErpResponse<void>>(`/${id}`, { method: 'DELETE' });\r\n }\r\n\r\n /** Batch delete. */\r\n async removeBatch(ids: (string | number)[]): Promise<ErpResponse<void>> {\r\n return this.request<ErpResponse<void>>('/batch-delete', {\r\n method: 'POST',\r\n body: JSON.stringify({ ids }),\r\n });\r\n }\r\n\r\n /** Batch create — send multiple items in a single request. */\r\n async createBatch(items: Partial<T>[]): Promise<ErpResponse<T[]>> {\r\n return this.request<ErpResponse<T[]>>('/batch', {\r\n method: 'POST',\r\n body: JSON.stringify({ items }),\r\n });\r\n }\r\n\r\n /** Batch update — send multiple items with ids in a single request. */\r\n async updateBatch(items: { id: string | number; data: Partial<T> }[]): Promise<ErpResponse<T[]>> {\r\n return this.request<ErpResponse<T[]>>('/batch', {\r\n method: 'PUT',\r\n body: JSON.stringify({ items }),\r\n });\r\n }\r\n\r\n /** Partial update (PATCH) a single entity. */\r\n async patch(id: string | number, fields: Partial<T>): Promise<ErpResponse<T>> {\r\n return this.request<ErpResponse<T>>(`/${id}`, {\r\n method: 'PATCH',\r\n body: JSON.stringify(fields),\r\n });\r\n }\r\n}\r\n","/**\r\n * ErpSignalRAdapter — wraps a SignalR HubConnection for real-time features\r\n * (NiceChat, NiceScheduler live updates, NiceDataGrid auto-refresh, etc.)\r\n */\r\n\r\n/* eslint-disable @typescript-eslint/no-explicit-any --\r\n * `any[]` / `Promise<any>` mirror the upstream `@microsoft/signalr`\r\n * `HubConnection.on/off/invoke` signatures, where handler arguments and\r\n * server invocation results are intentionally untyped at the wire level\r\n * (consumers narrow them per-event). Widening to `unknown` would force\r\n * every consumer to add casts at every call site. */\r\n\r\nimport type { ErpSignalRConfig } from './types';\r\n\r\nexport type SignalRStatus = 'disconnected' | 'connecting' | 'connected' | 'reconnecting';\r\n\r\nexport interface ErpSignalRAdapter {\r\n readonly status: SignalRStatus;\r\n start(): Promise<void>;\r\n stop(): Promise<void>;\r\n on(event: string, handler: (...args: any[]) => void): void;\r\n off(event: string, handler: (...args: any[]) => void): void;\r\n invoke(method: string, ...args: any[]): Promise<any>;\r\n onStatusChange(cb: (status: SignalRStatus) => void): () => void;\r\n}\r\n\r\n/**\r\n * Create a SignalR adapter using dynamic import of @microsoft/signalr.\r\n * This keeps the dependency optional — consumers must install it themselves.\r\n */\r\nexport async function createSignalRAdapter(config: ErpSignalRConfig): Promise<ErpSignalRAdapter> {\r\n const signalR = await import('@microsoft/signalr');\r\n\r\n let status: SignalRStatus = 'disconnected';\r\n const listeners = new Set<(s: SignalRStatus) => void>();\r\n\r\n const setStatus = (s: SignalRStatus) => {\r\n status = s;\r\n listeners.forEach((cb) => cb(s));\r\n };\r\n\r\n const builder = new signalR.HubConnectionBuilder()\r\n .withUrl(config.hubUrl, {\r\n accessTokenFactory: config.accessTokenFactory\r\n ? () => config.accessTokenFactory!()\r\n : undefined,\r\n })\r\n .withAutomaticReconnect();\r\n\r\n const connection = builder.build();\r\n\r\n connection.onreconnecting(() => setStatus('reconnecting'));\r\n connection.onreconnected(() => setStatus('connected'));\r\n connection.onclose(() => setStatus('disconnected'));\r\n\r\n const adapter: ErpSignalRAdapter = {\r\n get status() {\r\n return status;\r\n },\r\n async start() {\r\n setStatus('connecting');\r\n await connection.start();\r\n setStatus('connected');\r\n },\r\n async stop() {\r\n await connection.stop();\r\n setStatus('disconnected');\r\n },\r\n on(event, handler) {\r\n connection.on(event, handler);\r\n },\r\n off(event, handler) {\r\n connection.off(event, handler);\r\n },\r\n invoke(method, ...args) {\r\n return connection.invoke(method, ...args);\r\n },\r\n onStatusChange(cb) {\r\n listeners.add(cb);\r\n return () => listeners.delete(cb);\r\n },\r\n };\r\n\r\n return adapter;\r\n}\r\n","/**\r\n * useSignalRLiveData — React hook for real-time data updates via SignalR.\r\n *\r\n * Provides automatic push updates for insert/update/delete operations\r\n * on a data grid or any list-based component.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { data, status, applyRemoteUpdate } = useSignalRLiveData<Employee>({\r\n * adapter: signalRAdapter,\r\n * entityName: 'employees',\r\n * keyField: 'id',\r\n * initialData: employees,\r\n * });\r\n *\r\n * return <NiceDataGrid data={data} keyField=\"id\" columns={columns} />;\r\n * ```\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useRef } from 'react';\r\n\r\nimport type { ErpSignalRAdapter, SignalRStatus } from './ErpSignalRAdapter';\r\n\r\n/* ================================================================\r\n TYPES\r\n ================================================================ */\r\n\r\n/** Type of real-time data operation. */\r\nexport type LiveDataOperation = 'insert' | 'update' | 'delete';\r\n\r\n/** A single real-time data change event. */\r\nexport interface LiveDataChange<T = unknown> {\r\n /** Operation type: insert, update, or delete. */\r\n operation: LiveDataOperation;\r\n /** Entity type name (e.g., \"employees\", \"orders\"). */\r\n entityName: string;\r\n /** Primary key of the affected row. */\r\n key: unknown;\r\n /** Full data for insert/update; undefined for delete. */\r\n data?: T;\r\n /** Partial changes for update (fields that changed). */\r\n changes?: Partial<T>;\r\n /** Server timestamp of the change. */\r\n timestamp?: string;\r\n /** User ID who made the change. */\r\n changedBy?: string;\r\n}\r\n\r\n/** Configuration for useSignalRLiveData hook. */\r\nexport interface UseSignalRLiveDataOptions<T> {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity name to subscribe to (e.g., \"employees\"). */\r\n entityName: string;\r\n /** Primary key field name. */\r\n keyField: keyof T & string;\r\n /** Initial data array. */\r\n initialData?: T[];\r\n /** Called when a remote change is received. Return false to reject the update. */\r\n onBeforeChange?: (change: LiveDataChange<T>) => boolean | Promise<boolean>;\r\n /** Called after a change has been applied locally. */\r\n onAfterChange?: (change: LiveDataChange<T>, data: T[]) => void;\r\n /** Called when there's a conflict (local pending edit vs remote update). */\r\n onConflict?: (local: T, remote: T, change: LiveDataChange<T>) => T | 'local' | 'remote' | 'merge';\r\n /** Hub method name for subscribing. Default: \"SubscribeToEntity\". */\r\n subscribeMethod?: string;\r\n /** Hub method name for unsubscribing. Default: \"UnsubscribeFromEntity\". */\r\n unsubscribeMethod?: string;\r\n /** Hub event name for data changes. Default: \"EntityChanged\". */\r\n changeEventName?: string;\r\n /** Enable flash animation for changed rows. */\r\n flashChanges?: boolean;\r\n /** Duration of flash animation in ms. Default: 500. */\r\n flashDuration?: number;\r\n /** Enable optimistic UI updates. Default: true. */\r\n optimisticUpdates?: boolean;\r\n}\r\n\r\n/** State returned by useSignalRLiveData hook. */\r\nexport interface SignalRLiveDataState<T> {\r\n /** Current data array with real-time updates applied. */\r\n data: T[];\r\n /** SignalR connection status. */\r\n status: SignalRStatus;\r\n /** Set of row keys that were recently changed (for flash animation). */\r\n flashedKeys: Set<unknown>;\r\n /** Set of row keys with pending local changes. */\r\n pendingKeys: Set<unknown>;\r\n /** Last error if any. */\r\n error: Error | null;\r\n /** Manually apply a remote update (for testing or custom handling). */\r\n applyRemoteUpdate: (change: LiveDataChange<T>) => void;\r\n /** Register a local pending change (for optimistic UI). */\r\n registerPendingChange: (key: unknown, change: Partial<T>) => void;\r\n /** Confirm a pending change was saved server-side. */\r\n confirmPendingChange: (key: unknown) => void;\r\n /** Rollback a pending change (server rejected). */\r\n rollbackPendingChange: (key: unknown) => void;\r\n /** Force re-subscribe to the entity. */\r\n resubscribe: () => Promise<void>;\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useSignalRLiveData<T extends Record<string, unknown>>(\r\n options: UseSignalRLiveDataOptions<T>,\r\n): SignalRLiveDataState<T> {\r\n const {\r\n adapter,\r\n entityName,\r\n keyField,\r\n initialData = [],\r\n onBeforeChange,\r\n onAfterChange,\r\n onConflict,\r\n subscribeMethod = 'SubscribeToEntity',\r\n unsubscribeMethod = 'UnsubscribeFromEntity',\r\n changeEventName = 'EntityChanged',\r\n flashChanges = true,\r\n flashDuration = 500,\r\n optimisticUpdates = true,\r\n } = options;\r\n\r\n const [data, setData] = useState<T[]>(initialData);\r\n const [status, setStatus] = useState<SignalRStatus>(adapter.status);\r\n const [flashedKeys, setFlashedKeys] = useState<Set<unknown>>(new Set());\r\n const [pendingKeys, setPendingKeys] = useState<Set<unknown>>(new Set());\r\n const [error, setError] = useState<Error | null>(null);\r\n\r\n // Keep track of pending local changes for conflict resolution\r\n const pendingChangesRef = useRef<Map<unknown, { original: T; changes: Partial<T> }>>(new Map());\r\n const flashTimeoutsRef = useRef<Map<unknown, ReturnType<typeof setTimeout>>>(new Map());\r\n\r\n // Apply a single change to the data array\r\n const applyChange = useCallback(\r\n (currentData: T[], change: LiveDataChange<T>): T[] => {\r\n const { operation, key, data: newData, changes } = change;\r\n\r\n switch (operation) {\r\n case 'insert':\r\n if (!newData) {\r\n return currentData;\r\n }\r\n // Check if already exists (duplicate event)\r\n if (currentData.some((row) => row[keyField] === key)) {\r\n return currentData;\r\n }\r\n return [...currentData, newData];\r\n\r\n case 'update':\r\n return currentData.map((row) => {\r\n if (row[keyField] !== key) {\r\n return row;\r\n }\r\n\r\n // Check for conflicts with pending local changes\r\n const pending = pendingChangesRef.current.get(key);\r\n if (pending && onConflict) {\r\n const resolved = onConflict(\r\n { ...row, ...pending.changes } as T,\r\n (newData ?? { ...row, ...changes }) as T,\r\n change,\r\n );\r\n if (resolved === 'local') {\r\n return { ...row, ...pending.changes };\r\n } else if (resolved === 'remote') {\r\n pendingChangesRef.current.delete(key);\r\n return newData ?? { ...row, ...changes };\r\n } else if (resolved === 'merge') {\r\n return { ...row, ...changes, ...pending.changes };\r\n } else {\r\n return resolved;\r\n }\r\n }\r\n\r\n // No conflict, apply remote change\r\n if (newData) {\r\n return newData;\r\n }\r\n return { ...row, ...changes };\r\n });\r\n\r\n case 'delete':\r\n return currentData.filter((row) => row[keyField] !== key);\r\n\r\n default:\r\n return currentData;\r\n }\r\n },\r\n [keyField, onConflict],\r\n );\r\n\r\n // Flash a row temporarily to indicate change\r\n const flashRow = useCallback(\r\n (key: unknown) => {\r\n if (!flashChanges) {\r\n return;\r\n }\r\n\r\n // Clear existing timeout for this key\r\n const existingTimeout = flashTimeoutsRef.current.get(key);\r\n if (existingTimeout) {\r\n clearTimeout(existingTimeout);\r\n }\r\n\r\n // Add to flashed set\r\n setFlashedKeys((prev) => new Set([...prev, key]));\r\n\r\n // Remove after duration\r\n const timeout = setTimeout(() => {\r\n setFlashedKeys((prev) => {\r\n const next = new Set(prev);\r\n next.delete(key);\r\n return next;\r\n });\r\n flashTimeoutsRef.current.delete(key);\r\n }, flashDuration);\r\n\r\n flashTimeoutsRef.current.set(key, timeout);\r\n },\r\n [flashChanges, flashDuration],\r\n );\r\n\r\n // Handle incoming real-time change\r\n const handleChange = useCallback(\r\n async (change: LiveDataChange<T>) => {\r\n // Filter by entity name\r\n if (change.entityName !== entityName) {\r\n return;\r\n }\r\n\r\n // Allow consumer to reject the change\r\n if (onBeforeChange) {\r\n const allowed = await onBeforeChange(change);\r\n if (!allowed) {\r\n return;\r\n }\r\n }\r\n\r\n // Apply the change\r\n setData((currentData) => {\r\n const newData = applyChange(currentData, change);\r\n onAfterChange?.(change, newData);\r\n return newData;\r\n });\r\n\r\n // Flash the affected row\r\n flashRow(change.key);\r\n },\r\n [entityName, onBeforeChange, applyChange, onAfterChange, flashRow],\r\n );\r\n\r\n // Public method to manually apply a change\r\n const applyRemoteUpdate = useCallback(\r\n (change: LiveDataChange<T>) => {\r\n handleChange(change);\r\n },\r\n [handleChange],\r\n );\r\n\r\n // Register a pending local change (optimistic UI)\r\n const registerPendingChange = useCallback(\r\n (key: unknown, changes: Partial<T>) => {\r\n if (!optimisticUpdates) {\r\n return;\r\n }\r\n\r\n const currentRow = data.find((row) => row[keyField] === key);\r\n if (currentRow) {\r\n pendingChangesRef.current.set(key, { original: currentRow, changes });\r\n setPendingKeys((prev) => new Set([...prev, key]));\r\n }\r\n },\r\n [data, keyField, optimisticUpdates],\r\n );\r\n\r\n // Confirm a pending change was saved\r\n const confirmPendingChange = useCallback((key: unknown) => {\r\n pendingChangesRef.current.delete(key);\r\n setPendingKeys((prev) => {\r\n const next = new Set(prev);\r\n next.delete(key);\r\n return next;\r\n });\r\n }, []);\r\n\r\n // Rollback a pending change\r\n const rollbackPendingChange = useCallback(\r\n (key: unknown) => {\r\n const pending = pendingChangesRef.current.get(key);\r\n if (pending) {\r\n setData((currentData) =>\r\n currentData.map((row) => (row[keyField] === key ? pending.original : row)),\r\n );\r\n pendingChangesRef.current.delete(key);\r\n setPendingKeys((prev) => {\r\n const next = new Set(prev);\r\n next.delete(key);\r\n return next;\r\n });\r\n }\r\n },\r\n [keyField],\r\n );\r\n\r\n // Subscribe/unsubscribe to entity changes\r\n const subscribe = useCallback(async () => {\r\n if (adapter.status !== 'connected') {\r\n return;\r\n }\r\n try {\r\n await adapter.invoke(subscribeMethod, entityName);\r\n setError(null);\r\n } catch (e) {\r\n setError(e instanceof Error ? e : new Error(String(e)));\r\n }\r\n }, [adapter, subscribeMethod, entityName]);\r\n\r\n const unsubscribe = useCallback(async () => {\r\n if (adapter.status !== 'connected') {\r\n return;\r\n }\r\n try {\r\n await adapter.invoke(unsubscribeMethod, entityName);\r\n } catch {\r\n // Ignore unsubscribe errors\r\n }\r\n }, [adapter, unsubscribeMethod, entityName]);\r\n\r\n // Setup event handlers\r\n useEffect(() => {\r\n const unsubStatus = adapter.onStatusChange((newStatus) => {\r\n setStatus(newStatus);\r\n if (newStatus === 'connected') {\r\n subscribe();\r\n }\r\n });\r\n\r\n adapter.on(changeEventName, handleChange);\r\n\r\n // Initial subscription if already connected\r\n if (adapter.status === 'connected') {\r\n subscribe();\r\n }\r\n\r\n return () => {\r\n unsubStatus();\r\n adapter.off(changeEventName, handleChange);\r\n unsubscribe();\r\n\r\n // Clear all flash timeouts\r\n flashTimeoutsRef.current.forEach((timeout) => clearTimeout(timeout));\r\n flashTimeoutsRef.current.clear();\r\n };\r\n }, [adapter, changeEventName, handleChange, subscribe, unsubscribe]);\r\n\r\n // Update data when initialData changes\r\n useEffect(() => {\r\n setData(initialData);\r\n }, [initialData]);\r\n\r\n return {\r\n data,\r\n status,\r\n flashedKeys,\r\n pendingKeys,\r\n error,\r\n applyRemoteUpdate,\r\n registerPendingChange,\r\n confirmPendingChange,\r\n rollbackPendingChange,\r\n resubscribe: subscribe,\r\n };\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Create a SignalR-backed DataSource\r\n ================================================================ */\r\n\r\nexport interface SignalRDataSourceConfig<T> {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity name for subscriptions. */\r\n entityName: string;\r\n /** Primary key field. */\r\n keyField: keyof T & string;\r\n /** Hub method name to fetch initial data. Default: \"GetEntities\". */\r\n fetchMethod?: string;\r\n /** Hub method name to insert. Default: \"InsertEntity\". */\r\n insertMethod?: string;\r\n /** Hub method name to update. Default: \"UpdateEntity\". */\r\n updateMethod?: string;\r\n /** Hub method name to delete. Default: \"DeleteEntity\". */\r\n deleteMethod?: string;\r\n}\r\n\r\n/**\r\n * Create callbacks for CRUD operations via SignalR.\r\n * Use with useSignalRLiveData for a complete real-time data solution.\r\n */\r\nexport function createSignalRDataOperations<T extends Record<string, unknown>>(\r\n config: SignalRDataSourceConfig<T>,\r\n) {\r\n const {\r\n adapter,\r\n entityName,\r\n keyField,\r\n fetchMethod = 'GetEntities',\r\n insertMethod = 'InsertEntity',\r\n updateMethod = 'UpdateEntity',\r\n deleteMethod = 'DeleteEntity',\r\n } = config;\r\n\r\n return {\r\n /** Fetch all entities from the hub. */\r\n async fetchAll(options?: { skip?: number; take?: number; filter?: unknown }): Promise<T[]> {\r\n return adapter.invoke(fetchMethod, entityName, options);\r\n },\r\n\r\n /** Insert a new entity via the hub. */\r\n async insert(data: Partial<T>): Promise<T> {\r\n return adapter.invoke(insertMethod, entityName, data);\r\n },\r\n\r\n /** Update an existing entity via the hub. */\r\n async update(key: unknown, changes: Partial<T>): Promise<T> {\r\n return adapter.invoke(updateMethod, entityName, key, changes);\r\n },\r\n\r\n /** Delete an entity via the hub. */\r\n async remove(key: unknown): Promise<void> {\r\n return adapter.invoke(deleteMethod, entityName, key);\r\n },\r\n\r\n /** Get the key field name. */\r\n getKeyField(): keyof T & string {\r\n return keyField;\r\n },\r\n };\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Batch update helper\r\n ================================================================ */\r\n\r\nexport interface BatchChange<T> {\r\n operation: LiveDataOperation;\r\n key: unknown;\r\n data?: T;\r\n changes?: Partial<T>;\r\n}\r\n\r\n/**\r\n * Apply multiple changes to a data array efficiently.\r\n * Useful for initial sync or batch operations.\r\n */\r\nexport function applyBatchChanges<T extends Record<string, unknown>>(\r\n data: T[],\r\n changes: BatchChange<T>[],\r\n keyField: keyof T & string,\r\n): T[] {\r\n const dataMap = new Map<unknown, T>(data.map((row) => [row[keyField], row]));\r\n\r\n for (const change of changes) {\r\n switch (change.operation) {\r\n case 'insert':\r\n if (change.data && !dataMap.has(change.key)) {\r\n dataMap.set(change.key, change.data);\r\n }\r\n break;\r\n case 'update':\r\n if (dataMap.has(change.key)) {\r\n const existing = dataMap.get(change.key)!;\r\n dataMap.set(change.key, change.data ?? { ...existing, ...change.changes });\r\n }\r\n break;\r\n case 'delete':\r\n dataMap.delete(change.key);\r\n break;\r\n }\r\n }\r\n\r\n return Array.from(dataMap.values());\r\n}\r\n","/**\r\n * useEntityPresence — React hook for tracking who is viewing/editing entities via SignalR.\r\n *\r\n * Shows real-time presence information: which users are viewing or editing\r\n * specific records in a DataGrid, form, or any entity-based view.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { viewers, editors, myStatus, setMyStatus } = useEntityPresence({\r\n * adapter: signalRAdapter,\r\n * entityType: 'orders',\r\n * entityId: orderId,\r\n * currentUser: { id: userId, name: 'John Doe', avatarUrl: '...' },\r\n * });\r\n *\r\n * return (\r\n * <div>\r\n * {viewers.map(u => <UserAvatar key={u.id} user={u} />)}\r\n * {editors.length > 0 && <span>Editing: {editors.map(e => e.name).join(', ')}</span>}\r\n * </div>\r\n * );\r\n * ```\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useRef } from 'react';\r\n\r\nimport type { ErpSignalRAdapter, SignalRStatus } from './ErpSignalRAdapter';\r\n\r\n/* ================================================================\r\n TYPES\r\n ================================================================ */\r\n\r\n/** User presence status for entity tracking. */\r\nexport type EntityPresenceStatus = 'viewing' | 'editing' | 'idle' | 'away';\r\n\r\n/** User information for presence display. */\r\nexport interface PresenceUser {\r\n /** User ID. */\r\n id: string;\r\n /** Display name. */\r\n name: string;\r\n /** Avatar URL (optional). */\r\n avatarUrl?: string;\r\n /** User's email (optional). */\r\n email?: string;\r\n /** Custom color for cursor/indicator. */\r\n color?: string;\r\n}\r\n\r\n/** Presence data for a single user on an entity. */\r\nexport interface EntityPresenceInfo {\r\n /** The user. */\r\n user: PresenceUser;\r\n /** Current presence status. */\r\n status: EntityPresenceStatus;\r\n /** When they joined this entity view. */\r\n joinedAt: string;\r\n /** Last activity timestamp. */\r\n lastActivityAt: string;\r\n /** Field being edited (if editing). */\r\n editingField?: string;\r\n /** Cursor position (for text fields). */\r\n cursorPosition?: { start: number; end: number };\r\n /** Selection range (for grids). */\r\n selectedRange?: { startRow: number; endRow: number; startCol: number; endCol: number };\r\n}\r\n\r\n/** Configuration for useEntityPresence hook. */\r\nexport interface UseEntityPresenceOptions {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity type name (e.g., \"orders\", \"employees\"). */\r\n entityType: string;\r\n /** Entity ID being viewed/edited. Can be array for bulk selection. */\r\n entityId: string | string[];\r\n /** Current user information. */\r\n currentUser: PresenceUser;\r\n /** Initial presence status. Default: \"viewing\". */\r\n initialStatus?: EntityPresenceStatus;\r\n /** Heartbeat interval in ms. Default: 30000. */\r\n heartbeatInterval?: number;\r\n /** Idle timeout in ms. Default: 60000 (1 min). */\r\n idleTimeout?: number;\r\n /** Away timeout in ms. Default: 300000 (5 min). */\r\n awayTimeout?: number;\r\n /** Hub method for joining. Default: \"JoinEntityView\". */\r\n joinMethod?: string;\r\n /** Hub method for leaving. Default: \"LeaveEntityView\". */\r\n leaveMethod?: string;\r\n /** Hub method for status update. Default: \"UpdateEntityPresence\". */\r\n updateMethod?: string;\r\n /** Hub event for presence changes. Default: \"EntityPresenceChanged\". */\r\n presenceEventName?: string;\r\n /** Hub event for user joined. Default: \"EntityUserJoined\". */\r\n joinedEventName?: string;\r\n /** Hub event for user left. Default: \"EntityUserLeft\". */\r\n leftEventName?: string;\r\n /** Called when presence list changes. */\r\n onPresenceChange?: (presence: EntityPresenceInfo[]) => void;\r\n /** Called when a conflict is detected. */\r\n onEditConflict?: (editor: PresenceUser, field?: string) => void;\r\n}\r\n\r\n/** State returned by useEntityPresence hook. */\r\nexport interface EntityPresenceState {\r\n /** All users currently present on this entity. */\r\n presence: EntityPresenceInfo[];\r\n /** Users only viewing (not editing). */\r\n viewers: PresenceUser[];\r\n /** Users currently editing. */\r\n editors: PresenceUser[];\r\n /** Current user's status. */\r\n myStatus: EntityPresenceStatus;\r\n /** Set current user's status. */\r\n setMyStatus: (status: EntityPresenceStatus, editingField?: string) => void;\r\n /** Start editing a field. */\r\n startEditing: (field: string) => void;\r\n /** Stop editing. */\r\n stopEditing: () => void;\r\n /** Update cursor/selection position. */\r\n updateSelection: (\r\n selection: EntityPresenceInfo['selectedRange'] | EntityPresenceInfo['cursorPosition'],\r\n ) => void;\r\n /** SignalR connection status. */\r\n connectionStatus: SignalRStatus;\r\n /** Check if another user is editing a specific field. */\r\n isFieldLocked: (field: string) => PresenceUser | null;\r\n /** Check if there are any active editors (conflict potential). */\r\n hasActiveEditors: boolean;\r\n /** Get the user editing a field (if any). */\r\n getFieldEditor: (field: string) => PresenceUser | null;\r\n /** Force refresh presence list. */\r\n refresh: () => Promise<void>;\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useEntityPresence(options: UseEntityPresenceOptions): EntityPresenceState {\r\n const {\r\n adapter,\r\n entityType,\r\n entityId,\r\n currentUser,\r\n initialStatus = 'viewing',\r\n heartbeatInterval = 30000,\r\n idleTimeout = 60000,\r\n awayTimeout = 300000,\r\n joinMethod = 'JoinEntityView',\r\n leaveMethod = 'LeaveEntityView',\r\n updateMethod = 'UpdateEntityPresence',\r\n presenceEventName = 'EntityPresenceChanged',\r\n joinedEventName = 'EntityUserJoined',\r\n leftEventName = 'EntityUserLeft',\r\n onPresenceChange,\r\n onEditConflict,\r\n } = options;\r\n\r\n const [presence, setPresence] = useState<EntityPresenceInfo[]>([]);\r\n const [myStatus, setLocalMyStatus] = useState<EntityPresenceStatus>(initialStatus);\r\n const [editingField, setEditingField] = useState<string | undefined>();\r\n const [connectionStatus, setConnectionStatus] = useState<SignalRStatus>(adapter.status);\r\n\r\n const lastActivityRef = useRef<number>(Date.now());\r\n const heartbeatRef = useRef<ReturnType<typeof setInterval> | null>(null);\r\n const idleTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const awayTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n\r\n const entityIds = Array.isArray(entityId) ? entityId : [entityId];\r\n const entityKey = `${entityType}:${entityIds.sort().join(',')}`;\r\n\r\n // Send presence update to server\r\n const sendUpdate = useCallback(\r\n async (status: EntityPresenceStatus, field?: string, selection?: unknown) => {\r\n if (adapter.status !== 'connected') {\r\n return;\r\n }\r\n try {\r\n await adapter.invoke(updateMethod, {\r\n entityType,\r\n entityIds,\r\n userId: currentUser.id,\r\n status,\r\n editingField: field,\r\n selection,\r\n timestamp: new Date().toISOString(),\r\n });\r\n } catch {\r\n // Ignore update errors\r\n }\r\n },\r\n [adapter, updateMethod, entityType, entityIds, currentUser.id],\r\n );\r\n\r\n // Set user status\r\n const setMyStatus = useCallback(\r\n (status: EntityPresenceStatus, field?: string) => {\r\n setLocalMyStatus(status);\r\n setEditingField(field);\r\n sendUpdate(status, field);\r\n lastActivityRef.current = Date.now();\r\n },\r\n [sendUpdate],\r\n );\r\n\r\n // Start editing a field\r\n const startEditing = useCallback(\r\n (field: string) => {\r\n // Check if someone else is editing this field\r\n const existingEditor = presence.find(\r\n (p) => p.user.id !== currentUser.id && p.status === 'editing' && p.editingField === field,\r\n );\r\n if (existingEditor) {\r\n onEditConflict?.(existingEditor.user, field);\r\n }\r\n setMyStatus('editing', field);\r\n },\r\n [presence, currentUser.id, onEditConflict, setMyStatus],\r\n );\r\n\r\n // Stop editing\r\n const stopEditing = useCallback(() => {\r\n setMyStatus('viewing', undefined);\r\n }, [setMyStatus]);\r\n\r\n // Update cursor/selection\r\n const updateSelection = useCallback(\r\n (selection: EntityPresenceInfo['selectedRange'] | EntityPresenceInfo['cursorPosition']) => {\r\n sendUpdate(myStatus, editingField, selection);\r\n lastActivityRef.current = Date.now();\r\n },\r\n [sendUpdate, myStatus, editingField],\r\n );\r\n\r\n // Check if a field is locked by another user\r\n const isFieldLocked = useCallback(\r\n (field: string): PresenceUser | null => {\r\n const editor = presence.find(\r\n (p) => p.user.id !== currentUser.id && p.status === 'editing' && p.editingField === field,\r\n );\r\n return editor?.user ?? null;\r\n },\r\n [presence, currentUser.id],\r\n );\r\n\r\n // Get field editor\r\n const getFieldEditor = useCallback(\r\n (field: string): PresenceUser | null => {\r\n const editor = presence.find((p) => p.status === 'editing' && p.editingField === field);\r\n return editor?.user ?? null;\r\n },\r\n [presence],\r\n );\r\n\r\n // Refresh presence from server\r\n const refresh = useCallback(async () => {\r\n if (adapter.status !== 'connected') {\r\n return;\r\n }\r\n try {\r\n const result = await adapter.invoke('GetEntityPresence', entityType, entityIds);\r\n if (Array.isArray(result)) {\r\n setPresence(result);\r\n onPresenceChange?.(result);\r\n }\r\n } catch {\r\n // Ignore refresh errors\r\n }\r\n }, [adapter, entityType, entityIds, onPresenceChange]);\r\n\r\n // Handle activity (reset idle timers)\r\n const handleActivity = useCallback(() => {\r\n lastActivityRef.current = Date.now();\r\n\r\n // Clear and reset idle timer\r\n if (idleTimerRef.current) {\r\n clearTimeout(idleTimerRef.current);\r\n }\r\n if (awayTimerRef.current) {\r\n clearTimeout(awayTimerRef.current);\r\n }\r\n\r\n // If currently idle/away, go back to viewing/editing\r\n if (myStatus === 'idle' || myStatus === 'away') {\r\n setMyStatus(editingField ? 'editing' : 'viewing', editingField);\r\n }\r\n\r\n // Set new idle timer\r\n idleTimerRef.current = setTimeout(() => {\r\n setMyStatus('idle', editingField);\r\n }, idleTimeout);\r\n\r\n // Set new away timer\r\n awayTimerRef.current = setTimeout(() => {\r\n setMyStatus('away', editingField);\r\n }, awayTimeout);\r\n }, [myStatus, editingField, idleTimeout, awayTimeout, setMyStatus]);\r\n\r\n // Handle presence update events\r\n const handlePresenceChange = useCallback(\r\n (data: { entityType: string; entityIds: string[]; presence: EntityPresenceInfo[] }) => {\r\n if (data.entityType !== entityType) {\r\n return;\r\n }\r\n const hasCommonEntity = data.entityIds.some((id) => entityIds.includes(id));\r\n if (!hasCommonEntity) {\r\n return;\r\n }\r\n\r\n setPresence(data.presence.filter((p) => p.user.id !== currentUser.id));\r\n onPresenceChange?.(data.presence);\r\n },\r\n [entityType, entityIds, currentUser.id, onPresenceChange],\r\n );\r\n\r\n // Handle user joined\r\n const handleUserJoined = useCallback(\r\n (data: { entityType: string; entityIds: string[]; presence: EntityPresenceInfo }) => {\r\n if (data.entityType !== entityType) {\r\n return;\r\n }\r\n const hasCommonEntity = data.entityIds.some((id) => entityIds.includes(id));\r\n if (!hasCommonEntity || data.presence.user.id === currentUser.id) {\r\n return;\r\n }\r\n\r\n setPresence((prev) => {\r\n const existing = prev.findIndex((p) => p.user.id === data.presence.user.id);\r\n if (existing >= 0) {\r\n const next = [...prev];\r\n next[existing] = data.presence;\r\n return next;\r\n }\r\n return [...prev, data.presence];\r\n });\r\n },\r\n [entityType, entityIds, currentUser.id],\r\n );\r\n\r\n // Handle user left\r\n const handleUserLeft = useCallback(\r\n (data: { entityType: string; entityIds: string[]; userId: string }) => {\r\n if (data.entityType !== entityType) {\r\n return;\r\n }\r\n const hasCommonEntity = data.entityIds.some((id) => entityIds.includes(id));\r\n if (!hasCommonEntity) {\r\n return;\r\n }\r\n\r\n setPresence((prev) => prev.filter((p) => p.user.id !== data.userId));\r\n },\r\n [entityType, entityIds],\r\n );\r\n\r\n // Setup SignalR connection and events\r\n useEffect(() => {\r\n const unsubStatus = adapter.onStatusChange(setConnectionStatus);\r\n\r\n adapter.on(presenceEventName, handlePresenceChange);\r\n adapter.on(joinedEventName, handleUserJoined);\r\n adapter.on(leftEventName, handleUserLeft);\r\n\r\n // Join when connected\r\n const join = async () => {\r\n if (adapter.status !== 'connected') {\r\n return;\r\n }\r\n try {\r\n await adapter.invoke(joinMethod, {\r\n entityType,\r\n entityIds,\r\n user: currentUser,\r\n status: myStatus,\r\n });\r\n await refresh();\r\n } catch {\r\n // Ignore join errors\r\n }\r\n };\r\n\r\n if (adapter.status === 'connected') {\r\n join();\r\n }\r\n\r\n return () => {\r\n unsubStatus();\r\n adapter.off(presenceEventName, handlePresenceChange);\r\n adapter.off(joinedEventName, handleUserJoined);\r\n adapter.off(leftEventName, handleUserLeft);\r\n\r\n // Leave on cleanup\r\n if (adapter.status === 'connected') {\r\n adapter\r\n .invoke(leaveMethod, {\r\n entityType,\r\n entityIds,\r\n userId: currentUser.id,\r\n })\r\n .catch(() => {});\r\n }\r\n };\r\n }, [\r\n adapter,\r\n entityKey,\r\n joinMethod,\r\n leaveMethod,\r\n presenceEventName,\r\n joinedEventName,\r\n leftEventName,\r\n handlePresenceChange,\r\n handleUserJoined,\r\n handleUserLeft,\r\n refresh,\r\n currentUser,\r\n myStatus,\r\n entityType,\r\n entityIds,\r\n ]);\r\n\r\n // Setup heartbeat\r\n useEffect(() => {\r\n heartbeatRef.current = setInterval(() => {\r\n sendUpdate(myStatus, editingField);\r\n }, heartbeatInterval);\r\n\r\n return () => {\r\n if (heartbeatRef.current) {\r\n clearInterval(heartbeatRef.current);\r\n }\r\n };\r\n }, [heartbeatInterval, sendUpdate, myStatus, editingField]);\r\n\r\n // Setup activity tracking\r\n useEffect(() => {\r\n const events = ['mousemove', 'keydown', 'mousedown', 'touchstart', 'scroll'];\r\n events.forEach((event) => window.addEventListener(event, handleActivity, { passive: true }));\r\n\r\n // Initial idle timer\r\n idleTimerRef.current = setTimeout(() => {\r\n setMyStatus('idle', editingField);\r\n }, idleTimeout);\r\n\r\n awayTimerRef.current = setTimeout(() => {\r\n setMyStatus('away', editingField);\r\n }, awayTimeout);\r\n\r\n return () => {\r\n events.forEach((event) => window.removeEventListener(event, handleActivity));\r\n if (idleTimerRef.current) {\r\n clearTimeout(idleTimerRef.current);\r\n }\r\n if (awayTimerRef.current) {\r\n clearTimeout(awayTimerRef.current);\r\n }\r\n };\r\n }, [handleActivity, idleTimeout, awayTimeout, editingField, setMyStatus]);\r\n\r\n // Derived state\r\n const viewers = presence\r\n .filter((p) => p.status === 'viewing' || p.status === 'idle')\r\n .map((p) => p.user);\r\n const editors = presence.filter((p) => p.status === 'editing').map((p) => p.user);\r\n const hasActiveEditors = editors.length > 0;\r\n\r\n return {\r\n presence,\r\n viewers,\r\n editors,\r\n myStatus,\r\n setMyStatus,\r\n startEditing,\r\n stopEditing,\r\n updateSelection,\r\n connectionStatus,\r\n isFieldLocked,\r\n hasActiveEditors,\r\n getFieldEditor,\r\n refresh,\r\n };\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Presence avatars component helper\r\n ================================================================ */\r\n\r\n/** Generate a consistent color for a user (based on ID hash). */\r\nexport function generateUserColor(userId: string): string {\r\n const colors = [\r\n '#3b82f6', // blue\r\n '#ef4444', // red\r\n '#22c55e', // green\r\n '#f59e0b', // amber\r\n '#8b5cf6', // violet\r\n '#ec4899', // pink\r\n '#06b6d4', // cyan\r\n '#84cc16', // lime\r\n '#f97316', // orange\r\n '#14b8a6', // teal\r\n ];\r\n let hash = 0;\r\n for (let i = 0; i < userId.length; i++) {\r\n hash = userId.charCodeAt(i) + ((hash << 5) - hash);\r\n }\r\n return colors[Math.abs(hash) % colors.length];\r\n}\r\n\r\n/** Format presence status for display. */\r\nexport function formatPresenceStatus(status: EntityPresenceStatus): string {\r\n switch (status) {\r\n case 'viewing':\r\n return 'Viewing';\r\n case 'editing':\r\n return 'Editing';\r\n case 'idle':\r\n return 'Idle';\r\n case 'away':\r\n return 'Away';\r\n default:\r\n return 'Unknown';\r\n }\r\n}\r\n\r\n/* ================================================================\r\n UTILITY: Multi-entity presence tracking\r\n ================================================================ */\r\n\r\nexport interface MultiEntityPresenceOptions {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity type name. */\r\n entityType: string;\r\n /** Current user information. */\r\n currentUser: PresenceUser;\r\n /** Hub event name for batch presence updates. Default: \"BatchPresenceUpdate\". */\r\n batchEventName?: string;\r\n}\r\n\r\nexport interface MultiEntityPresenceState {\r\n /** Map of entity ID to presence info array. */\r\n presenceByEntity: Map<string, EntityPresenceInfo[]>;\r\n /** Get presence for a specific entity. */\r\n getPresence: (entityId: string) => EntityPresenceInfo[];\r\n /** Check if any user is editing an entity. */\r\n isEntityBeingEdited: (entityId: string) => boolean;\r\n /** Get editors for an entity. */\r\n getEntityEditors: (entityId: string) => PresenceUser[];\r\n}\r\n\r\n/**\r\n * Hook for tracking presence across multiple entities at once.\r\n * Useful for DataGrid row-level presence indicators.\r\n */\r\nexport function useMultiEntityPresence(\r\n options: MultiEntityPresenceOptions,\r\n): MultiEntityPresenceState {\r\n const { adapter, entityType, currentUser, batchEventName = 'BatchPresenceUpdate' } = options;\r\n\r\n const [presenceByEntity, setPresenceByEntity] = useState<Map<string, EntityPresenceInfo[]>>(\r\n new Map(),\r\n );\r\n\r\n // Handle batch presence update\r\n const handleBatchUpdate = useCallback(\r\n (data: { entityType: string; presenceByEntity: Record<string, EntityPresenceInfo[]> }) => {\r\n if (data.entityType !== entityType) {\r\n return;\r\n }\r\n setPresenceByEntity(new Map(Object.entries(data.presenceByEntity)));\r\n },\r\n [entityType],\r\n );\r\n\r\n useEffect(() => {\r\n adapter.on(batchEventName, handleBatchUpdate);\r\n\r\n return () => {\r\n adapter.off(batchEventName, handleBatchUpdate);\r\n };\r\n }, [adapter, batchEventName, handleBatchUpdate]);\r\n\r\n const getPresence = useCallback(\r\n (entityId: string): EntityPresenceInfo[] => {\r\n return presenceByEntity.get(entityId) ?? [];\r\n },\r\n [presenceByEntity],\r\n );\r\n\r\n const isEntityBeingEdited = useCallback(\r\n (entityId: string): boolean => {\r\n const presence = presenceByEntity.get(entityId);\r\n return presence?.some((p) => p.status === 'editing' && p.user.id !== currentUser.id) ?? false;\r\n },\r\n [presenceByEntity, currentUser.id],\r\n );\r\n\r\n const getEntityEditors = useCallback(\r\n (entityId: string): PresenceUser[] => {\r\n const presence = presenceByEntity.get(entityId);\r\n return presence?.filter((p) => p.status === 'editing').map((p) => p.user) ?? [];\r\n },\r\n [presenceByEntity],\r\n );\r\n\r\n return {\r\n presenceByEntity,\r\n getPresence,\r\n isEntityBeingEdited,\r\n getEntityEditors,\r\n };\r\n}\r\n","/**\r\n * useCollaborativeDataGrid — React hook for multi-user collaborative DataGrid editing.\r\n *\r\n * Combines real-time data updates via SignalR with presence tracking to enable\r\n * multiple users to edit the same DataGrid simultaneously with conflict resolution.\r\n *\r\n * Features:\r\n * - Real-time row insert/update/delete across all connected clients\r\n * - Cell-level locking to prevent simultaneous edits\r\n * - Visual presence indicators (who's viewing/editing which row/cell)\r\n * - Optimistic updates with automatic rollback on conflict\r\n * - CRDT-style last-write-wins or custom merge strategies\r\n * - Cursor sharing for selected cells/rows\r\n *\r\n * @example\r\n * ```tsx\r\n * const {\r\n * data, presence, mySelection,\r\n * startCellEdit, commitCellEdit, cancelCellEdit,\r\n * isCellLocked, getCellEditor,\r\n * } = useCollaborativeDataGrid({\r\n * adapter: signalRAdapter,\r\n * entityType: 'orders',\r\n * keyField: 'id',\r\n * currentUser: { id: userId, name: 'John' },\r\n * initialData: orders,\r\n * });\r\n *\r\n * return (\r\n * <NiceDataGrid\r\n * data={data}\r\n * keyField=\"id\"\r\n * columns={columns}\r\n * flashRowKeys={flashedKeys}\r\n * onCellEdit={handleEdit}\r\n * />\r\n * );\r\n * ```\r\n */\r\n\r\nimport { useState, useEffect, useCallback, useRef, useMemo } from 'react';\r\n\r\nimport type { ErpSignalRAdapter, SignalRStatus } from './ErpSignalRAdapter';\r\nimport {\r\n useEntityPresence,\r\n type PresenceUser,\r\n type EntityPresenceInfo,\r\n type EntityPresenceStatus,\r\n generateUserColor,\r\n} from './useEntityPresence';\r\nimport {\r\n useSignalRLiveData,\r\n type LiveDataChange,\r\n type UseSignalRLiveDataOptions,\r\n} from './useSignalRLiveData';\r\n\r\n/* ================================================================\r\n TYPES\r\n ================================================================ */\r\n\r\n/** Cell position in the grid. */\r\nexport interface CellPosition {\r\n rowKey: unknown;\r\n field: string;\r\n}\r\n\r\n/** Range of cells. */\r\nexport interface CellRange {\r\n startRowKey: unknown;\r\n endRowKey: unknown;\r\n startField: string;\r\n endField: string;\r\n}\r\n\r\n/** Pending cell edit. */\r\nexport interface PendingEdit<T = unknown> {\r\n cell: CellPosition;\r\n originalValue: T;\r\n newValue: T;\r\n timestamp: number;\r\n}\r\n\r\n/** Conflict resolution strategy. */\r\nexport type ConflictStrategy =\r\n | 'last-write-wins'\r\n | 'first-write-wins'\r\n | 'merge'\r\n | 'ask-user'\r\n | 'reject';\r\n\r\n/** Edit conflict information. */\r\nexport interface EditConflict<T = unknown> {\r\n cell: CellPosition;\r\n localValue: T;\r\n remoteValue: T;\r\n localUser: PresenceUser;\r\n remoteUser: PresenceUser;\r\n localTimestamp: number;\r\n remoteTimestamp: number;\r\n}\r\n\r\n/** User cursor/selection in the grid. */\r\nexport interface UserSelection {\r\n user: PresenceUser;\r\n selectedCells: CellPosition[];\r\n selectedRows: unknown[];\r\n focusedCell: CellPosition | null;\r\n editingCell: CellPosition | null;\r\n color: string;\r\n}\r\n\r\n/** Configuration for useCollaborativeDataGrid hook. */\r\nexport interface UseCollaborativeDataGridOptions<T> {\r\n /** The SignalR adapter instance. */\r\n adapter: ErpSignalRAdapter;\r\n /** Entity type name (e.g., \"orders\"). */\r\n entityType: string;\r\n /** Primary key field name. */\r\n keyField: keyof T & string;\r\n /** Current user information. */\r\n currentUser: PresenceUser;\r\n /** Initial data array. */\r\n initialData?: T[];\r\n /** Conflict resolution strategy. Default: \"last-write-wins\". */\r\n conflictStrategy?: ConflictStrategy;\r\n /** Custom merge function for \"merge\" strategy. */\r\n mergeFunction?: (local: T, remote: T, field: string) => T;\r\n /** Called when a conflict needs user resolution (for \"ask-user\" strategy). */\r\n onConflict?: (conflict: EditConflict<unknown>) => Promise<'local' | 'remote' | 'merge'>;\r\n /** Called before a cell edit is committed. Return false to cancel. */\r\n onBeforeCommit?: (\r\n cell: CellPosition,\r\n oldValue: unknown,\r\n newValue: unknown,\r\n ) => boolean | Promise<boolean>;\r\n /** Called after a cell edit is committed. */\r\n onAfterCommit?: (cell: CellPosition, newValue: unknown) => void;\r\n /** Enable cell-level locking. Default: true. */\r\n enableCellLocking?: boolean;\r\n /** Lock timeout in ms. Default: 60000 (1 min). */\r\n lockTimeout?: number;\r\n /** Enable cursor sharing. Default: true. */\r\n enableCursorSharing?: boolean;\r\n /** Cursor update debounce in ms. Default: 50. */\r\n cursorDebounce?: number;\r\n /** Enable optimistic updates. Default: true. */\r\n optimisticUpdates?: boolean;\r\n /** Flash updated rows. Default: true. */\r\n flashChanges?: boolean;\r\n /** Flash duration in ms. Default: 500. */\r\n flashDuration?: number;\r\n /** Additional options for SignalR live data. */\r\n liveDataOptions?: Partial<UseSignalRLiveDataOptions<T>>;\r\n}\r\n\r\n/** State returned by useCollaborativeDataGrid hook. */\r\nexport interface CollaborativeDataGridState<T> {\r\n /** Current data array with real-time updates. */\r\n data: T[];\r\n /** All user presence information. */\r\n presence: EntityPresenceInfo[];\r\n /** Other users' selections and cursors. */\r\n userSelections: UserSelection[];\r\n /** Current user's selection. */\r\n mySelection: UserSelection;\r\n /** Set of row keys that were recently changed (for flash animation). */\r\n flashedKeys: Set<unknown>;\r\n /** Pending local edits. */\r\n pendingEdits: Map<string, PendingEdit>;\r\n /** Connection status. */\r\n connectionStatus: SignalRStatus;\r\n /** Current user's presence status. */\r\n myPresenceStatus: EntityPresenceStatus;\r\n\r\n // Selection methods\r\n /** Set focused cell. */\r\n setFocusedCell: (cell: CellPosition | null) => void;\r\n /** Set selected cells. */\r\n setSelectedCells: (cells: CellPosition[]) => void;\r\n /** Set selected rows. */\r\n setSelectedRows: (rowKeys: unknown[]) => void;\r\n\r\n // Edit methods\r\n /** Start editing a cell. Returns false if locked by another user. */\r\n startCellEdit: (cell: CellPosition) => boolean;\r\n /** Commit a cell edit. */\r\n commitCellEdit: (cell: CellPosition, newValue: unknown) => Promise<boolean>;\r\n /** Cancel a cell edit. */\r\n cancelCellEdit: (cell: CellPosition) => void;\r\n /** Check if a cell is locked by another user. */\r\n isCellLocked: (cell: CellPosition) => boolean;\r\n /** Get the user editing a cell (if any). */\r\n getCellEditor: (cell: CellPosition) => PresenceUser | null;\r\n /** Get the user's cursor color. */\r\n getUserColor: (userId: string) => string;\r\n\r\n // Row methods\r\n /** Insert a new row. */\r\n insertRow: (data: Partial<T>) => Promise<void>;\r\n /** Update a row. */\r\n updateRow: (key: unknown, changes: Partial<T>) => Promise<void>;\r\n /** Delete a row. */\r\n deleteRow: (key: unknown) => Promise<void>;\r\n\r\n // Conflict handling\r\n /** Resolve a pending conflict. */\r\n resolveConflict: (cell: CellPosition, resolution: 'local' | 'remote' | 'merge') => void;\r\n /** Get current conflicts. */\r\n conflicts: EditConflict[];\r\n}\r\n\r\n/* ================================================================\r\n HELPER FUNCTIONS\r\n ================================================================ */\r\n\r\nfunction cellKey(cell: CellPosition): string {\r\n return `${String(cell.rowKey)}:${cell.field}`;\r\n}\r\n\r\nfunction parseTimestamp(ts: string | number | undefined): number {\r\n if (!ts) {\r\n return Date.now();\r\n }\r\n if (typeof ts === 'number') {\r\n return ts;\r\n }\r\n return new Date(ts).getTime();\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useCollaborativeDataGrid<T extends Record<string, unknown>>(\r\n options: UseCollaborativeDataGridOptions<T>,\r\n): CollaborativeDataGridState<T> {\r\n const {\r\n adapter,\r\n entityType,\r\n keyField,\r\n currentUser,\r\n initialData = [],\r\n conflictStrategy = 'last-write-wins',\r\n mergeFunction,\r\n onConflict,\r\n onBeforeCommit,\r\n onAfterCommit,\r\n enableCellLocking = true,\r\n lockTimeout = 60000,\r\n enableCursorSharing = true,\r\n cursorDebounce = 50,\r\n optimisticUpdates = true,\r\n flashChanges = true,\r\n flashDuration = 500,\r\n liveDataOptions = {},\r\n } = options;\r\n\r\n // Cell locks and pending edits\r\n const [cellLocks, setCellLocks] = useState<\r\n Map<string, { user: PresenceUser; timestamp: number }>\r\n >(new Map());\r\n const [pendingEdits, setPendingEdits] = useState<Map<string, PendingEdit>>(new Map());\r\n const [conflicts, setConflicts] = useState<EditConflict[]>([]);\r\n\r\n // Selection state\r\n const [focusedCell, setFocusedCell] = useState<CellPosition | null>(null);\r\n const [selectedCells, setSelectedCells] = useState<CellPosition[]>([]);\r\n const [selectedRows, setSelectedRows] = useState<unknown[]>([]);\r\n const [editingCell, setEditingCell] = useState<CellPosition | null>(null);\r\n\r\n // Other users' selections\r\n const [userSelections, setUserSelections] = useState<UserSelection[]>([]);\r\n\r\n const cursorDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const userColorCache = useRef<Map<string, string>>(new Map());\r\n\r\n // Get consistent color for a user\r\n const getUserColor = useCallback((userId: string): string => {\r\n if (!userColorCache.current.has(userId)) {\r\n userColorCache.current.set(userId, generateUserColor(userId));\r\n }\r\n return userColorCache.current.get(userId)!;\r\n }, []);\r\n\r\n // Conflict handler for live data\r\n const handleDataConflict = useCallback(\r\n (local: T, remote: T, change: LiveDataChange<T>): T | 'local' | 'remote' | 'merge' => {\r\n if (!change.changes) {\r\n return remote;\r\n }\r\n\r\n // Check if this is related to a pending edit\r\n const pendingEdit = pendingEdits.get(\r\n cellKey({ rowKey: change.key, field: Object.keys(change.changes)[0] }),\r\n );\r\n if (!pendingEdit) {\r\n return remote;\r\n }\r\n\r\n const localTimestamp = pendingEdit.timestamp;\r\n const remoteTimestamp = parseTimestamp(change.timestamp);\r\n\r\n switch (conflictStrategy) {\r\n case 'last-write-wins':\r\n return localTimestamp > remoteTimestamp ? 'local' : 'remote';\r\n\r\n case 'first-write-wins':\r\n return localTimestamp < remoteTimestamp ? 'local' : 'remote';\r\n\r\n case 'merge':\r\n if (mergeFunction) {\r\n return mergeFunction(local, remote, Object.keys(change.changes)[0]);\r\n }\r\n return 'merge';\r\n\r\n case 'ask-user':\r\n // Add to conflicts queue\r\n const conflict: EditConflict = {\r\n cell: { rowKey: change.key, field: Object.keys(change.changes)[0] },\r\n localValue: pendingEdit.newValue,\r\n remoteValue: change.changes[Object.keys(change.changes)[0] as keyof T],\r\n localUser: currentUser,\r\n remoteUser: change.changedBy\r\n ? { id: change.changedBy, name: change.changedBy }\r\n : { id: 'unknown', name: 'Unknown' },\r\n localTimestamp,\r\n remoteTimestamp,\r\n };\r\n setConflicts((prev) => [...prev, conflict]);\r\n return 'local'; // Keep local until resolved\r\n\r\n case 'reject':\r\n return 'remote';\r\n\r\n default:\r\n return 'remote';\r\n }\r\n },\r\n [pendingEdits, conflictStrategy, mergeFunction, currentUser],\r\n );\r\n\r\n // Use SignalR live data hook\r\n const liveData = useSignalRLiveData<T>({\r\n adapter,\r\n entityName: entityType,\r\n keyField,\r\n initialData,\r\n flashChanges,\r\n flashDuration,\r\n optimisticUpdates,\r\n onConflict: handleDataConflict,\r\n ...liveDataOptions,\r\n });\r\n\r\n // Use entity presence hook\r\n const presence = useEntityPresence({\r\n adapter,\r\n entityType,\r\n entityId: 'grid', // Special ID for grid-level presence\r\n currentUser,\r\n initialStatus: 'viewing',\r\n });\r\n\r\n // Send cursor update\r\n const sendCursorUpdate = useCallback(() => {\r\n if (!enableCursorSharing || adapter.status !== 'connected') {\r\n return;\r\n }\r\n\r\n adapter\r\n .invoke('UpdateGridSelection', {\r\n entityType,\r\n userId: currentUser.id,\r\n focusedCell,\r\n selectedCells,\r\n selectedRows,\r\n editingCell,\r\n })\r\n .catch(() => {});\r\n }, [\r\n adapter,\r\n enableCursorSharing,\r\n entityType,\r\n currentUser.id,\r\n focusedCell,\r\n selectedCells,\r\n selectedRows,\r\n editingCell,\r\n ]);\r\n\r\n // Debounced cursor sharing\r\n useEffect(() => {\r\n if (!enableCursorSharing) {\r\n return;\r\n }\r\n\r\n if (cursorDebounceRef.current) {\r\n clearTimeout(cursorDebounceRef.current);\r\n }\r\n\r\n cursorDebounceRef.current = setTimeout(sendCursorUpdate, cursorDebounce);\r\n\r\n return () => {\r\n if (cursorDebounceRef.current) {\r\n clearTimeout(cursorDebounceRef.current);\r\n }\r\n };\r\n }, [\r\n enableCursorSharing,\r\n sendCursorUpdate,\r\n cursorDebounce,\r\n focusedCell,\r\n selectedCells,\r\n selectedRows,\r\n editingCell,\r\n ]);\r\n\r\n // Handle selection updates from other users\r\n useEffect(() => {\r\n if (!enableCursorSharing) {\r\n return;\r\n }\r\n\r\n const handler = (data: {\r\n entityType: string;\r\n userId: string;\r\n userName: string;\r\n avatarUrl?: string;\r\n focusedCell: CellPosition | null;\r\n selectedCells: CellPosition[];\r\n selectedRows: unknown[];\r\n editingCell: CellPosition | null;\r\n }) => {\r\n if (data.entityType !== entityType || data.userId === currentUser.id) {\r\n return;\r\n }\r\n\r\n setUserSelections((prev) => {\r\n const existing = prev.findIndex((s) => s.user.id === data.userId);\r\n const selection: UserSelection = {\r\n user: { id: data.userId, name: data.userName, avatarUrl: data.avatarUrl },\r\n focusedCell: data.focusedCell,\r\n selectedCells: data.selectedCells,\r\n selectedRows: data.selectedRows,\r\n editingCell: data.editingCell,\r\n color: getUserColor(data.userId),\r\n };\r\n\r\n if (existing >= 0) {\r\n const next = [...prev];\r\n next[existing] = selection;\r\n return next;\r\n }\r\n return [...prev, selection];\r\n });\r\n };\r\n\r\n adapter.on('GridSelectionUpdated', handler);\r\n\r\n return () => {\r\n adapter.off('GridSelectionUpdated', handler);\r\n };\r\n }, [adapter, enableCursorSharing, entityType, currentUser.id, getUserColor]);\r\n\r\n // Handle cell lock updates\r\n useEffect(() => {\r\n if (!enableCellLocking) {\r\n return;\r\n }\r\n\r\n const lockHandler = (data: {\r\n entityType: string;\r\n cell: CellPosition;\r\n user: PresenceUser;\r\n timestamp: number;\r\n }) => {\r\n if (data.entityType !== entityType) {\r\n return;\r\n }\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.set(cellKey(data.cell), { user: data.user, timestamp: data.timestamp });\r\n return next;\r\n });\r\n };\r\n\r\n const unlockHandler = (data: { entityType: string; cell: CellPosition }) => {\r\n if (data.entityType !== entityType) {\r\n return;\r\n }\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(data.cell));\r\n return next;\r\n });\r\n };\r\n\r\n adapter.on('CellLocked', lockHandler);\r\n adapter.on('CellUnlocked', unlockHandler);\r\n\r\n return () => {\r\n adapter.off('CellLocked', lockHandler);\r\n adapter.off('CellUnlocked', unlockHandler);\r\n };\r\n }, [adapter, enableCellLocking, entityType]);\r\n\r\n // Clean up expired locks\r\n useEffect(() => {\r\n if (!enableCellLocking) {\r\n return;\r\n }\r\n\r\n const interval = setInterval(() => {\r\n const now = Date.now();\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n let changed = false;\r\n for (const [key, lock] of next) {\r\n if (now - lock.timestamp > lockTimeout) {\r\n next.delete(key);\r\n changed = true;\r\n }\r\n }\r\n return changed ? next : prev;\r\n });\r\n }, 10000);\r\n\r\n return () => clearInterval(interval);\r\n }, [enableCellLocking, lockTimeout]);\r\n\r\n // Check if a cell is locked\r\n const isCellLocked = useCallback(\r\n (cell: CellPosition): boolean => {\r\n if (!enableCellLocking) {\r\n return false;\r\n }\r\n const lock = cellLocks.get(cellKey(cell));\r\n if (!lock) {\r\n return false;\r\n }\r\n return lock.user.id !== currentUser.id && Date.now() - lock.timestamp < lockTimeout;\r\n },\r\n [enableCellLocking, cellLocks, currentUser.id, lockTimeout],\r\n );\r\n\r\n // Get the user editing a cell\r\n const getCellEditor = useCallback(\r\n (cell: CellPosition): PresenceUser | null => {\r\n const lock = cellLocks.get(cellKey(cell));\r\n if (!lock) {\r\n return null;\r\n }\r\n if (Date.now() - lock.timestamp > lockTimeout) {\r\n return null;\r\n }\r\n return lock.user;\r\n },\r\n [cellLocks, lockTimeout],\r\n );\r\n\r\n // Start editing a cell\r\n const startCellEdit = useCallback(\r\n (cell: CellPosition): boolean => {\r\n if (isCellLocked(cell)) {\r\n return false;\r\n }\r\n\r\n setEditingCell(cell);\r\n presence.startEditing(`${cell.rowKey}:${cell.field}`);\r\n\r\n // Acquire lock\r\n if (enableCellLocking && adapter.status === 'connected') {\r\n adapter\r\n .invoke('AcquireCellLock', {\r\n entityType,\r\n cell,\r\n user: currentUser,\r\n timestamp: Date.now(),\r\n })\r\n .catch(() => {});\r\n\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.set(cellKey(cell), { user: currentUser, timestamp: Date.now() });\r\n return next;\r\n });\r\n }\r\n\r\n return true;\r\n },\r\n [isCellLocked, presence, enableCellLocking, adapter, entityType, currentUser],\r\n );\r\n\r\n // Commit a cell edit\r\n const commitCellEdit = useCallback(\r\n async (cell: CellPosition, newValue: unknown): Promise<boolean> => {\r\n // Find current row\r\n const row = liveData.data.find((r) => r[keyField] === cell.rowKey);\r\n if (!row) {\r\n return false;\r\n }\r\n\r\n const oldValue = row[cell.field as keyof T];\r\n\r\n // Check before commit callback\r\n if (onBeforeCommit) {\r\n const allowed = await onBeforeCommit(cell, oldValue, newValue);\r\n if (!allowed) {\r\n cancelCellEdit(cell);\r\n return false;\r\n }\r\n }\r\n\r\n // Store pending edit\r\n const edit: PendingEdit = {\r\n cell,\r\n originalValue: oldValue,\r\n newValue,\r\n timestamp: Date.now(),\r\n };\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.set(cellKey(cell), edit);\r\n return next;\r\n });\r\n\r\n // Optimistic update\r\n if (optimisticUpdates) {\r\n liveData.registerPendingChange(cell.rowKey, { [cell.field]: newValue } as Partial<T>);\r\n }\r\n\r\n // Send to server\r\n try {\r\n await adapter.invoke('UpdateEntityField', {\r\n entityType,\r\n entityId: cell.rowKey,\r\n field: cell.field,\r\n value: newValue,\r\n userId: currentUser.id,\r\n timestamp: edit.timestamp,\r\n });\r\n\r\n liveData.confirmPendingChange(cell.rowKey);\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n\r\n onAfterCommit?.(cell, newValue);\r\n\r\n // Release lock\r\n if (enableCellLocking) {\r\n adapter.invoke('ReleaseCellLock', { entityType, cell }).catch(() => {});\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n }\r\n\r\n setEditingCell(null);\r\n presence.stopEditing();\r\n\r\n return true;\r\n } catch (error) {\r\n // Rollback on error\r\n liveData.rollbackPendingChange(cell.rowKey);\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n\r\n throw error;\r\n }\r\n },\r\n [\r\n liveData,\r\n keyField,\r\n onBeforeCommit,\r\n optimisticUpdates,\r\n adapter,\r\n entityType,\r\n currentUser.id,\r\n onAfterCommit,\r\n enableCellLocking,\r\n presence,\r\n ],\r\n );\r\n\r\n // Cancel a cell edit\r\n const cancelCellEdit = useCallback(\r\n (cell: CellPosition) => {\r\n setEditingCell(null);\r\n presence.stopEditing();\r\n\r\n // Release lock\r\n if (enableCellLocking && adapter.status === 'connected') {\r\n adapter.invoke('ReleaseCellLock', { entityType, cell }).catch(() => {});\r\n setCellLocks((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n }\r\n\r\n // Remove pending edit\r\n setPendingEdits((prev) => {\r\n const next = new Map(prev);\r\n next.delete(cellKey(cell));\r\n return next;\r\n });\r\n },\r\n [presence, enableCellLocking, adapter, entityType],\r\n );\r\n\r\n // Resolve a conflict\r\n const resolveConflict = useCallback(\r\n (cell: CellPosition, resolution: 'local' | 'remote' | 'merge') => {\r\n const conflict = conflicts.find((c) => cellKey(c.cell) === cellKey(cell));\r\n if (!conflict) {\r\n return;\r\n }\r\n\r\n if (resolution === 'remote') {\r\n // Apply remote value\r\n liveData.applyRemoteUpdate({\r\n operation: 'update',\r\n entityName: entityType,\r\n key: cell.rowKey,\r\n changes: { [cell.field]: conflict.remoteValue } as Partial<T>,\r\n });\r\n }\r\n // For \"local\" and \"merge\", keep current value\r\n\r\n // Remove from conflicts\r\n setConflicts((prev) => prev.filter((c) => cellKey(c.cell) !== cellKey(cell)));\r\n },\r\n [conflicts, liveData, entityType],\r\n );\r\n\r\n // Row operations\r\n const insertRow = useCallback(\r\n async (data: Partial<T>): Promise<void> => {\r\n await adapter.invoke('InsertEntity', entityType, data);\r\n },\r\n [adapter, entityType],\r\n );\r\n\r\n const updateRow = useCallback(\r\n async (key: unknown, changes: Partial<T>): Promise<void> => {\r\n if (optimisticUpdates) {\r\n liveData.registerPendingChange(key, changes);\r\n }\r\n try {\r\n await adapter.invoke('UpdateEntity', entityType, key, changes);\r\n liveData.confirmPendingChange(key);\r\n } catch (error) {\r\n liveData.rollbackPendingChange(key);\r\n throw error;\r\n }\r\n },\r\n [adapter, entityType, optimisticUpdates, liveData],\r\n );\r\n\r\n const deleteRow = useCallback(\r\n async (key: unknown): Promise<void> => {\r\n await adapter.invoke('DeleteEntity', entityType, key);\r\n },\r\n [adapter, entityType],\r\n );\r\n\r\n // Build my selection\r\n const mySelection: UserSelection = useMemo(\r\n () => ({\r\n user: currentUser,\r\n focusedCell,\r\n selectedCells,\r\n selectedRows,\r\n editingCell,\r\n color: getUserColor(currentUser.id),\r\n }),\r\n [currentUser, focusedCell, selectedCells, selectedRows, editingCell, getUserColor],\r\n );\r\n\r\n return {\r\n data: liveData.data,\r\n presence: presence.presence,\r\n userSelections,\r\n mySelection,\r\n flashedKeys: liveData.flashedKeys,\r\n pendingEdits,\r\n connectionStatus: liveData.status,\r\n myPresenceStatus: presence.myStatus,\r\n\r\n // Selection methods\r\n setFocusedCell,\r\n setSelectedCells,\r\n setSelectedRows,\r\n\r\n // Edit methods\r\n startCellEdit,\r\n commitCellEdit,\r\n cancelCellEdit,\r\n isCellLocked,\r\n getCellEditor,\r\n getUserColor,\r\n\r\n // Row methods\r\n insertRow,\r\n updateRow,\r\n deleteRow,\r\n\r\n // Conflict handling\r\n resolveConflict,\r\n conflicts,\r\n };\r\n}\r\n\r\nexport default useCollaborativeDataGrid;\r\n","/**\r\n * ErpAuthAdapter — connects @nice2dev/auth components (LoginForm, RoleGuard, etc.)\r\n * to OmniVerk's JWT-based authentication API.\r\n */\r\n\r\nimport type { ErpLoginRequest, ErpLoginResponse, ErpUser, ErpResponse } from './types';\r\n\r\nexport interface ErpAuthAdapterConfig {\r\n /** Auth API base URL, e.g. \"/api/auth\". */\r\n baseUrl: string;\r\n fetch?: typeof fetch;\r\n /** Called when a new token pair is obtained. */\r\n onTokens?: (access: string, refresh: string) => void;\r\n}\r\n\r\nexport class ErpAuthAdapter {\r\n private cfg: ErpAuthAdapterConfig;\r\n private accessToken: string | null = null;\r\n private refreshToken: string | null = null;\r\n\r\n constructor(config: ErpAuthAdapterConfig) {\r\n this.cfg = config;\r\n }\r\n\r\n /** Current access token (or null). */\r\n getAccessToken(): string | null {\r\n return this.accessToken;\r\n }\r\n\r\n /** Token factory for use with ErpDataAdapter.tokenFactory. */\r\n tokenFactory = async (): Promise<string> => {\r\n if (!this.accessToken) {\r\n throw new Error('Not authenticated');\r\n }\r\n return this.accessToken;\r\n };\r\n\r\n private async post<R>(path: string, body: unknown): Promise<R> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}${path}`, {\r\n method: 'POST',\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify(body),\r\n });\r\n if (!res.ok) {\r\n throw new Error(`Auth request failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<R>;\r\n }\r\n\r\n /** Login with username/password. */\r\n async login(req: ErpLoginRequest): Promise<ErpLoginResponse> {\r\n const result = await this.post<ErpLoginResponse>('/login', req);\r\n if (result.token) {\r\n this.accessToken = result.token;\r\n this.refreshToken = result.refreshToken ?? null;\r\n this.cfg.onTokens?.(result.token, result.refreshToken ?? '');\r\n }\r\n return result;\r\n }\r\n\r\n /** Refresh the access token. */\r\n async refresh(): Promise<ErpLoginResponse> {\r\n if (!this.refreshToken) {\r\n throw new Error('No refresh token');\r\n }\r\n const result = await this.post<ErpLoginResponse>('/refresh', {\r\n refreshToken: this.refreshToken,\r\n });\r\n if (result.token) {\r\n this.accessToken = result.token;\r\n this.refreshToken = result.refreshToken ?? this.refreshToken;\r\n this.cfg.onTokens?.(result.token, this.refreshToken!);\r\n }\r\n return result;\r\n }\r\n\r\n /** Get current user info. */\r\n async me(): Promise<ErpResponse<ErpUser>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/me`, {\r\n headers: { Authorization: `Bearer ${this.accessToken}` },\r\n });\r\n if (!res.ok) {\r\n throw new Error(`Auth /me failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<ErpResponse<ErpUser>>;\r\n }\r\n\r\n /** Logout (invalidate tokens on server). */\r\n async logout(): Promise<void> {\r\n try {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n await f(`${this.cfg.baseUrl}/logout`, {\r\n method: 'POST',\r\n headers: { Authorization: `Bearer ${this.accessToken}` },\r\n });\r\n } finally {\r\n this.accessToken = null;\r\n this.refreshToken = null;\r\n }\r\n }\r\n}\r\n","/**\r\n * ErpFileAdapter — handles file uploads / downloads / management\r\n * against OmniVerk's blob-storage API.\r\n */\r\n\r\nimport type { ErpResponse, ErpFileInfo } from './types';\r\n\r\nexport interface ErpFileAdapterConfig {\r\n /** File API base URL, e.g. \"/api/files\". */\r\n baseUrl: string;\r\n fetch?: typeof fetch;\r\n tokenFactory?: () => string | Promise<string>;\r\n}\r\n\r\n/** Progress callback for upload tracking. */\r\nexport type UploadProgressCallback = (event: UploadProgressEvent) => void;\r\n\r\nexport interface UploadProgressEvent {\r\n /** Bytes uploaded so far. */\r\n loaded: number;\r\n /** Total bytes (0 if unknown). */\r\n total: number;\r\n /** Progress as 0–1 fraction (NaN if total unknown). */\r\n percent: number;\r\n}\r\n\r\nexport class ErpFileAdapter {\r\n private cfg: ErpFileAdapterConfig;\r\n\r\n constructor(config: ErpFileAdapterConfig) {\r\n this.cfg = config;\r\n }\r\n\r\n private async headers(): Promise<Record<string, string>> {\r\n const h: Record<string, string> = {};\r\n if (this.cfg.tokenFactory) {\r\n h['Authorization'] = `Bearer ${await this.cfg.tokenFactory()}`;\r\n }\r\n return h;\r\n }\r\n\r\n /** Upload a file. Returns file metadata on success. */\r\n async upload(file: File, folder?: string): Promise<ErpResponse<ErpFileInfo>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const form = new FormData();\r\n form.append('file', file);\r\n if (folder) {\r\n form.append('folder', folder);\r\n }\r\n const res = await f(this.cfg.baseUrl, {\r\n method: 'POST',\r\n headers: await this.headers(),\r\n body: form,\r\n });\r\n if (!res.ok) {\r\n throw new Error(`File upload failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<ErpResponse<ErpFileInfo>>;\r\n }\r\n\r\n /** Upload multiple files. */\r\n async uploadMultiple(files: File[], folder?: string): Promise<ErpResponse<ErpFileInfo[]>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const form = new FormData();\r\n files.forEach((file) => form.append('files', file));\r\n if (folder) {\r\n form.append('folder', folder);\r\n }\r\n const res = await f(`${this.cfg.baseUrl}/batch`, {\r\n method: 'POST',\r\n headers: await this.headers(),\r\n body: form,\r\n });\r\n if (!res.ok) {\r\n throw new Error(`Batch upload failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<ErpResponse<ErpFileInfo[]>>;\r\n }\r\n\r\n /** Get file metadata by id. */\r\n async getInfo(id: string): Promise<ErpResponse<ErpFileInfo>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/${id}/info`, {\r\n headers: { ...(await this.headers()), 'Content-Type': 'application/json' },\r\n });\r\n if (!res.ok) {\r\n throw new Error(`File info failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<ErpResponse<ErpFileInfo>>;\r\n }\r\n\r\n /** Get a download URL (or blob URL) for a file. */\r\n async getDownloadUrl(id: string): Promise<string> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/${id}`, {\r\n headers: await this.headers(),\r\n });\r\n if (!res.ok) {\r\n throw new Error(`File download failed: ${res.status}`);\r\n }\r\n const blob = await res.blob();\r\n return URL.createObjectURL(blob);\r\n }\r\n\r\n /** Delete file by id. */\r\n async remove(id: string): Promise<ErpResponse<void>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(`${this.cfg.baseUrl}/${id}`, {\r\n method: 'DELETE',\r\n headers: { ...(await this.headers()), 'Content-Type': 'application/json' },\r\n });\r\n if (!res.ok) {\r\n throw new Error(`File delete failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<ErpResponse<void>>;\r\n }\r\n\r\n /**\r\n * Upload a file with progress tracking via XMLHttpRequest.\r\n * Falls back to standard fetch-based upload if XHR is unavailable.\r\n */\r\n async uploadWithProgress(\r\n file: File,\r\n onProgress: UploadProgressCallback,\r\n folder?: string,\r\n signal?: AbortSignal,\r\n ): Promise<ErpResponse<ErpFileInfo>> {\r\n if (typeof XMLHttpRequest === 'undefined') {\r\n return this.upload(file, folder);\r\n }\r\n\r\n const headers = await this.headers();\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest();\r\n const form = new FormData();\r\n form.append('file', file);\r\n if (folder) {\r\n form.append('folder', folder);\r\n }\r\n\r\n // Abort support\r\n if (signal) {\r\n if (signal.aborted) {\r\n reject(new DOMException('Aborted', 'AbortError'));\r\n return;\r\n }\r\n signal.addEventListener('abort', () => {\r\n xhr.abort();\r\n reject(new DOMException('Aborted', 'AbortError'));\r\n });\r\n }\r\n\r\n xhr.upload.addEventListener('progress', (e) => {\r\n onProgress({\r\n loaded: e.loaded,\r\n total: e.total,\r\n percent: e.lengthComputable ? e.loaded / e.total : NaN,\r\n });\r\n });\r\n\r\n xhr.addEventListener('load', () => {\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n try {\r\n resolve(JSON.parse(xhr.responseText));\r\n } catch {\r\n reject(new Error('Invalid JSON response'));\r\n }\r\n } else {\r\n reject(new Error(`File upload failed: ${xhr.status}`));\r\n }\r\n });\r\n\r\n xhr.addEventListener('error', () => reject(new Error('Network error during upload')));\r\n xhr.addEventListener('abort', () => reject(new DOMException('Aborted', 'AbortError')));\r\n\r\n xhr.open('POST', this.cfg.baseUrl);\r\n Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v));\r\n xhr.send(form);\r\n });\r\n }\r\n\r\n /**\r\n * Upload multiple files with per-file progress tracking.\r\n * Returns progress for each file individually.\r\n */\r\n async uploadMultipleWithProgress(\r\n files: File[],\r\n onProgress: (fileIndex: number, event: UploadProgressEvent) => void,\r\n folder?: string,\r\n signal?: AbortSignal,\r\n ): Promise<ErpResponse<ErpFileInfo>[]> {\r\n const results: ErpResponse<ErpFileInfo>[] = [];\r\n for (let i = 0; i < files.length; i++) {\r\n if (signal?.aborted) {\r\n throw new DOMException('Aborted', 'AbortError');\r\n }\r\n const res = await this.uploadWithProgress(\r\n files[i],\r\n (evt) => onProgress(i, evt),\r\n folder,\r\n signal,\r\n );\r\n results.push(res);\r\n }\r\n return results;\r\n }\r\n}\r\n","/**\r\n * ErpExportAdapter — connects NiceDataGrid export actions to OmniVerk's\r\n * server-side report engine (xlsx, pdf, csv, json, xml).\r\n */\r\n\r\nimport type { ErpExportRequest, ErpExportResponse, ErpResponse } from './types';\r\n\r\nexport interface ErpExportAdapterConfig {\r\n /** Export API base URL, e.g. \"/api/export\". */\r\n baseUrl: string;\r\n fetch?: typeof fetch;\r\n tokenFactory?: () => string | Promise<string>;\r\n}\r\n\r\nexport class ErpExportAdapter {\r\n private cfg: ErpExportAdapterConfig;\r\n\r\n constructor(config: ErpExportAdapterConfig) {\r\n this.cfg = config;\r\n }\r\n\r\n private async headers(): Promise<Record<string, string>> {\r\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\r\n if (this.cfg.tokenFactory) {\r\n h['Authorization'] = `Bearer ${await this.cfg.tokenFactory()}`;\r\n }\r\n return h;\r\n }\r\n\r\n /** Request a server-side export. Returns a download URL / job id. */\r\n async requestExport(req: ErpExportRequest): Promise<ErpResponse<ErpExportResponse>> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(this.cfg.baseUrl, {\r\n method: 'POST',\r\n headers: await this.headers(),\r\n body: JSON.stringify(req),\r\n });\r\n if (!res.ok) {\r\n throw new Error(`Export request failed: ${res.status}`);\r\n }\r\n return res.json() as Promise<ErpResponse<ErpExportResponse>>;\r\n }\r\n\r\n /** Download an exported file as a Blob. */\r\n async download(downloadUrl: string): Promise<Blob> {\r\n const f = this.cfg.fetch ?? globalThis.fetch;\r\n const res = await f(downloadUrl, {\r\n headers: await this.headers(),\r\n });\r\n if (!res.ok) {\r\n throw new Error(`Export download failed: ${res.status}`);\r\n }\r\n return res.blob();\r\n }\r\n\r\n /** Trigger browser download for an export. */\r\n async downloadToFile(req: ErpExportRequest, filename?: string): Promise<void> {\r\n const result = await this.requestExport(req);\r\n if (!result.data?.downloadUrl) {\r\n throw new Error('No download URL returned');\r\n }\r\n const blob = await this.download(result.data.downloadUrl);\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n a.href = url;\r\n a.download = filename ?? `export.${req.format}`;\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n }\r\n}\r\n","/**\r\n * ErpMiddleware — composable request/response interceptor pipeline.\r\n *\r\n * Middleware functions wrap `fetch` calls, enabling logging, retry,\r\n * header injection, metrics, caching, etc. without modifying adapters.\r\n *\r\n * Usage:\r\n * const pipeline = createMiddlewarePipeline([\r\n * loggingMiddleware(),\r\n * retryMiddleware({ maxRetries: 3 }),\r\n * ]);\r\n * const adapter = new ErpDataAdapter({ baseUrl: '/api/products', fetch: pipeline(fetch) });\r\n */\r\n\r\n/** Request context available to middleware. */\r\nexport interface ErpRequestContext {\r\n url: string;\r\n init: RequestInit;\r\n /** Custom metadata bag — middleware can attach/read arbitrary keys. */\r\n meta: Record<string, unknown>;\r\n}\r\n\r\n/** A middleware function: receives context + next handler, returns Response. */\r\nexport type ErpMiddleware = (\r\n ctx: ErpRequestContext,\r\n next: (ctx: ErpRequestContext) => Promise<Response>,\r\n) => Promise<Response>;\r\n\r\n/**\r\n * Compose an array of middleware into a single `fetch`-compatible function.\r\n *\r\n * Middleware executes in order: first in the array wraps outermost.\r\n * The innermost call delegates to the real `fetch`.\r\n */\r\nexport function createMiddlewarePipeline(\r\n middlewares: ErpMiddleware[],\r\n): (baseFetch?: typeof fetch) => typeof fetch {\r\n return (baseFetch?: typeof fetch) => {\r\n const realFetch = baseFetch ?? globalThis.fetch;\r\n\r\n const wrappedFetch: typeof fetch = (input, init?) => {\r\n const url =\r\n typeof input === 'string'\r\n ? input\r\n : input instanceof URL\r\n ? input.toString()\r\n : (input as Request).url;\r\n const mergedInit: RequestInit = init ?? {};\r\n const ctx: ErpRequestContext = { url, init: mergedInit, meta: {} };\r\n\r\n // Build chain from right (innermost) to left (outermost)\r\n let handler = (c: ErpRequestContext): Promise<Response> => realFetch(c.url, c.init);\r\n\r\n for (let i = middlewares.length - 1; i >= 0; i--) {\r\n const mw = middlewares[i];\r\n const nextHandler = handler;\r\n handler = (c) => mw(c, nextHandler);\r\n }\r\n\r\n return handler(ctx);\r\n };\r\n\r\n return wrappedFetch;\r\n };\r\n}\r\n\r\n/* ── Built-in middleware ──────────────────────────────────────── */\r\n\r\n/** Logging middleware — logs request/response timing to console. */\r\nexport function loggingMiddleware(): ErpMiddleware {\r\n return async (ctx, next) => {\r\n const start = performance.now();\r\n const method = (ctx.init.method ?? 'GET').toUpperCase();\r\n try {\r\n const res = await next(ctx);\r\n const ms = (performance.now() - start).toFixed(1);\r\n console.debug(`[ERP] ${method} ${ctx.url} → ${res.status} (${ms}ms)`);\r\n return res;\r\n } catch (err) {\r\n const ms = (performance.now() - start).toFixed(1);\r\n console.error(`[ERP] ${method} ${ctx.url} FAILED (${ms}ms)`, err);\r\n throw err;\r\n }\r\n };\r\n}\r\n\r\nexport interface RetryOptions {\r\n /** Maximum number of retry attempts (default: 3). */\r\n maxRetries?: number;\r\n /** Base delay in ms for exponential backoff (default: 500). */\r\n baseDelay?: number;\r\n /** HTTP status codes that trigger retry (default: [408, 429, 500, 502, 503, 504]). */\r\n retryOn?: number[];\r\n}\r\n\r\n/** Retry middleware with exponential backoff for transient failures. */\r\nexport function retryMiddleware(opts?: RetryOptions): ErpMiddleware {\r\n const maxRetries = opts?.maxRetries ?? 3;\r\n const baseDelay = opts?.baseDelay ?? 500;\r\n const retryOn = new Set(opts?.retryOn ?? [408, 429, 500, 502, 503, 504]);\r\n\r\n return async (ctx, next) => {\r\n let lastError: unknown;\r\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\r\n try {\r\n const res = await next(ctx);\r\n if (res.ok || !retryOn.has(res.status) || attempt === maxRetries) {\r\n return res;\r\n }\r\n // Transient error — will retry\r\n lastError = new Error(`HTTP ${res.status}`);\r\n } catch (err) {\r\n lastError = err;\r\n if (attempt === maxRetries) {\r\n throw err;\r\n }\r\n }\r\n // Exponential backoff with jitter\r\n const delay = baseDelay * Math.pow(2, attempt) * (0.5 + Math.random() * 0.5);\r\n await new Promise((r) => setTimeout(r, delay));\r\n }\r\n throw lastError;\r\n };\r\n}\r\n\r\n/** Header injection middleware — merges extra headers into every request. */\r\nexport function headerMiddleware(\r\n headers:\r\n | Record<string, string>\r\n | (() => Record<string, string> | Promise<Record<string, string>>),\r\n): ErpMiddleware {\r\n return async (ctx, next) => {\r\n const extra = typeof headers === 'function' ? await headers() : headers;\r\n ctx.init.headers = { ...(ctx.init.headers as Record<string, string>), ...extra };\r\n return next(ctx);\r\n };\r\n}\r\n","/**\r\n * ErpRateLimiter — client-side rate limiting / throttling for ERP requests.\r\n *\r\n * Wraps fetch to enforce:\r\n * - Maximum concurrent requests\r\n * - Sliding window request rate\r\n * - Automatic 429 back-off\r\n *\r\n * Usage:\r\n * const limiter = new ErpRateLimiter({ maxConcurrent: 4, maxPerSecond: 10 });\r\n * const adapter = new ErpDataAdapter({ baseUrl: '/api/products', fetch: limiter.fetch });\r\n */\r\n\r\nexport interface ErpRateLimiterConfig {\r\n /** Maximum concurrent in-flight requests (default: 6). */\r\n maxConcurrent?: number;\r\n /** Maximum requests per second (default: 20). */\r\n maxPerSecond?: number;\r\n /** Delay (ms) to wait after receiving a 429 response (default: 2000). */\r\n backoffDelay?: number;\r\n /** Base fetch implementation (default: globalThis.fetch). */\r\n baseFetch?: typeof fetch;\r\n}\r\n\r\ninterface QueueEntry {\r\n resolve: (value: Response | PromiseLike<Response>) => void;\r\n reject: (reason?: unknown) => void;\r\n input: RequestInfo | URL;\r\n init?: RequestInit;\r\n}\r\n\r\nexport class ErpRateLimiter {\r\n private maxConcurrent: number;\r\n private maxPerSecond: number;\r\n private backoffDelay: number;\r\n private baseFetch: typeof fetch;\r\n\r\n private inFlight = 0;\r\n private timestamps: number[] = [];\r\n private queue: QueueEntry[] = [];\r\n private processing = false;\r\n private backingOff = false;\r\n\r\n constructor(config?: ErpRateLimiterConfig) {\r\n this.maxConcurrent = config?.maxConcurrent ?? 6;\r\n this.maxPerSecond = config?.maxPerSecond ?? 20;\r\n this.backoffDelay = config?.backoffDelay ?? 2000;\r\n this.baseFetch = config?.baseFetch ?? globalThis.fetch.bind(globalThis);\r\n\r\n // Bind fetch so it can be passed as a plain function\r\n this.fetch = this.fetch.bind(this);\r\n }\r\n\r\n /** A fetch-compatible function that respects rate limits. */\r\n fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\r\n return new Promise<Response>((resolve, reject) => {\r\n this.queue.push({ resolve, reject, input, init });\r\n this.drain();\r\n });\r\n }\r\n\r\n /** Number of requests currently waiting in the queue. */\r\n get pending(): number {\r\n return this.queue.length;\r\n }\r\n\r\n /** Number of requests currently in flight. */\r\n get active(): number {\r\n return this.inFlight;\r\n }\r\n\r\n private drain(): void {\r\n if (this.processing) {\r\n return;\r\n }\r\n this.processing = true;\r\n\r\n const tryNext = () => {\r\n if (this.queue.length === 0 || this.backingOff) {\r\n this.processing = false;\r\n return;\r\n }\r\n\r\n // Enforce concurrent limit\r\n if (this.inFlight >= this.maxConcurrent) {\r\n this.processing = false;\r\n return;\r\n }\r\n\r\n // Enforce rate limit (sliding window of 1 second)\r\n const now = Date.now();\r\n this.timestamps = this.timestamps.filter((t) => now - t < 1000);\r\n if (this.timestamps.length >= this.maxPerSecond) {\r\n const oldestInWindow = this.timestamps[0];\r\n const waitMs = 1000 - (now - oldestInWindow) + 1;\r\n setTimeout(() => {\r\n this.processing = false;\r\n this.drain();\r\n }, waitMs);\r\n return;\r\n }\r\n\r\n const entry = this.queue.shift()!;\r\n this.inFlight++;\r\n this.timestamps.push(now);\r\n\r\n this.baseFetch(entry.input, entry.init)\r\n .then((res) => {\r\n this.inFlight--;\r\n if (res.status === 429) {\r\n // Re-queue and back off\r\n this.queue.unshift(entry);\r\n this.inFlight++; // undo: we didn't really complete\r\n this.inFlight--;\r\n this.backingOff = true;\r\n setTimeout(() => {\r\n this.backingOff = false;\r\n this.drain();\r\n }, this.backoffDelay);\r\n return;\r\n }\r\n entry.resolve(res);\r\n // Immediately attempt next\r\n queueMicrotask(tryNext);\r\n })\r\n .catch((err) => {\r\n this.inFlight--;\r\n entry.reject(err);\r\n queueMicrotask(tryNext);\r\n });\r\n\r\n // Try to launch more concurrent requests\r\n queueMicrotask(tryNext);\r\n };\r\n\r\n tryNext();\r\n }\r\n}\r\n","/**\r\n * ErpOptimisticStore — client-side optimistic mutation store.\r\n *\r\n * Applies mutations optimistically (instant UI update) and rolls back\r\n * if the server request fails. Works with any ErpDataAdapter.\r\n *\r\n * Usage:\r\n * const store = new ErpOptimisticStore(adapter);\r\n * const items = store.getItems(); // reactive snapshot\r\n * await store.create({ name: 'New' }); // instant add, confirmed by server\r\n * await store.update('42', { name: 'Edit' }); // instant change, rolled back on error\r\n */\r\n\r\nimport type { ErpDataAdapter } from './ErpDataAdapter';\r\nimport type { ErpResponse } from './types';\r\n\r\n/** Pending mutation waiting for server confirmation. */\r\nexport interface PendingMutation<T = unknown> {\r\n id: string;\r\n type: 'create' | 'update' | 'delete';\r\n entityId?: string | number;\r\n optimisticData?: T;\r\n previousData?: T;\r\n timestamp: number;\r\n status: 'pending' | 'confirmed' | 'failed';\r\n error?: string;\r\n}\r\n\r\nexport type OptimisticListener<T> = (items: T[], pending: PendingMutation<T>[]) => void;\r\n\r\nexport class ErpOptimisticStore<T extends Record<string, unknown> = Record<string, unknown>> {\r\n private adapter: ErpDataAdapter<T>;\r\n private items: T[] = [];\r\n private keyField: string;\r\n private mutations: PendingMutation<T>[] = [];\r\n private listeners = new Set<OptimisticListener<T>>();\r\n private nextMutationId = 1;\r\n\r\n constructor(adapter: ErpDataAdapter<T>, keyField = 'id') {\r\n this.adapter = adapter;\r\n this.keyField = keyField;\r\n }\r\n\r\n /** Subscribe to state changes. Returns unsubscribe function. */\r\n subscribe(listener: OptimisticListener<T>): () => void {\r\n this.listeners.add(listener);\r\n return () => this.listeners.delete(listener);\r\n }\r\n\r\n private notify(): void {\r\n const snapshot = [...this.items];\r\n const pending = [...this.mutations];\r\n this.listeners.forEach((fn) => fn(snapshot, pending));\r\n }\r\n\r\n /** Current items including optimistic (unconfirmed) changes. */\r\n getItems(): T[] {\r\n return [...this.items];\r\n }\r\n\r\n /** Pending mutations not yet confirmed by server. */\r\n getPending(): PendingMutation<T>[] {\r\n return this.mutations.filter((m) => m.status === 'pending');\r\n }\r\n\r\n /** Set initial data (e.g. after load()). */\r\n setItems(items: T[]): void {\r\n this.items = [...items];\r\n this.notify();\r\n }\r\n\r\n /** Load data from server and replace local state. */\r\n async load(...args: Parameters<ErpDataAdapter<T>['load']>): Promise<ErpResponse<T[]>> {\r\n const res = await this.adapter.load(...args);\r\n if (res.success) {\r\n this.items = res.data;\r\n this.mutations = [];\r\n this.notify();\r\n }\r\n return res;\r\n }\r\n\r\n /** Optimistically create an item. */\r\n async create(item: Partial<T>): Promise<ErpResponse<T>> {\r\n const tempKey = `__temp_${this.nextMutationId}`;\r\n const optimistic = { ...item, [this.keyField]: tempKey } as T;\r\n const mutId = String(this.nextMutationId++);\r\n const mutation: PendingMutation<T> = {\r\n id: mutId,\r\n type: 'create',\r\n optimisticData: optimistic,\r\n timestamp: Date.now(),\r\n status: 'pending',\r\n };\r\n\r\n // Apply optimistically\r\n this.items = [...this.items, optimistic];\r\n this.mutations.push(mutation);\r\n this.notify();\r\n\r\n try {\r\n const res = await this.adapter.create(item);\r\n if (res.success) {\r\n // Replace temp item with server-confirmed item\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === tempKey ? res.data : it,\r\n );\r\n mutation.status = 'confirmed';\r\n mutation.entityId = (res.data as Record<string, unknown>)[this.keyField] as string;\r\n } else {\r\n // Rollback\r\n this.items = this.items.filter(\r\n (it) => (it as Record<string, unknown>)[this.keyField] !== tempKey,\r\n );\r\n mutation.status = 'failed';\r\n mutation.error = res.error;\r\n }\r\n this.notify();\r\n return res;\r\n } catch (err) {\r\n // Rollback on network error\r\n this.items = this.items.filter(\r\n (it) => (it as Record<string, unknown>)[this.keyField] !== tempKey,\r\n );\r\n mutation.status = 'failed';\r\n mutation.error = err instanceof Error ? err.message : String(err);\r\n this.notify();\r\n throw err;\r\n }\r\n }\r\n\r\n /** Optimistically update an item. */\r\n async update(id: string | number, changes: Partial<T>): Promise<ErpResponse<T>> {\r\n const idx = this.items.findIndex((it) => (it as Record<string, unknown>)[this.keyField] === id);\r\n const previous = idx >= 0 ? { ...this.items[idx] } : undefined;\r\n const mutId = String(this.nextMutationId++);\r\n const mutation: PendingMutation<T> = {\r\n id: mutId,\r\n type: 'update',\r\n entityId: id,\r\n previousData: previous,\r\n timestamp: Date.now(),\r\n status: 'pending',\r\n };\r\n\r\n // Apply optimistically\r\n if (idx >= 0) {\r\n this.items = this.items.map((it, i) => (i === idx ? { ...it, ...changes } : it));\r\n }\r\n this.mutations.push(mutation);\r\n this.notify();\r\n\r\n try {\r\n const res = await this.adapter.update(id, changes);\r\n if (res.success) {\r\n // Replace with server version\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === id ? res.data : it,\r\n );\r\n mutation.status = 'confirmed';\r\n } else {\r\n // Rollback\r\n if (previous && idx >= 0) {\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === id ? previous : it,\r\n );\r\n }\r\n mutation.status = 'failed';\r\n mutation.error = res.error;\r\n }\r\n this.notify();\r\n return res;\r\n } catch (err) {\r\n // Rollback\r\n if (previous && idx >= 0) {\r\n this.items = this.items.map((it) =>\r\n (it as Record<string, unknown>)[this.keyField] === id ? previous : it,\r\n );\r\n }\r\n mutation.status = 'failed';\r\n mutation.error = err instanceof Error ? err.message : String(err);\r\n this.notify();\r\n throw err;\r\n }\r\n }\r\n\r\n /** Optimistically delete an item. */\r\n async remove(id: string | number): Promise<ErpResponse<void>> {\r\n const idx = this.items.findIndex((it) => (it as Record<string, unknown>)[this.keyField] === id);\r\n const previous = idx >= 0 ? { ...this.items[idx] } : undefined;\r\n const mutId = String(this.nextMutationId++);\r\n const mutation: PendingMutation<T> = {\r\n id: mutId,\r\n type: 'delete',\r\n entityId: id,\r\n previousData: previous,\r\n timestamp: Date.now(),\r\n status: 'pending',\r\n };\r\n\r\n // Apply optimistically — remove immediately\r\n this.items = this.items.filter((it) => (it as Record<string, unknown>)[this.keyField] !== id);\r\n this.mutations.push(mutation);\r\n this.notify();\r\n\r\n try {\r\n const res = await this.adapter.remove(id);\r\n if (res.success) {\r\n mutation.status = 'confirmed';\r\n } else {\r\n // Rollback — re-insert\r\n if (previous) {\r\n this.items.splice(idx, 0, previous);\r\n }\r\n mutation.status = 'failed';\r\n mutation.error = res.error;\r\n }\r\n this.notify();\r\n return res;\r\n } catch (err) {\r\n if (previous) {\r\n this.items.splice(idx, 0, previous);\r\n }\r\n mutation.status = 'failed';\r\n mutation.error = err instanceof Error ? err.message : String(err);\r\n this.notify();\r\n throw err;\r\n }\r\n }\r\n}\r\n","/**\r\n * ErpOfflineQueue — queues requests when offline and replays them on reconnect.\r\n *\r\n * Wraps fetch to detect navigator.onLine. When offline, mutations (POST/PUT/PATCH/DELETE)\r\n * are stored in an IndexedDB-backed queue and replayed in order when connectivity returns.\r\n *\r\n * GET requests while offline reject immediately (or return cached data if cacheGets is true).\r\n *\r\n * Usage:\r\n * const queue = new ErpOfflineQueue({ onSync: (results) => console.log('Synced', results) });\r\n * const adapter = new ErpDataAdapter({ baseUrl: '/api/products', fetch: queue.fetch });\r\n */\r\n\r\nexport interface ErpOfflineQueueConfig {\r\n /** Base fetch to use when online (default: globalThis.fetch). */\r\n baseFetch?: typeof fetch;\r\n /** Called when the queue finishes replaying after reconnect. */\r\n onSync?: (results: SyncResult[]) => void;\r\n /** Called when a request is queued while offline. */\r\n onQueued?: (entry: QueueEntry) => void;\r\n /** Called on online/offline status change. */\r\n onStatusChange?: (online: boolean) => void;\r\n /** Storage key prefix for persistence (default: \"erp_offline_\"). */\r\n storageKey?: string;\r\n}\r\n\r\nexport interface QueueEntry {\r\n id: string;\r\n url: string;\r\n method: string;\r\n headers: Record<string, string>;\r\n body?: string;\r\n timestamp: number;\r\n}\r\n\r\nexport interface SyncResult {\r\n entry: QueueEntry;\r\n success: boolean;\r\n status?: number;\r\n error?: string;\r\n}\r\n\r\nexport class ErpOfflineQueue {\r\n private baseFetch: typeof fetch;\r\n private queue: QueueEntry[] = [];\r\n private config: ErpOfflineQueueConfig;\r\n private syncing = false;\r\n private storageKey: string;\r\n private nextId = 1;\r\n private boundOnline: () => void;\r\n private boundOffline: () => void;\r\n\r\n constructor(config?: ErpOfflineQueueConfig) {\r\n this.config = config ?? {};\r\n this.baseFetch = config?.baseFetch ?? globalThis.fetch.bind(globalThis);\r\n this.storageKey = config?.storageKey ?? \"erp_offline_\";\r\n\r\n this.fetch = this.fetch.bind(this);\r\n\r\n // Restore persisted queue\r\n this.restoreQueue();\r\n\r\n // Listen for online/offline events\r\n this.boundOnline = () => {\r\n this.config.onStatusChange?.(true);\r\n this.sync();\r\n };\r\n this.boundOffline = () => {\r\n this.config.onStatusChange?.(false);\r\n };\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.addEventListener(\"online\", this.boundOnline);\r\n window.addEventListener(\"offline\", this.boundOffline);\r\n }\r\n }\r\n\r\n /** A fetch-compatible function that queues mutations when offline. */\r\n fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\r\n const url = typeof input === \"string\" ? input : input instanceof URL ? input.toString() : (input as Request).url;\r\n const method = (init?.method ?? \"GET\").toUpperCase();\r\n\r\n // If online, use real fetch\r\n if (this.isOnline()) {\r\n return this.baseFetch(input, init);\r\n }\r\n\r\n // Offline: GET requests fail immediately\r\n if (method === \"GET\" || method === \"HEAD\") {\r\n return Promise.reject(new Error(\"Offline: GET requests are not queued\"));\r\n }\r\n\r\n // Queue the mutation\r\n const entry: QueueEntry = {\r\n id: `q_${this.nextId++}`,\r\n url,\r\n method,\r\n headers: { ...(init?.headers as Record<string, string>) },\r\n body: typeof init?.body === \"string\" ? init.body : undefined,\r\n timestamp: Date.now(),\r\n };\r\n\r\n this.queue.push(entry);\r\n this.persistQueue();\r\n this.config.onQueued?.(entry);\r\n\r\n // Return a synthetic 202 Accepted response\r\n return Promise.resolve(new Response(\r\n JSON.stringify({ queued: true, queueId: entry.id }),\r\n { status: 202, headers: { \"Content-Type\": \"application/json\" } },\r\n ));\r\n }\r\n\r\n /** Is the browser currently online? */\r\n isOnline(): boolean {\r\n return typeof navigator === \"undefined\" || navigator.onLine;\r\n }\r\n\r\n /** Number of entries waiting to be synced. */\r\n get pendingCount(): number {\r\n return this.queue.length;\r\n }\r\n\r\n /** Get a snapshot of the current queue. */\r\n getQueue(): QueueEntry[] {\r\n return [...this.queue];\r\n }\r\n\r\n /** Clear all pending entries without syncing. */\r\n clear(): void {\r\n this.queue = [];\r\n this.persistQueue();\r\n }\r\n\r\n /** Manually trigger sync (e.g. when you detect connectivity). */\r\n async sync(): Promise<SyncResult[]> {\r\n if (this.syncing || this.queue.length === 0 || !this.isOnline()) {\r\n return [];\r\n }\r\n this.syncing = true;\r\n\r\n const results: SyncResult[] = [];\r\n // Process in FIFO order\r\n while (this.queue.length > 0 && this.isOnline()) {\r\n const entry = this.queue[0];\r\n try {\r\n const res = await this.baseFetch(entry.url, {\r\n method: entry.method,\r\n headers: entry.headers,\r\n body: entry.body,\r\n });\r\n results.push({\r\n entry,\r\n success: res.ok,\r\n status: res.status,\r\n error: res.ok ? undefined : `HTTP ${res.status}`,\r\n });\r\n // Remove from queue regardless of status (don't retry 4xx)\r\n this.queue.shift();\r\n } catch (err) {\r\n results.push({\r\n entry,\r\n success: false,\r\n error: err instanceof Error ? err.message : String(err),\r\n });\r\n // Network error — stop syncing, keep entry for later retry\r\n break;\r\n }\r\n }\r\n\r\n this.persistQueue();\r\n this.syncing = false;\r\n if (results.length > 0) {\r\n this.config.onSync?.(results);\r\n }\r\n return results;\r\n }\r\n\r\n /** Remove event listeners. */\r\n destroy(): void {\r\n if (typeof window !== \"undefined\") {\r\n window.removeEventListener(\"online\", this.boundOnline);\r\n window.removeEventListener(\"offline\", this.boundOffline);\r\n }\r\n }\r\n\r\n private persistQueue(): void {\r\n try {\r\n if (typeof localStorage !== \"undefined\") {\r\n localStorage.setItem(\r\n `${this.storageKey}queue`,\r\n JSON.stringify(this.queue),\r\n );\r\n }\r\n } catch {\r\n // Storage full or unavailable — silently continue\r\n }\r\n }\r\n\r\n private restoreQueue(): void {\r\n try {\r\n if (typeof localStorage !== \"undefined\") {\r\n const raw = localStorage.getItem(`${this.storageKey}queue`);\r\n if (raw) {\r\n this.queue = JSON.parse(raw);\r\n this.nextId = this.queue.reduce(\r\n (max, e) => Math.max(max, parseInt(e.id.replace(\"q_\", \"\"), 10) || 0),\r\n 0,\r\n ) + 1;\r\n }\r\n }\r\n } catch {\r\n this.queue = [];\r\n }\r\n }\r\n}\r\n","/**\r\n * 5.1.1 — Audio control registrations for NiceViewBuilder.\r\n * Controls: player, waveform, piano-roll, karaoke\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const audioControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceAudioPlayer\",\r\n label: \"Audio Player\",\r\n category: \"Audio\",\r\n icon: \"music\",\r\n defaultProps: { src: \"\", autoplay: false, showWaveform: true },\r\n propDescriptors: [\r\n { name: \"src\", type: \"string\", label: \"Audio source URL\" },\r\n { name: \"autoplay\", type: \"boolean\", label: \"Auto-play\", defaultValue: false },\r\n { name: \"loop\", type: \"boolean\", label: \"Loop\", defaultValue: false },\r\n { name: \"showWaveform\", type: \"boolean\", label: \"Show waveform\", defaultValue: true },\r\n { name: \"showPlaylist\", type: \"boolean\", label: \"Show playlist\", defaultValue: false },\r\n { name: \"visualizerMode\", type: \"select\", label: \"Visualizer mode\", options: [\r\n { label: \"None\", value: \"none\" },\r\n { label: \"Bars\", value: \"bars\" },\r\n { label: \"Wave\", value: \"wave\" },\r\n { label: \"Circle\", value: \"circle\" },\r\n ], defaultValue: \"bars\" },\r\n { name: \"height\", type: \"size\", label: \"Height\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceWaveform\",\r\n label: \"Waveform\",\r\n category: \"Audio\",\r\n icon: \"waveform\",\r\n defaultProps: { peaks: [], color: \"#4a90d9\" },\r\n propDescriptors: [\r\n { name: \"peaks\", type: \"json\", label: \"Peak data (JSON array)\" },\r\n { name: \"color\", type: \"color\", label: \"Waveform color\", defaultValue: \"#4a90d9\" },\r\n { name: \"progressColor\", type: \"color\", label: \"Progress color\", defaultValue: \"#1b5e20\" },\r\n { name: \"barWidth\", type: \"number\", label: \"Bar width (px)\", min: 1, max: 10, step: 1, defaultValue: 2 },\r\n { name: \"height\", type: \"size\", label: \"Height\", group: \"Layout\" },\r\n { name: \"interactive\", type: \"boolean\", label: \"Allow seeking\", defaultValue: true },\r\n ],\r\n },\r\n {\r\n type: \"NicePianoRoll\",\r\n label: \"Piano Roll\",\r\n category: \"Audio\",\r\n icon: \"piano\",\r\n defaultProps: { notes: [], octaves: 4, startOctave: 3 },\r\n propDescriptors: [\r\n { name: \"notes\", type: \"json\", label: \"MIDI notes (JSON)\" },\r\n { name: \"octaves\", type: \"number\", label: \"Visible octaves\", min: 1, max: 8, defaultValue: 4 },\r\n { name: \"startOctave\", type: \"number\", label: \"Start octave\", min: 0, max: 8, defaultValue: 3 },\r\n { name: \"quantize\", type: \"select\", label: \"Quantize\", options: [\r\n { label: \"1/4\", value: \"4\" },\r\n { label: \"1/8\", value: \"8\" },\r\n { label: \"1/16\", value: \"16\" },\r\n { label: \"1/32\", value: \"32\" },\r\n ], defaultValue: \"16\" },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n { name: \"height\", type: \"size\", label: \"Height\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceKaraoke\",\r\n label: \"Karaoke\",\r\n category: \"Audio\",\r\n icon: \"microphone\",\r\n defaultProps: { lyrics: [], audioSrc: \"\" },\r\n propDescriptors: [\r\n { name: \"audioSrc\", type: \"string\", label: \"Audio source URL\" },\r\n { name: \"lyrics\", type: \"json\", label: \"Lyrics (JSON array of {time, text})\" },\r\n { name: \"showScore\", type: \"boolean\", label: \"Show score\", defaultValue: true },\r\n { name: \"pitchDetection\", type: \"boolean\", label: \"Pitch detection\", defaultValue: true },\r\n { name: \"highlightColor\", type: \"color\", label: \"Highlight color\", defaultValue: \"#ffd700\" },\r\n { name: \"fontSize\", type: \"size\", label: \"Font size\", group: \"Typography\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.6 — Auth control registrations for NiceViewBuilder.\r\n * Controls: login-form, captcha, 2fa-setup\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const authControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceLoginForm\",\r\n label: \"Login Form\",\r\n category: \"Auth\",\r\n icon: \"log-in\",\r\n defaultProps: { showRemember: true, showForgotPassword: true },\r\n propDescriptors: [\r\n { name: \"title\", type: \"string\", label: \"Form title\", defaultValue: \"Sign In\" },\r\n { name: \"showRemember\", type: \"boolean\", label: \"Show 'Remember me'\", defaultValue: true },\r\n { name: \"showForgotPassword\", type: \"boolean\", label: \"Show 'Forgot password'\", defaultValue: true },\r\n { name: \"showRegisterLink\", type: \"boolean\", label: \"Show register link\", defaultValue: false },\r\n { name: \"showLogo\", type: \"boolean\", label: \"Show logo\", defaultValue: true },\r\n { name: \"logoUrl\", type: \"string\", label: \"Logo URL\", group: \"Branding\" },\r\n { name: \"primaryColor\", type: \"color\", label: \"Primary color\", defaultValue: \"#1976d2\", group: \"Branding\" },\r\n { name: \"variant\", type: \"variant\", label: \"Visual style\", defaultValue: \"card\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceCaptcha\",\r\n label: \"Captcha\",\r\n category: \"Auth\",\r\n icon: \"shield\",\r\n defaultProps: { provider: \"recaptcha\", size: \"normal\" },\r\n propDescriptors: [\r\n { name: \"provider\", type: \"select\", label: \"Provider\", options: [\r\n { label: \"reCAPTCHA v2\", value: \"recaptcha\" },\r\n { label: \"reCAPTCHA v3\", value: \"recaptcha-v3\" },\r\n { label: \"hCaptcha\", value: \"hcaptcha\" },\r\n { label: \"Turnstile\", value: \"turnstile\" },\r\n ], defaultValue: \"recaptcha\" },\r\n { name: \"siteKey\", type: \"string\", label: \"Site key\" },\r\n { name: \"size\", type: \"select\", label: \"Size\", options: [\r\n { label: \"Normal\", value: \"normal\" },\r\n { label: \"Compact\", value: \"compact\" },\r\n { label: \"Invisible\", value: \"invisible\" },\r\n ], defaultValue: \"normal\" },\r\n { name: \"theme\", type: \"select\", label: \"Theme\", options: [\r\n { label: \"Light\", value: \"light\" },\r\n { label: \"Dark\", value: \"dark\" },\r\n ], defaultValue: \"light\" },\r\n ],\r\n },\r\n {\r\n type: \"Nice2FASetup\",\r\n label: \"2FA Setup\",\r\n category: \"Auth\",\r\n icon: \"key\",\r\n defaultProps: { method: \"totp\" },\r\n propDescriptors: [\r\n { name: \"method\", type: \"select\", label: \"2FA method\", options: [\r\n { label: \"TOTP (Authenticator)\", value: \"totp\" },\r\n { label: \"SMS\", value: \"sms\" },\r\n { label: \"Email\", value: \"email\" },\r\n ], defaultValue: \"totp\" },\r\n { name: \"issuer\", type: \"string\", label: \"Issuer name\", defaultValue: \"OmniVerk\" },\r\n { name: \"showBackupCodes\", type: \"boolean\", label: \"Show backup codes\", defaultValue: true },\r\n { name: \"showQR\", type: \"boolean\", label: \"Show QR code\", defaultValue: true },\r\n { name: \"codeLength\", type: \"number\", label: \"Code length\", min: 4, max: 8, defaultValue: 6 },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.5 — Business control registrations for NiceViewBuilder.\r\n * Controls: address, phone, currency, vat, invoice-lines\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const businessControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceAddress\",\r\n label: \"Address Editor\",\r\n category: \"Business\",\r\n icon: \"map-pin\",\r\n defaultProps: { country: \"PL\" },\r\n propDescriptors: [\r\n { name: \"street\", type: \"string\", label: \"Street\", group: \"Address\" },\r\n { name: \"city\", type: \"string\", label: \"City\", group: \"Address\" },\r\n { name: \"postalCode\", type: \"string\", label: \"Postal code\", group: \"Address\" },\r\n { name: \"country\", type: \"select\", label: \"Country\", options: [\r\n { label: \"Poland\", value: \"PL\" },\r\n { label: \"Germany\", value: \"DE\" },\r\n { label: \"USA\", value: \"US\" },\r\n { label: \"UK\", value: \"GB\" },\r\n { label: \"France\", value: \"FR\" },\r\n ], defaultValue: \"PL\", group: \"Address\" },\r\n { name: \"showMap\", type: \"boolean\", label: \"Show map preview\", defaultValue: false },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NicePhone\",\r\n label: \"Phone Input\",\r\n category: \"Business\",\r\n icon: \"phone\",\r\n defaultProps: { countryCode: \"+48\" },\r\n propDescriptors: [\r\n { name: \"value\", type: \"string\", label: \"Phone number\" },\r\n { name: \"countryCode\", type: \"string\", label: \"Default country code\", defaultValue: \"+48\" },\r\n { name: \"showFlag\", type: \"boolean\", label: \"Show country flag\", defaultValue: true },\r\n { name: \"format\", type: \"select\", label: \"Format\", options: [\r\n { label: \"International\", value: \"international\" },\r\n { label: \"National\", value: \"national\" },\r\n { label: \"E.164\", value: \"e164\" },\r\n ], defaultValue: \"international\" },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceCurrency\",\r\n label: \"Currency Input\",\r\n category: \"Business\",\r\n icon: \"dollar-sign\",\r\n defaultProps: { currency: \"PLN\", decimals: 2 },\r\n propDescriptors: [\r\n { name: \"value\", type: \"number\", label: \"Amount\", defaultValue: 0 },\r\n { name: \"currency\", type: \"select\", label: \"Currency\", options: [\r\n { label: \"PLN\", value: \"PLN\" },\r\n { label: \"EUR\", value: \"EUR\" },\r\n { label: \"USD\", value: \"USD\" },\r\n { label: \"GBP\", value: \"GBP\" },\r\n ], defaultValue: \"PLN\" },\r\n { name: \"decimals\", type: \"number\", label: \"Decimal places\", min: 0, max: 4, defaultValue: 2 },\r\n { name: \"showCurrencySymbol\", type: \"boolean\", label: \"Show symbol\", defaultValue: true },\r\n { name: \"thousandSeparator\", type: \"boolean\", label: \"Thousand separator\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceVAT\",\r\n label: \"VAT Input\",\r\n category: \"Business\",\r\n icon: \"percent\",\r\n defaultProps: { rate: 23, country: \"PL\" },\r\n propDescriptors: [\r\n { name: \"netValue\", type: \"number\", label: \"Net value\", defaultValue: 0, group: \"Values\" },\r\n { name: \"rate\", type: \"number\", label: \"VAT rate (%)\", min: 0, max: 100, defaultValue: 23, group: \"Values\" },\r\n { name: \"country\", type: \"select\", label: \"Country\", options: [\r\n { label: \"Poland\", value: \"PL\" },\r\n { label: \"Germany\", value: \"DE\" },\r\n { label: \"France\", value: \"FR\" },\r\n ], defaultValue: \"PL\" },\r\n { name: \"showGross\", type: \"boolean\", label: \"Show gross value\", defaultValue: true },\r\n { name: \"showVatAmount\", type: \"boolean\", label: \"Show VAT amount\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceInvoiceLines\",\r\n label: \"Invoice Lines\",\r\n category: \"Business\",\r\n icon: \"file-text\",\r\n defaultProps: { lines: [], currency: \"PLN\" },\r\n propDescriptors: [\r\n { name: \"lines\", type: \"json\", label: \"Invoice lines (JSON)\" },\r\n { name: \"currency\", type: \"select\", label: \"Currency\", options: [\r\n { label: \"PLN\", value: \"PLN\" },\r\n { label: \"EUR\", value: \"EUR\" },\r\n { label: \"USD\", value: \"USD\" },\r\n ], defaultValue: \"PLN\" },\r\n { name: \"showVAT\", type: \"boolean\", label: \"Show VAT column\", defaultValue: true },\r\n { name: \"showDiscount\", type: \"boolean\", label: \"Show discount column\", defaultValue: false },\r\n { name: \"showTotal\", type: \"boolean\", label: \"Show totals row\", defaultValue: true },\r\n { name: \"editable\", type: \"boolean\", label: \"Editable\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.4 — Gamification control registrations for NiceViewBuilder.\r\n * Controls: leaderboard, achievement, quest, xp-bar\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const gamificationControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceLeaderboard\",\r\n label: \"Leaderboard\",\r\n category: \"Gamification\",\r\n icon: \"trophy\",\r\n defaultProps: { dataSource: [], maxRows: 10 },\r\n propDescriptors: [\r\n { name: \"dataSource\", type: \"json\", label: \"Data source (JSON)\" },\r\n { name: \"maxRows\", type: \"number\", label: \"Max visible rows\", min: 3, max: 100, defaultValue: 10 },\r\n { name: \"showRank\", type: \"boolean\", label: \"Show rank column\", defaultValue: true },\r\n { name: \"showAvatar\", type: \"boolean\", label: \"Show avatar\", defaultValue: true },\r\n { name: \"highlightCurrentUser\", type: \"boolean\", label: \"Highlight current user\", defaultValue: true },\r\n { name: \"scoreField\", type: \"string\", label: \"Score field name\", defaultValue: \"score\" },\r\n { name: \"nameField\", type: \"string\", label: \"Name field name\", defaultValue: \"name\" },\r\n { name: \"variant\", type: \"variant\", label: \"Visual style\", defaultValue: \"default\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceAchievement\",\r\n label: \"Achievement Badge\",\r\n category: \"Gamification\",\r\n icon: \"award\",\r\n defaultProps: { title: \"Achievement\", unlocked: false },\r\n propDescriptors: [\r\n { name: \"title\", type: \"string\", label: \"Achievement title\", defaultValue: \"Achievement\" },\r\n { name: \"description\", type: \"string\", label: \"Description\" },\r\n { name: \"icon\", type: \"string\", label: \"Icon URL\" },\r\n { name: \"unlocked\", type: \"boolean\", label: \"Unlocked\", defaultValue: false },\r\n { name: \"progress\", type: \"number\", label: \"Progress (0-100)\", min: 0, max: 100, defaultValue: 0 },\r\n { name: \"rarity\", type: \"select\", label: \"Rarity\", options: [\r\n { label: \"Common\", value: \"common\" },\r\n { label: \"Rare\", value: \"rare\" },\r\n { label: \"Epic\", value: \"epic\" },\r\n { label: \"Legendary\", value: \"legendary\" },\r\n ], defaultValue: \"common\" },\r\n { name: \"size\", type: \"size\", label: \"Badge size\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceQuest\",\r\n label: \"Quest Tracker\",\r\n category: \"Gamification\",\r\n icon: \"map\",\r\n defaultProps: { title: \"Quest\", steps: [] },\r\n propDescriptors: [\r\n { name: \"title\", type: \"string\", label: \"Quest title\", defaultValue: \"Quest\" },\r\n { name: \"description\", type: \"string\", label: \"Quest description\" },\r\n { name: \"steps\", type: \"json\", label: \"Steps (JSON array of {title, completed})\" },\r\n { name: \"reward\", type: \"string\", label: \"Reward description\" },\r\n { name: \"rewardXP\", type: \"number\", label: \"Reward XP\", min: 0, defaultValue: 100 },\r\n { name: \"showProgress\", type: \"boolean\", label: \"Show progress bar\", defaultValue: true },\r\n { name: \"variant\", type: \"variant\", label: \"Visual style\", defaultValue: \"default\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceXPBar\",\r\n label: \"XP Progress Bar\",\r\n category: \"Gamification\",\r\n icon: \"zap\",\r\n defaultProps: { currentXP: 0, maxXP: 1000, level: 1 },\r\n propDescriptors: [\r\n { name: \"currentXP\", type: \"number\", label: \"Current XP\", min: 0, defaultValue: 0 },\r\n { name: \"maxXP\", type: \"number\", label: \"Max XP (next level)\", min: 1, defaultValue: 1000 },\r\n { name: \"level\", type: \"number\", label: \"Current level\", min: 1, defaultValue: 1 },\r\n { name: \"showLabel\", type: \"boolean\", label: \"Show XP label\", defaultValue: true },\r\n { name: \"showLevel\", type: \"boolean\", label: \"Show level badge\", defaultValue: true },\r\n { name: \"color\", type: \"color\", label: \"Bar color\", defaultValue: \"#4caf50\" },\r\n { name: \"animated\", type: \"boolean\", label: \"Animate changes\", defaultValue: true },\r\n { name: \"height\", type: \"size\", label: \"Bar height\", group: \"Layout\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.2 — Graphic control registrations for NiceViewBuilder.\r\n * Controls: pixel-editor, vector-editor, photo-editor\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const graphicControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NicePixelEditor\",\r\n label: \"Pixel Editor\",\r\n category: \"Graphics\",\r\n icon: \"grid\",\r\n defaultProps: { width: 32, height: 32, palette: [] },\r\n propDescriptors: [\r\n { name: \"width\", type: \"number\", label: \"Canvas width (px)\", min: 1, max: 256, defaultValue: 32 },\r\n { name: \"height\", type: \"number\", label: \"Canvas height (px)\", min: 1, max: 256, defaultValue: 32 },\r\n { name: \"palette\", type: \"json\", label: \"Color palette (JSON array)\" },\r\n { name: \"gridVisible\", type: \"boolean\", label: \"Show grid\", defaultValue: true },\r\n { name: \"zoom\", type: \"number\", label: \"Zoom level\", min: 1, max: 32, step: 1, defaultValue: 8 },\r\n { name: \"layers\", type: \"boolean\", label: \"Enable layers\", defaultValue: true },\r\n { name: \"onion\", type: \"boolean\", label: \"Onion skin (animation)\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceVectorEditor\",\r\n label: \"Vector Editor\",\r\n category: \"Graphics\",\r\n icon: \"pen-tool\",\r\n defaultProps: { width: 800, height: 600 },\r\n propDescriptors: [\r\n { name: \"width\", type: \"number\", label: \"Canvas width\", min: 100, max: 4096, defaultValue: 800, group: \"Layout\" },\r\n { name: \"height\", type: \"number\", label: \"Canvas height\", min: 100, max: 4096, defaultValue: 600, group: \"Layout\" },\r\n { name: \"showRulers\", type: \"boolean\", label: \"Show rulers\", defaultValue: true },\r\n { name: \"snapToGrid\", type: \"boolean\", label: \"Snap to grid\", defaultValue: true },\r\n { name: \"gridSize\", type: \"number\", label: \"Grid size\", min: 1, max: 100, defaultValue: 10 },\r\n { name: \"fillColor\", type: \"color\", label: \"Default fill\", defaultValue: \"#ffffff\" },\r\n { name: \"strokeColor\", type: \"color\", label: \"Default stroke\", defaultValue: \"#000000\" },\r\n { name: \"strokeWidth\", type: \"number\", label: \"Stroke width\", min: 0, max: 20, step: 0.5, defaultValue: 1 },\r\n ],\r\n },\r\n {\r\n type: \"NicePhotoEditor\",\r\n label: \"Photo Editor\",\r\n category: \"Graphics\",\r\n icon: \"image\",\r\n defaultProps: { src: \"\" },\r\n propDescriptors: [\r\n { name: \"src\", type: \"string\", label: \"Image source URL\" },\r\n { name: \"crop\", type: \"boolean\", label: \"Allow crop\", defaultValue: true },\r\n { name: \"rotate\", type: \"boolean\", label: \"Allow rotate\", defaultValue: true },\r\n { name: \"filters\", type: \"boolean\", label: \"Show filters\", defaultValue: true },\r\n { name: \"adjustments\", type: \"boolean\", label: \"Show adjustments\", defaultValue: true },\r\n { name: \"text\", type: \"boolean\", label: \"Allow text overlay\", defaultValue: true },\r\n { name: \"outputFormat\", type: \"select\", label: \"Output format\", options: [\r\n { label: \"PNG\", value: \"png\" },\r\n { label: \"JPEG\", value: \"jpeg\" },\r\n { label: \"WebP\", value: \"webp\" },\r\n ], defaultValue: \"png\" },\r\n { name: \"maxWidth\", type: \"number\", label: \"Max output width\", min: 100, max: 8192, group: \"Output\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.7 — Social control registrations for NiceViewBuilder.\r\n * Controls: comments, ratings, wiki\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const socialControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceComments\",\r\n label: \"Comments\",\r\n category: \"Social\",\r\n icon: \"message-circle\",\r\n defaultProps: { threadId: \"\", allowReplies: true },\r\n propDescriptors: [\r\n { name: \"threadId\", type: \"string\", label: \"Thread ID / entity ID\" },\r\n { name: \"allowReplies\", type: \"boolean\", label: \"Allow replies (nested)\", defaultValue: true },\r\n { name: \"maxDepth\", type: \"number\", label: \"Max nesting depth\", min: 1, max: 10, defaultValue: 3 },\r\n { name: \"sortOrder\", type: \"select\", label: \"Default sort\", options: [\r\n { label: \"Newest first\", value: \"newest\" },\r\n { label: \"Oldest first\", value: \"oldest\" },\r\n { label: \"Most liked\", value: \"popular\" },\r\n ], defaultValue: \"newest\" },\r\n { name: \"allowReactions\", type: \"boolean\", label: \"Allow reactions\", defaultValue: true },\r\n { name: \"showAvatar\", type: \"boolean\", label: \"Show avatars\", defaultValue: true },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceRatings\",\r\n label: \"Ratings\",\r\n category: \"Social\",\r\n icon: \"star\",\r\n defaultProps: { maxStars: 5, allowHalf: true },\r\n propDescriptors: [\r\n { name: \"value\", type: \"number\", label: \"Current rating\", min: 0, max: 10, step: 0.5, defaultValue: 0 },\r\n { name: \"maxStars\", type: \"number\", label: \"Maximum stars\", min: 3, max: 10, defaultValue: 5 },\r\n { name: \"allowHalf\", type: \"boolean\", label: \"Allow half stars\", defaultValue: true },\r\n { name: \"showCount\", type: \"boolean\", label: \"Show vote count\", defaultValue: true },\r\n { name: \"showAverage\", type: \"boolean\", label: \"Show average\", defaultValue: true },\r\n { name: \"color\", type: \"color\", label: \"Star color\", defaultValue: \"#ffc107\" },\r\n { name: \"size\", type: \"size\", label: \"Star size\", group: \"Layout\" },\r\n { name: \"readOnly\", type: \"boolean\", label: \"Read only\", defaultValue: false },\r\n ],\r\n },\r\n {\r\n type: \"NiceWiki\",\r\n label: \"Wiki Editor\",\r\n category: \"Social\",\r\n icon: \"book\",\r\n defaultProps: { content: \"\", showTOC: true },\r\n propDescriptors: [\r\n { name: \"content\", type: \"string\", label: \"Initial content (Markdown)\" },\r\n { name: \"showTOC\", type: \"boolean\", label: \"Show table of contents\", defaultValue: true },\r\n { name: \"showHistory\", type: \"boolean\", label: \"Show version history\", defaultValue: true },\r\n { name: \"allowEdit\", type: \"boolean\", label: \"Allow editing\", defaultValue: true },\r\n { name: \"showToolbar\", type: \"boolean\", label: \"Show Markdown toolbar\", defaultValue: true },\r\n { name: \"previewMode\", type: \"select\", label: \"Preview mode\", options: [\r\n { label: \"Side by side\", value: \"split\" },\r\n { label: \"Tab switch\", value: \"tabs\" },\r\n { label: \"Live preview\", value: \"live\" },\r\n ], defaultValue: \"split\" },\r\n { name: \"maxLength\", type: \"number\", label: \"Max content length\", min: 0, defaultValue: 0 },\r\n ],\r\n },\r\n];\r\n","/**\r\n * 5.1.3 — 3D control registrations for NiceViewBuilder.\r\n * Controls: model-editor, model-viewer\r\n */\r\nimport type { ControlRegistryEntry } from \"../types\";\r\n\r\nexport const threeControlRegistry: ControlRegistryEntry[] = [\r\n {\r\n type: \"NiceModelEditor\",\r\n label: \"3D Model Editor\",\r\n category: \"3D\",\r\n icon: \"cube\",\r\n defaultProps: { modelUrl: \"\" },\r\n propDescriptors: [\r\n { name: \"modelUrl\", type: \"string\", label: \"Model URL (.glb / .gltf)\" },\r\n { name: \"environmentMap\", type: \"select\", label: \"Environment\", options: [\r\n { label: \"Studio\", value: \"studio\" },\r\n { label: \"Outdoor\", value: \"outdoor\" },\r\n { label: \"Warehouse\", value: \"warehouse\" },\r\n { label: \"None\", value: \"none\" },\r\n ], defaultValue: \"studio\" },\r\n { name: \"showGrid\", type: \"boolean\", label: \"Show grid\", defaultValue: true },\r\n { name: \"showAxes\", type: \"boolean\", label: \"Show axes\", defaultValue: true },\r\n { name: \"enablePhysics\", type: \"boolean\", label: \"Enable physics\", defaultValue: false },\r\n { name: \"wireframe\", type: \"boolean\", label: \"Wireframe mode\", defaultValue: false },\r\n { name: \"backgroundColor\", type: \"color\", label: \"Background\", defaultValue: \"#1a1a2e\" },\r\n { name: \"height\", type: \"size\", label: \"Viewport height\", group: \"Layout\" },\r\n ],\r\n },\r\n {\r\n type: \"NiceModelViewer\",\r\n label: \"3D Model Viewer\",\r\n category: \"3D\",\r\n icon: \"eye\",\r\n defaultProps: { modelUrl: \"\", autoRotate: true },\r\n propDescriptors: [\r\n { name: \"modelUrl\", type: \"string\", label: \"Model URL (.glb / .gltf)\" },\r\n { name: \"autoRotate\", type: \"boolean\", label: \"Auto-rotate\", defaultValue: true },\r\n { name: \"rotateSpeed\", type: \"number\", label: \"Rotate speed\", min: 0.1, max: 10, step: 0.1, defaultValue: 1 },\r\n { name: \"zoom\", type: \"boolean\", label: \"Allow zoom\", defaultValue: true },\r\n { name: \"pan\", type: \"boolean\", label: \"Allow pan\", defaultValue: true },\r\n { name: \"environmentMap\", type: \"select\", label: \"Environment\", options: [\r\n { label: \"Studio\", value: \"studio\" },\r\n { label: \"Outdoor\", value: \"outdoor\" },\r\n { label: \"Warehouse\", value: \"warehouse\" },\r\n { label: \"None\", value: \"none\" },\r\n ], defaultValue: \"studio\" },\r\n { name: \"backgroundColor\", type: \"color\", label: \"Background\", defaultValue: \"#1a1a2e\" },\r\n { name: \"showAnnotations\", type: \"boolean\", label: \"Show annotations\", defaultValue: false },\r\n { name: \"height\", type: \"size\", label: \"Viewport height\", group: \"Layout\" },\r\n ],\r\n },\r\n];\r\n","/**\r\n * Combined control registry — all @nice2dev/* control registrations in one place.\r\n * Use `allControlRegistries` to register everything with NiceViewBuilder.\r\n */\r\nimport type { ControlRegistryEntry } from '../types';\r\n\r\nimport { audioControlRegistry } from './audioControls';\r\nimport { authControlRegistry } from './authControls';\r\nimport { businessControlRegistry } from './businessControls';\r\nimport { gamificationControlRegistry } from './gamificationControls';\r\nimport { graphicControlRegistry } from './graphicControls';\r\nimport { socialControlRegistry } from './socialControls';\r\nimport { threeControlRegistry } from './threeControls';\r\n\r\nexport { audioControlRegistry } from './audioControls';\r\nexport { graphicControlRegistry } from './graphicControls';\r\nexport { threeControlRegistry } from './threeControls';\r\nexport { gamificationControlRegistry } from './gamificationControls';\r\nexport { businessControlRegistry } from './businessControls';\r\nexport { authControlRegistry } from './authControls';\r\nexport { socialControlRegistry } from './socialControls';\r\n\r\n/** All control registries combined into a single flat array. */\r\nexport const allControlRegistries: ControlRegistryEntry[] = [\r\n ...audioControlRegistry,\r\n ...graphicControlRegistry,\r\n ...threeControlRegistry,\r\n ...gamificationControlRegistry,\r\n ...businessControlRegistry,\r\n ...authControlRegistry,\r\n ...socialControlRegistry,\r\n];\r\n","/**\r\n * useLoanApi — React hook for LoanManagement API integration.\r\n *\r\n * Provides full integration between NiceLoanCalculator and OmniVerk backend:\r\n * - Fetch loan products/offers from banks\r\n * - Calculate amortization schedules\r\n * - Compare multiple loan scenarios\r\n * - Submit loan applications\r\n * - Track application status\r\n * - Save/load calculations\r\n *\r\n * @example\r\n * ```tsx\r\n * const {\r\n * offers,\r\n * loading,\r\n * fetchOffers,\r\n * calculateAmortization,\r\n * compareLoanScenarios,\r\n * submitApplication,\r\n * } = useLoanApi({\r\n * baseUrl: '/api/loans',\r\n * currency: 'PLN',\r\n * });\r\n *\r\n * // Use with NiceLoanCalculator\r\n * <NiceLoanCalculator\r\n * loanOffers={offers}\r\n * onCalculate={calculateAmortization}\r\n * onApply={submitApplication}\r\n * />\r\n * ```\r\n */\r\n\r\nimport { useState, useCallback, useRef, useEffect } from 'react';\r\n\r\nimport type {\r\n LoanOffer,\r\n LoanType,\r\n AmortizationConfig,\r\n AmortizationResult,\r\n CreditAnalysisResult,\r\n LoanApplicationState,\r\n ActiveLoan,\r\n RefinancingAnalysis,\r\n ConsolidationSimulation,\r\n DebtItem,\r\n} from './loans';\r\n\r\n/* ================================================================\r\n API TYPES\r\n ================================================================ */\r\n\r\n/** API endpoint configuration. */\r\nexport interface LoanApiConfig {\r\n /** Base URL for loan API endpoints. */\r\n baseUrl: string;\r\n /** Default currency. */\r\n currency?: string;\r\n /** Request timeout in ms. */\r\n timeout?: number;\r\n /** Custom fetch function (for testing or middleware). */\r\n fetcher?: <T>(url: string, options?: RequestInit) => Promise<T>;\r\n /** Auth token getter. */\r\n getAuthToken?: () => string | null;\r\n /** Error handler. */\r\n onError?: (error: LoanApiError) => void;\r\n}\r\n\r\n/** API error with details. */\r\nexport interface LoanApiError {\r\n code: string;\r\n message: string;\r\n details?: Record<string, unknown>;\r\n endpoint: string;\r\n statusCode?: number;\r\n}\r\n\r\n/** Loan search filters. */\r\nexport interface LoanOfferFilters {\r\n loanType?: LoanType;\r\n minAmount?: number;\r\n maxAmount?: number;\r\n minTerm?: number;\r\n maxTerm?: number;\r\n maxApr?: number;\r\n banks?: string[];\r\n features?: string[];\r\n currency?: string;\r\n}\r\n\r\n/** Amortization calculation request. */\r\nexport interface AmortizationRequest {\r\n principal: number;\r\n annualRate: number;\r\n termMonths: number;\r\n type?: 'annuity' | 'linear' | 'balloon';\r\n startDate?: string;\r\n extraPayments?: Array<{\r\n month: number;\r\n amount: number;\r\n }>;\r\n gracePeriodMonths?: number;\r\n currency?: string;\r\n}\r\n\r\n/** Loan scenario for comparison. */\r\nexport interface LoanScenarioRequest {\r\n name: string;\r\n offerId?: string;\r\n principal: number;\r\n annualRate: number;\r\n termMonths: number;\r\n downPayment?: number;\r\n extraMonthlyPayment?: number;\r\n oneTimePayments?: Array<{\r\n month: number;\r\n amount: number;\r\n }>;\r\n}\r\n\r\n/** Loan comparison result. */\r\nexport interface LoanComparisonResult {\r\n scenarios: Array<{\r\n name: string;\r\n offer?: LoanOffer;\r\n amortization: AmortizationResult;\r\n totalCost: number;\r\n totalInterest: number;\r\n effectiveRate: number;\r\n monthlyPayment: number;\r\n }>;\r\n recommendedIndex: number;\r\n savingsVsWorst: number;\r\n comparisonDate: string;\r\n}\r\n\r\n/** Loan application submission. */\r\nexport interface LoanApplicationRequest {\r\n offerId: string;\r\n principal: number;\r\n termMonths: number;\r\n purpose: string;\r\n applicant: {\r\n firstName: string;\r\n lastName: string;\r\n email: string;\r\n phone?: string;\r\n nationalId?: string;\r\n };\r\n employment?: {\r\n type: 'employed' | 'self-employed' | 'retired' | 'other';\r\n employer?: string;\r\n monthlyIncome: number;\r\n yearsEmployed?: number;\r\n };\r\n documents?: Array<{\r\n type: string;\r\n fileId: string;\r\n }>;\r\n consent: {\r\n creditCheck: boolean;\r\n marketing?: boolean;\r\n dataProcessing: boolean;\r\n };\r\n}\r\n\r\n/** Saved calculation for later retrieval. */\r\nexport interface SavedCalculation {\r\n id: string;\r\n name: string;\r\n createdAt: string;\r\n updatedAt: string;\r\n config: AmortizationConfig;\r\n result: AmortizationResult;\r\n scenarios?: LoanScenarioRequest[];\r\n notes?: string;\r\n}\r\n\r\n/* ================================================================\r\n HOOK RESULT TYPES\r\n ================================================================ */\r\n\r\n/** Loading state for multiple operations. */\r\nexport interface LoanApiLoadingState {\r\n offers: boolean;\r\n calculate: boolean;\r\n compare: boolean;\r\n apply: boolean;\r\n creditCheck: boolean;\r\n refinancing: boolean;\r\n consolidation: boolean;\r\n savedCalculations: boolean;\r\n}\r\n\r\n/** Hook return type. */\r\nexport interface UseLoanApiResult {\r\n /* State */\r\n offers: LoanOffer[];\r\n selectedOffer: LoanOffer | null;\r\n latestCalculation: AmortizationResult | null;\r\n latestComparison: LoanComparisonResult | null;\r\n applicationState: LoanApplicationState | null;\r\n creditAnalysis: CreditAnalysisResult | null;\r\n savedCalculations: SavedCalculation[];\r\n activeLoans: ActiveLoan[];\r\n loading: LoanApiLoadingState;\r\n error: LoanApiError | null;\r\n\r\n /* Offer operations */\r\n fetchOffers: (filters?: LoanOfferFilters) => Promise<LoanOffer[]>;\r\n selectOffer: (offer: LoanOffer | null) => void;\r\n getOfferDetails: (offerId: string) => Promise<LoanOffer>;\r\n\r\n /* Calculation operations */\r\n calculateAmortization: (request: AmortizationRequest) => Promise<AmortizationResult>;\r\n compareLoanScenarios: (scenarios: LoanScenarioRequest[]) => Promise<LoanComparisonResult>;\r\n\r\n /* Credit analysis */\r\n runCreditAnalysis: (\r\n monthlyIncome: number,\r\n monthlyDebts: number,\r\n requestedAmount: number,\r\n ) => Promise<CreditAnalysisResult>;\r\n\r\n /* Application operations */\r\n submitApplication: (application: LoanApplicationRequest) => Promise<LoanApplicationState>;\r\n getApplicationStatus: (applicationId: string) => Promise<LoanApplicationState>;\r\n cancelApplication: (applicationId: string) => Promise<void>;\r\n\r\n /* Saved calculations */\r\n saveCalculation: (\r\n name: string,\r\n config: AmortizationConfig,\r\n result: AmortizationResult,\r\n notes?: string,\r\n ) => Promise<SavedCalculation>;\r\n loadCalculation: (id: string) => Promise<SavedCalculation>;\r\n deleteSavedCalculation: (id: string) => Promise<void>;\r\n fetchSavedCalculations: () => Promise<SavedCalculation[]>;\r\n\r\n /* Refinancing & Consolidation */\r\n analyzeRefinancing: (\r\n currentLoanId: string,\r\n newOfferIds?: string[],\r\n ) => Promise<RefinancingAnalysis[]>;\r\n simulateConsolidation: (\r\n debts: DebtItem[],\r\n targetRate?: number,\r\n ) => Promise<ConsolidationSimulation>;\r\n\r\n /* Active loans */\r\n fetchActiveLoans: () => Promise<ActiveLoan[]>;\r\n\r\n /* Utilities */\r\n clearError: () => void;\r\n reset: () => void;\r\n}\r\n\r\n/* ================================================================\r\n DEFAULT FETCHER\r\n ================================================================ */\r\n\r\nasync function defaultFetcher<T>(url: string, options?: RequestInit): Promise<T> {\r\n const response = await fetch(url, {\r\n ...options,\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...options?.headers,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n const errorBody = await response.text();\r\n let parsed: { code?: string; message?: string } = {};\r\n try {\r\n parsed = JSON.parse(errorBody);\r\n } catch {\r\n // Not JSON error response\r\n }\r\n throw {\r\n code: parsed.code || `HTTP_${response.status}`,\r\n message: parsed.message || response.statusText,\r\n statusCode: response.status,\r\n };\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n}\r\n\r\n/* ================================================================\r\n HOOK IMPLEMENTATION\r\n ================================================================ */\r\n\r\nexport function useLoanApi(config: LoanApiConfig): UseLoanApiResult {\r\n const {\r\n baseUrl,\r\n currency = 'PLN',\r\n timeout = 30000,\r\n fetcher = defaultFetcher,\r\n getAuthToken,\r\n onError,\r\n } = config;\r\n\r\n /* ── State ─────────────────────────────────────────────────────── */\r\n const [offers, setOffers] = useState<LoanOffer[]>([]);\r\n const [selectedOffer, setSelectedOffer] = useState<LoanOffer | null>(null);\r\n const [latestCalculation, setLatestCalculation] = useState<AmortizationResult | null>(null);\r\n const [latestComparison, setLatestComparison] = useState<LoanComparisonResult | null>(null);\r\n const [applicationState, setApplicationState] = useState<LoanApplicationState | null>(null);\r\n const [creditAnalysis, setCreditAnalysis] = useState<CreditAnalysisResult | null>(null);\r\n const [savedCalculations, setSavedCalculations] = useState<SavedCalculation[]>([]);\r\n const [activeLoans, setActiveLoans] = useState<ActiveLoan[]>([]);\r\n const [error, setError] = useState<LoanApiError | null>(null);\r\n const [loading, setLoading] = useState<LoanApiLoadingState>({\r\n offers: false,\r\n calculate: false,\r\n compare: false,\r\n apply: false,\r\n creditCheck: false,\r\n refinancing: false,\r\n consolidation: false,\r\n savedCalculations: false,\r\n });\r\n\r\n const abortControllers = useRef<Map<string, AbortController>>(new Map());\r\n\r\n /* ── Helpers ───────────────────────────────────────────────────── */\r\n const getHeaders = useCallback((): Record<string, string> => {\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n const token = getAuthToken?.();\r\n if (token) {\r\n headers['Authorization'] = `Bearer ${token}`;\r\n }\r\n return headers;\r\n }, [getAuthToken]);\r\n\r\n const makeRequest = useCallback(\r\n async <T>(\r\n endpoint: string,\r\n options: RequestInit = {},\r\n loadingKey: keyof LoanApiLoadingState,\r\n ): Promise<T> => {\r\n // Cancel previous request to same endpoint\r\n const existingController = abortControllers.current.get(endpoint);\r\n if (existingController) {\r\n existingController.abort();\r\n }\r\n\r\n const controller = new AbortController();\r\n abortControllers.current.set(endpoint, controller);\r\n\r\n // Timeout\r\n const timeoutId = setTimeout(() => controller.abort(), timeout);\r\n\r\n setLoading((prev) => ({ ...prev, [loadingKey]: true }));\r\n setError(null);\r\n\r\n try {\r\n const url = `${baseUrl}${endpoint}`;\r\n const result = await fetcher<T>(url, {\r\n ...options,\r\n headers: {\r\n ...getHeaders(),\r\n ...(options.headers as Record<string, string> | undefined),\r\n },\r\n signal: controller.signal,\r\n });\r\n\r\n return result;\r\n } catch (err) {\r\n const apiError: LoanApiError = {\r\n code: (err as LoanApiError).code || 'UNKNOWN_ERROR',\r\n message: (err as Error).message || 'An unknown error occurred',\r\n details: (err as LoanApiError).details,\r\n endpoint,\r\n statusCode: (err as LoanApiError).statusCode,\r\n };\r\n\r\n if ((err as Error).name !== 'AbortError') {\r\n setError(apiError);\r\n onError?.(apiError);\r\n }\r\n\r\n throw apiError;\r\n } finally {\r\n clearTimeout(timeoutId);\r\n abortControllers.current.delete(endpoint);\r\n setLoading((prev) => ({ ...prev, [loadingKey]: false }));\r\n }\r\n },\r\n [baseUrl, timeout, fetcher, getHeaders, onError],\r\n );\r\n\r\n /* ── Offer Operations ──────────────────────────────────────────── */\r\n const fetchOffers = useCallback(\r\n async (filters?: LoanOfferFilters): Promise<LoanOffer[]> => {\r\n const params = new URLSearchParams();\r\n if (filters?.loanType) {\r\n params.set('loanType', filters.loanType);\r\n }\r\n if (filters?.minAmount) {\r\n params.set('minAmount', String(filters.minAmount));\r\n }\r\n if (filters?.maxAmount) {\r\n params.set('maxAmount', String(filters.maxAmount));\r\n }\r\n if (filters?.minTerm) {\r\n params.set('minTerm', String(filters.minTerm));\r\n }\r\n if (filters?.maxTerm) {\r\n params.set('maxTerm', String(filters.maxTerm));\r\n }\r\n if (filters?.maxApr) {\r\n params.set('maxApr', String(filters.maxApr));\r\n }\r\n if (filters?.banks?.length) {\r\n params.set('banks', filters.banks.join(','));\r\n }\r\n if (filters?.features?.length) {\r\n params.set('features', filters.features.join(','));\r\n }\r\n params.set('currency', filters?.currency || currency);\r\n\r\n const queryString = params.toString();\r\n const endpoint = `/offers${queryString ? `?${queryString}` : ''}`;\r\n\r\n const result = await makeRequest<LoanOffer[]>(endpoint, { method: 'GET' }, 'offers');\r\n setOffers(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n const selectOffer = useCallback((offer: LoanOffer | null) => {\r\n setSelectedOffer(offer);\r\n }, []);\r\n\r\n const getOfferDetails = useCallback(\r\n async (offerId: string): Promise<LoanOffer> => {\r\n return makeRequest<LoanOffer>(`/offers/${offerId}`, { method: 'GET' }, 'offers');\r\n },\r\n [makeRequest],\r\n );\r\n\r\n /* ── Calculation Operations ────────────────────────────────────── */\r\n const calculateAmortization = useCallback(\r\n async (request: AmortizationRequest): Promise<AmortizationResult> => {\r\n const result = await makeRequest<AmortizationResult>(\r\n '/calculate/amortization',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n ...request,\r\n currency: request.currency || currency,\r\n }),\r\n },\r\n 'calculate',\r\n );\r\n setLatestCalculation(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n const compareLoanScenarios = useCallback(\r\n async (scenarios: LoanScenarioRequest[]): Promise<LoanComparisonResult> => {\r\n const result = await makeRequest<LoanComparisonResult>(\r\n '/calculate/compare',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ scenarios, currency }),\r\n },\r\n 'compare',\r\n );\r\n setLatestComparison(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n /* ── Credit Analysis ───────────────────────────────────────────── */\r\n const runCreditAnalysis = useCallback(\r\n async (\r\n monthlyIncome: number,\r\n monthlyDebts: number,\r\n requestedAmount: number,\r\n ): Promise<CreditAnalysisResult> => {\r\n const result = await makeRequest<CreditAnalysisResult>(\r\n '/credit/analyze',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n monthlyIncome,\r\n monthlyDebts,\r\n requestedAmount,\r\n currency,\r\n }),\r\n },\r\n 'creditCheck',\r\n );\r\n setCreditAnalysis(result);\r\n return result;\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n /* ── Application Operations ────────────────────────────────────── */\r\n const submitApplication = useCallback(\r\n async (application: LoanApplicationRequest): Promise<LoanApplicationState> => {\r\n const result = await makeRequest<LoanApplicationState>(\r\n '/applications',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify(application),\r\n },\r\n 'apply',\r\n );\r\n setApplicationState(result);\r\n return result;\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const getApplicationStatus = useCallback(\r\n async (applicationId: string): Promise<LoanApplicationState> => {\r\n const result = await makeRequest<LoanApplicationState>(\r\n `/applications/${applicationId}`,\r\n { method: 'GET' },\r\n 'apply',\r\n );\r\n setApplicationState(result);\r\n return result;\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const cancelApplication = useCallback(\r\n async (applicationId: string): Promise<void> => {\r\n await makeRequest<void>(`/applications/${applicationId}/cancel`, { method: 'POST' }, 'apply');\r\n setApplicationState(null);\r\n },\r\n [makeRequest],\r\n );\r\n\r\n /* ── Saved Calculations ────────────────────────────────────────── */\r\n const saveCalculation = useCallback(\r\n async (\r\n name: string,\r\n savedConfig: AmortizationConfig,\r\n result: AmortizationResult,\r\n notes?: string,\r\n ): Promise<SavedCalculation> => {\r\n const savedCalc = await makeRequest<SavedCalculation>(\r\n '/calculations',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ name, config: savedConfig, result, notes }),\r\n },\r\n 'savedCalculations',\r\n );\r\n setSavedCalculations((prev) => [...prev, savedCalc]);\r\n return savedCalc;\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const loadCalculation = useCallback(\r\n async (id: string): Promise<SavedCalculation> => {\r\n return makeRequest<SavedCalculation>(\r\n `/calculations/${id}`,\r\n { method: 'GET' },\r\n 'savedCalculations',\r\n );\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const deleteSavedCalculation = useCallback(\r\n async (id: string): Promise<void> => {\r\n await makeRequest<void>(`/calculations/${id}`, { method: 'DELETE' }, 'savedCalculations');\r\n setSavedCalculations((prev) => prev.filter((c) => c.id !== id));\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const fetchSavedCalculations = useCallback(async (): Promise<SavedCalculation[]> => {\r\n const result = await makeRequest<SavedCalculation[]>(\r\n '/calculations',\r\n { method: 'GET' },\r\n 'savedCalculations',\r\n );\r\n setSavedCalculations(result);\r\n return result;\r\n }, [makeRequest]);\r\n\r\n /* ── Refinancing & Consolidation ───────────────────────────────── */\r\n const analyzeRefinancing = useCallback(\r\n async (currentLoanId: string, newOfferIds?: string[]): Promise<RefinancingAnalysis[]> => {\r\n return makeRequest<RefinancingAnalysis[]>(\r\n '/refinancing/analyze',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ currentLoanId, newOfferIds }),\r\n },\r\n 'refinancing',\r\n );\r\n },\r\n [makeRequest],\r\n );\r\n\r\n const simulateConsolidation = useCallback(\r\n async (debts: DebtItem[], targetRate?: number): Promise<ConsolidationSimulation> => {\r\n return makeRequest<ConsolidationSimulation>(\r\n '/consolidation/simulate',\r\n {\r\n method: 'POST',\r\n body: JSON.stringify({ debts, targetRate, currency }),\r\n },\r\n 'consolidation',\r\n );\r\n },\r\n [makeRequest, currency],\r\n );\r\n\r\n /* ── Active Loans ──────────────────────────────────────────────── */\r\n const fetchActiveLoans = useCallback(async (): Promise<ActiveLoan[]> => {\r\n const result = await makeRequest<ActiveLoan[]>('/active', { method: 'GET' }, 'offers');\r\n setActiveLoans(result);\r\n return result;\r\n }, [makeRequest]);\r\n\r\n /* ── Utilities ─────────────────────────────────────────────────── */\r\n const clearError = useCallback(() => {\r\n setError(null);\r\n }, []);\r\n\r\n const reset = useCallback(() => {\r\n setOffers([]);\r\n setSelectedOffer(null);\r\n setLatestCalculation(null);\r\n setLatestComparison(null);\r\n setApplicationState(null);\r\n setCreditAnalysis(null);\r\n setSavedCalculations([]);\r\n setActiveLoans([]);\r\n setError(null);\r\n setLoading({\r\n offers: false,\r\n calculate: false,\r\n compare: false,\r\n apply: false,\r\n creditCheck: false,\r\n refinancing: false,\r\n consolidation: false,\r\n savedCalculations: false,\r\n });\r\n }, []);\r\n\r\n /* ── Cleanup ───────────────────────────────────────────────────── */\r\n useEffect(() => {\r\n return () => {\r\n abortControllers.current.forEach((controller) => controller.abort());\r\n abortControllers.current.clear();\r\n };\r\n }, []);\r\n\r\n return {\r\n // State\r\n offers,\r\n selectedOffer,\r\n latestCalculation,\r\n latestComparison,\r\n applicationState,\r\n creditAnalysis,\r\n savedCalculations,\r\n activeLoans,\r\n loading,\r\n error,\r\n\r\n // Offer operations\r\n fetchOffers,\r\n selectOffer,\r\n getOfferDetails,\r\n\r\n // Calculation operations\r\n calculateAmortization,\r\n compareLoanScenarios,\r\n\r\n // Credit analysis\r\n runCreditAnalysis,\r\n\r\n // Application operations\r\n submitApplication,\r\n getApplicationStatus,\r\n cancelApplication,\r\n\r\n // Saved calculations\r\n saveCalculation,\r\n loadCalculation,\r\n deleteSavedCalculation,\r\n fetchSavedCalculations,\r\n\r\n // Refinancing & Consolidation\r\n analyzeRefinancing,\r\n simulateConsolidation,\r\n\r\n // Active loans\r\n fetchActiveLoans,\r\n\r\n // Utilities\r\n clearError,\r\n reset,\r\n };\r\n}\r\n\r\n/* ================================================================\r\n LOAN SERVICE CLASS (For non-hook contexts)\r\n ================================================================ */\r\n\r\n/**\r\n * LoanService — standalone class for LoanManagement API calls.\r\n * Use in non-React contexts or for server-side operations.\r\n */\r\nexport class LoanService {\r\n private baseUrl: string;\r\n private currency: string;\r\n private timeout: number;\r\n private getAuthToken?: () => string | null;\r\n\r\n constructor(config: LoanApiConfig) {\r\n this.baseUrl = config.baseUrl;\r\n this.currency = config.currency || 'PLN';\r\n this.timeout = config.timeout || 30000;\r\n this.getAuthToken = config.getAuthToken;\r\n }\r\n\r\n private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\r\n\r\n const headers: Record<string, string> = {\r\n 'Content-Type': 'application/json',\r\n };\r\n const token = this.getAuthToken?.();\r\n if (token) {\r\n headers['Authorization'] = `Bearer ${token}`;\r\n }\r\n\r\n try {\r\n const response = await fetch(`${this.baseUrl}${endpoint}`, {\r\n ...options,\r\n headers: { ...headers, ...(options.headers as Record<string, string> | undefined) },\r\n signal: controller.signal,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\r\n }\r\n\r\n return response.json() as Promise<T>;\r\n } finally {\r\n clearTimeout(timeoutId);\r\n }\r\n }\r\n\r\n async getOffers(filters?: LoanOfferFilters): Promise<LoanOffer[]> {\r\n const params = new URLSearchParams();\r\n if (filters?.loanType) {\r\n params.set('loanType', filters.loanType);\r\n }\r\n if (filters?.minAmount) {\r\n params.set('minAmount', String(filters.minAmount));\r\n }\r\n if (filters?.maxAmount) {\r\n params.set('maxAmount', String(filters.maxAmount));\r\n }\r\n params.set('currency', filters?.currency || this.currency);\r\n\r\n const queryString = params.toString();\r\n return this.request<LoanOffer[]>(`/offers${queryString ? `?${queryString}` : ''}`);\r\n }\r\n\r\n async getOfferById(id: string): Promise<LoanOffer> {\r\n return this.request<LoanOffer>(`/offers/${id}`);\r\n }\r\n\r\n async calculateAmortization(request: AmortizationRequest): Promise<AmortizationResult> {\r\n return this.request<AmortizationResult>('/calculate/amortization', {\r\n method: 'POST',\r\n body: JSON.stringify({ ...request, currency: request.currency || this.currency }),\r\n });\r\n }\r\n\r\n async compareScenarios(scenarios: LoanScenarioRequest[]): Promise<LoanComparisonResult> {\r\n return this.request<LoanComparisonResult>('/calculate/compare', {\r\n method: 'POST',\r\n body: JSON.stringify({ scenarios, currency: this.currency }),\r\n });\r\n }\r\n\r\n async submitApplication(application: LoanApplicationRequest): Promise<LoanApplicationState> {\r\n return this.request<LoanApplicationState>('/applications', {\r\n method: 'POST',\r\n body: JSON.stringify(application),\r\n });\r\n }\r\n\r\n async getApplicationStatus(applicationId: string): Promise<LoanApplicationState> {\r\n return this.request<LoanApplicationState>(`/applications/${applicationId}`);\r\n }\r\n\r\n async analyzeRefinancing(\r\n currentLoanId: string,\r\n newOfferIds?: string[],\r\n ): Promise<RefinancingAnalysis[]> {\r\n return this.request<RefinancingAnalysis[]>('/refinancing/analyze', {\r\n method: 'POST',\r\n body: JSON.stringify({ currentLoanId, newOfferIds }),\r\n });\r\n }\r\n\r\n async simulateConsolidation(\r\n debts: DebtItem[],\r\n targetRate?: number,\r\n ): Promise<ConsolidationSimulation> {\r\n return this.request<ConsolidationSimulation>('/consolidation/simulate', {\r\n method: 'POST',\r\n body: JSON.stringify({ debts, targetRate, currency: this.currency }),\r\n });\r\n }\r\n\r\n async getActiveLoans(): Promise<ActiveLoan[]> {\r\n return this.request<ActiveLoan[]>('/active');\r\n }\r\n}\r\n\r\nexport default useLoanApi;\r\n"],"names":["ErpDataAdapter","config","path","init","f","headers","token","res","req","params","_a","_b","qs","id","item","ids","items","fields","createSignalRAdapter","signalR","status","listeners","setStatus","s","cb","connection","event","handler","method","args","useSignalRLiveData","options","adapter","entityName","keyField","initialData","onBeforeChange","onAfterChange","onConflict","subscribeMethod","unsubscribeMethod","changeEventName","flashChanges","flashDuration","optimisticUpdates","data","setData","useState","flashedKeys","setFlashedKeys","pendingKeys","setPendingKeys","error","setError","pendingChangesRef","useRef","flashTimeoutsRef","applyChange","useCallback","currentData","change","operation","key","newData","changes","row","pending","resolved","flashRow","existingTimeout","prev","timeout","next","handleChange","applyRemoteUpdate","registerPendingChange","currentRow","confirmPendingChange","rollbackPendingChange","subscribe","e","unsubscribe","useEffect","unsubStatus","newStatus","createSignalRDataOperations","fetchMethod","insertMethod","updateMethod","deleteMethod","applyBatchChanges","dataMap","existing","useEntityPresence","entityType","entityId","currentUser","initialStatus","heartbeatInterval","idleTimeout","awayTimeout","joinMethod","leaveMethod","presenceEventName","joinedEventName","leftEventName","onPresenceChange","onEditConflict","presence","setPresence","myStatus","setLocalMyStatus","editingField","setEditingField","connectionStatus","setConnectionStatus","lastActivityRef","heartbeatRef","idleTimerRef","awayTimerRef","entityIds","entityKey","sendUpdate","field","selection","setMyStatus","startEditing","existingEditor","p","stopEditing","updateSelection","isFieldLocked","editor","getFieldEditor","refresh","result","handleActivity","handlePresenceChange","handleUserJoined","handleUserLeft","join","events","viewers","editors","hasActiveEditors","generateUserColor","userId","colors","hash","i","formatPresenceStatus","useMultiEntityPresence","batchEventName","presenceByEntity","setPresenceByEntity","handleBatchUpdate","getPresence","isEntityBeingEdited","getEntityEditors","cellKey","cell","parseTimestamp","ts","useCollaborativeDataGrid","conflictStrategy","mergeFunction","onBeforeCommit","onAfterCommit","enableCellLocking","lockTimeout","enableCursorSharing","cursorDebounce","liveDataOptions","cellLocks","setCellLocks","pendingEdits","setPendingEdits","conflicts","setConflicts","focusedCell","setFocusedCell","selectedCells","setSelectedCells","selectedRows","setSelectedRows","editingCell","setEditingCell","userSelections","setUserSelections","cursorDebounceRef","userColorCache","getUserColor","handleDataConflict","local","remote","pendingEdit","localTimestamp","remoteTimestamp","conflict","liveData","sendCursorUpdate","lockHandler","unlockHandler","interval","now","changed","lock","isCellLocked","getCellEditor","startCellEdit","commitCellEdit","newValue","r","oldValue","cancelCellEdit","edit","resolveConflict","resolution","c","insertRow","updateRow","deleteRow","mySelection","useMemo","ErpAuthAdapter","body","ErpFileAdapter","h","file","folder","form","files","blob","onProgress","signal","resolve","reject","xhr","k","v","results","evt","ErpExportAdapter","downloadUrl","filename","url","a","createMiddlewarePipeline","middlewares","baseFetch","realFetch","input","ctx","mw","nextHandler","loggingMiddleware","start","ms","err","retryMiddleware","opts","maxRetries","baseDelay","retryOn","lastError","attempt","delay","headerMiddleware","extra","ErpRateLimiter","tryNext","t","oldestInWindow","waitMs","entry","ErpOptimisticStore","listener","snapshot","fn","m","tempKey","optimistic","mutation","it","idx","previous","ErpOfflineQueue","raw","max","audioControlRegistry","authControlRegistry","businessControlRegistry","gamificationControlRegistry","graphicControlRegistry","socialControlRegistry","threeControlRegistry","allControlRegistries","defaultFetcher","response","errorBody","parsed","useLoanApi","baseUrl","currency","fetcher","getAuthToken","onError","offers","setOffers","selectedOffer","setSelectedOffer","latestCalculation","setLatestCalculation","latestComparison","setLatestComparison","applicationState","setApplicationState","creditAnalysis","setCreditAnalysis","savedCalculations","setSavedCalculations","activeLoans","setActiveLoans","loading","setLoading","abortControllers","getHeaders","makeRequest","endpoint","loadingKey","existingController","controller","timeoutId","apiError","fetchOffers","filters","queryString","selectOffer","offer","getOfferDetails","offerId","calculateAmortization","request","compareLoanScenarios","scenarios","runCreditAnalysis","monthlyIncome","monthlyDebts","requestedAmount","submitApplication","application","getApplicationStatus","applicationId","cancelApplication","saveCalculation","name","savedConfig","notes","savedCalc","loadCalculation","deleteSavedCalculation","fetchSavedCalculations","analyzeRefinancing","currentLoanId","newOfferIds","simulateConsolidation","debts","targetRate","fetchActiveLoans","clearError","reset","LoanService"],"mappings":"okBAsBO,MAAMA,EAA4B,CAGvC,YAAYC,EAAuC,CACjD,KAAK,IAAM,OAAOA,GAAW,SAAW,CAAE,QAASA,GAAWA,CAChE,CAEA,MAAc,QAAWC,EAAcC,EAAgC,CACrE,MAAMC,EAAI,KAAK,IAAI,OAAS,WAAW,MACjCC,EAAkC,CACtC,eAAgB,mBAChB,GAAG,KAAK,IAAI,OAAA,EAEd,GAAI,KAAK,IAAI,aAAc,CACzB,MAAMC,EAAQ,MAAM,KAAK,IAAI,aAAA,EAC7BD,EAAQ,cAAmB,UAAUC,CAAK,EAC5C,CACA,MAAMC,EAAM,MAAMH,EAAE,GAAG,KAAK,IAAI,OAAO,GAAGF,CAAI,GAAI,CAChD,GAAGC,EACH,QAAS,CAAE,GAAGE,EAAS,GAAGF,GAAA,YAAAA,EAAM,OAAA,CAAQ,CACzC,EACD,GAAI,CAACI,EAAI,GACP,MAAM,IAAI,MAAM,uBAAuBA,EAAI,MAAM,IAAIA,EAAI,UAAU,EAAE,EAEvE,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,KAAKC,EAAiD,SAC1D,MAAMC,EAAS,IAAI,iBACfD,GAAA,YAAAA,EAAK,OAAQ,MACfC,EAAO,IAAI,OAAQ,OAAOD,EAAI,IAAI,CAAC,GAEjCA,GAAA,YAAAA,EAAK,OAAQ,MACfC,EAAO,IAAI,OAAQ,OAAOD,EAAI,IAAI,CAAC,EAEjCA,GAAA,MAAAA,EAAK,QACPC,EAAO,IAAI,SAAUD,EAAI,MAAM,GAE7BE,EAAAF,GAAA,YAAAA,EAAK,OAAL,MAAAE,EAAW,QACbD,EAAO,IAAI,OAAQ,KAAK,UAAUD,EAAI,IAAI,CAAC,GAEzCG,EAAAH,GAAA,YAAAA,EAAK,UAAL,MAAAG,EAAc,QAChBF,EAAO,IAAI,UAAW,KAAK,UAAUD,EAAI,OAAO,CAAC,EAEnD,MAAMI,EAAKH,EAAO,SAAA,EAClB,OAAO,KAAK,QAA0BG,EAAK,IAAIA,CAAE,GAAK,EAAE,CAC1D,CAGA,MAAM,QAAQC,EAA8C,CAC1D,OAAO,KAAK,QAAwB,IAAIA,CAAE,EAAE,CAC9C,CAGA,MAAM,OAAOC,EAA2C,CACtD,OAAO,KAAK,QAAwB,GAAI,CAAE,OAAQ,OAAQ,KAAM,KAAK,UAAUA,CAAI,CAAA,CAAG,CACxF,CAGA,MAAM,OAAOD,EAAqBC,EAA2C,CAC3E,OAAO,KAAK,QAAwB,IAAID,CAAE,GAAI,CAAE,OAAQ,MAAO,KAAM,KAAK,UAAUC,CAAI,EAAG,CAC7F,CAGA,MAAM,OAAOD,EAAiD,CAC5D,OAAO,KAAK,QAA2B,IAAIA,CAAE,GAAI,CAAE,OAAQ,SAAU,CACvE,CAGA,MAAM,YAAYE,EAAsD,CACtE,OAAO,KAAK,QAA2B,gBAAiB,CACtD,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,IAAAA,EAAK,CAAA,CAC7B,CACH,CAGA,MAAM,YAAYC,EAAgD,CAChE,OAAO,KAAK,QAA0B,SAAU,CAC9C,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,MAAAA,EAAO,CAAA,CAC/B,CACH,CAGA,MAAM,YAAYA,EAA+E,CAC/F,OAAO,KAAK,QAA0B,SAAU,CAC9C,OAAQ,MACR,KAAM,KAAK,UAAU,CAAE,MAAAA,EAAO,CAAA,CAC/B,CACH,CAGA,MAAM,MAAMH,EAAqBI,EAA6C,CAC5E,OAAO,KAAK,QAAwB,IAAIJ,CAAE,GAAI,CAC5C,OAAQ,QACR,KAAM,KAAK,UAAUI,CAAM,CAAA,CAC5B,CACH,CACF,CC5FA,eAAsBC,GAAqBjB,EAAsD,CAC/F,MAAMkB,EAAU,KAAM,QAAO,oBAAoB,EAEjD,IAAIC,EAAwB,eAC5B,MAAMC,MAAgB,IAEhBC,EAAaC,GAAqB,CACtCH,EAASG,EACTF,EAAU,QAASG,GAAOA,EAAGD,CAAC,CAAC,CACjC,EAUME,EARU,IAAIN,EAAQ,uBACzB,QAAQlB,EAAO,OAAQ,CACtB,mBAAoBA,EAAO,mBACvB,IAAMA,EAAO,qBACb,MAAA,CACL,EACA,uBAAA,EAEwB,MAAA,EAE3B,OAAAwB,EAAW,eAAe,IAAMH,EAAU,cAAc,CAAC,EACzDG,EAAW,cAAc,IAAMH,EAAU,WAAW,CAAC,EACrDG,EAAW,QAAQ,IAAMH,EAAU,cAAc,CAAC,EAEf,CACjC,IAAI,QAAS,CACX,OAAOF,CACT,EACA,MAAM,OAAQ,CACZE,EAAU,YAAY,EACtB,MAAMG,EAAW,MAAA,EACjBH,EAAU,WAAW,CACvB,EACA,MAAM,MAAO,CACX,MAAMG,EAAW,KAAA,EACjBH,EAAU,cAAc,CAC1B,EACA,GAAGI,EAAOC,EAAS,CACjBF,EAAW,GAAGC,EAAOC,CAAO,CAC9B,EACA,IAAID,EAAOC,EAAS,CAClBF,EAAW,IAAIC,EAAOC,CAAO,CAC/B,EACA,OAAOC,KAAWC,EAAM,CACtB,OAAOJ,EAAW,OAAOG,EAAQ,GAAGC,CAAI,CAC1C,EACA,eAAeL,EAAI,CACjB,OAAAH,EAAU,IAAIG,CAAE,EACT,IAAMH,EAAU,OAAOG,CAAE,CAClC,CAAA,CAIJ,CCsBO,SAASM,GACdC,EACyB,CACzB,KAAM,CACJ,QAAAC,EACA,WAAAC,EACA,SAAAC,EACA,YAAAC,EAAc,CAAA,EACd,eAAAC,EACA,cAAAC,EACA,WAAAC,EACA,gBAAAC,EAAkB,oBAClB,kBAAAC,EAAoB,wBACpB,gBAAAC,EAAkB,gBAClB,aAAAC,EAAe,GACf,cAAAC,EAAgB,IAChB,kBAAAC,EAAoB,EAAA,EAClBb,EAEE,CAACc,EAAMC,CAAO,EAAIC,EAAAA,SAAcZ,CAAW,EAC3C,CAACf,EAAQE,CAAS,EAAIyB,EAAAA,SAAwBf,EAAQ,MAAM,EAC5D,CAACgB,EAAaC,CAAc,EAAIF,EAAAA,SAAuB,IAAI,GAAK,EAChE,CAACG,EAAaC,CAAc,EAAIJ,EAAAA,SAAuB,IAAI,GAAK,EAChE,CAACK,EAAOC,EAAQ,EAAIN,EAAAA,SAAuB,IAAI,EAG/CO,EAAoBC,EAAAA,OAA2D,IAAI,GAAK,EACxFC,EAAmBD,EAAAA,OAAoD,IAAI,GAAK,EAGhFE,EAAcC,EAAAA,YAClB,CAACC,EAAkBC,IAAmC,CACpD,KAAM,CAAE,UAAAC,EAAW,IAAAC,EAAK,KAAMC,EAAS,QAAAC,GAAYJ,EAEnD,OAAQC,EAAA,CACN,IAAK,SAKH,MAJI,CAACE,GAIDJ,EAAY,KAAMM,GAAQA,EAAI/B,CAAQ,IAAM4B,CAAG,EAC1CH,EAEF,CAAC,GAAGA,EAAaI,CAAO,EAEjC,IAAK,SACH,OAAOJ,EAAY,IAAKM,GAAQ,CAC9B,GAAIA,EAAI/B,CAAQ,IAAM4B,EACpB,OAAOG,EAIT,MAAMC,EAAUZ,EAAkB,QAAQ,IAAIQ,CAAG,EACjD,GAAII,GAAW5B,EAAY,CACzB,MAAM6B,GAAW7B,EACf,CAAE,GAAG2B,EAAK,GAAGC,EAAQ,OAAA,EACpBH,GAAW,CAAE,GAAGE,EAAK,GAAGD,CAAA,EACzBJ,CAAA,EAEF,OAAIO,KAAa,QACR,CAAE,GAAGF,EAAK,GAAGC,EAAQ,OAAA,EACnBC,KAAa,UACtBb,EAAkB,QAAQ,OAAOQ,CAAG,EAC7BC,GAAW,CAAE,GAAGE,EAAK,GAAGD,CAAA,GACtBG,KAAa,QACf,CAAE,GAAGF,EAAK,GAAGD,EAAS,GAAGE,EAAQ,OAAA,EAEjCC,EAEX,CAGA,OAAIJ,GAGG,CAAE,GAAGE,EAAK,GAAGD,CAAA,CACtB,CAAC,EAEH,IAAK,SACH,OAAOL,EAAY,OAAQM,GAAQA,EAAI/B,CAAQ,IAAM4B,CAAG,EAE1D,QACE,OAAOH,CAAA,CAEb,EACA,CAACzB,EAAUI,CAAU,CAAA,EAIjB8B,EAAWV,EAAAA,YACdI,GAAiB,CAChB,GAAI,CAACpB,EACH,OAIF,MAAM2B,EAAkBb,EAAiB,QAAQ,IAAIM,CAAG,EACpDO,GACF,aAAaA,CAAe,EAI9BpB,EAAgBqB,GAAS,IAAI,IAAI,CAAC,GAAGA,EAAMR,CAAG,CAAC,CAAC,EAGhD,MAAMS,EAAU,WAAW,IAAM,CAC/BtB,EAAgBqB,GAAS,CACvB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOV,CAAG,EACRU,CACT,CAAC,EACDhB,EAAiB,QAAQ,OAAOM,CAAG,CACrC,EAAGnB,CAAa,EAEhBa,EAAiB,QAAQ,IAAIM,EAAKS,CAAO,CAC3C,EACA,CAAC7B,EAAcC,CAAa,CAAA,EAIxB8B,EAAef,EAAAA,YACnB,MAAOE,GAA8B,CAE/BA,EAAO,aAAe3B,IAKtBG,GAEE,CADY,MAAMA,EAAewB,CAAM,IAO7Cd,EAASa,GAAgB,CACvB,MAAMI,EAAUN,EAAYE,EAAaC,CAAM,EAC/C,OAAAvB,GAAA,MAAAA,EAAgBuB,EAAQG,GACjBA,CACT,CAAC,EAGDK,EAASR,EAAO,GAAG,GACrB,EACA,CAAC3B,EAAYG,EAAgBqB,EAAapB,EAAe+B,CAAQ,CAAA,EAI7DM,EAAoBhB,EAAAA,YACvBE,GAA8B,CAC7Ba,EAAab,CAAM,CACrB,EACA,CAACa,CAAY,CAAA,EAITE,GAAwBjB,EAAAA,YAC5B,CAACI,EAAcE,IAAwB,CACrC,GAAI,CAACpB,EACH,OAGF,MAAMgC,EAAa/B,EAAK,KAAMoB,GAAQA,EAAI/B,CAAQ,IAAM4B,CAAG,EACvDc,IACFtB,EAAkB,QAAQ,IAAIQ,EAAK,CAAE,SAAUc,EAAY,QAAAZ,EAAS,EACpEb,EAAgBmB,GAAS,IAAI,IAAI,CAAC,GAAGA,EAAMR,CAAG,CAAC,CAAC,EAEpD,EACA,CAACjB,EAAMX,EAAUU,CAAiB,CAAA,EAI9BiC,EAAuBnB,cAAaI,GAAiB,CACzDR,EAAkB,QAAQ,OAAOQ,CAAG,EACpCX,EAAgBmB,GAAS,CACvB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOV,CAAG,EACRU,CACT,CAAC,CACH,EAAG,CAAA,CAAE,EAGCM,EAAwBpB,EAAAA,YAC3BI,GAAiB,CAChB,MAAMI,EAAUZ,EAAkB,QAAQ,IAAIQ,CAAG,EAC7CI,IACFpB,EAASa,GACPA,EAAY,IAAKM,GAASA,EAAI/B,CAAQ,IAAM4B,EAAMI,EAAQ,SAAWD,CAAI,CAAA,EAE3EX,EAAkB,QAAQ,OAAOQ,CAAG,EACpCX,EAAgBmB,GAAS,CACvB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOV,CAAG,EACRU,CACT,CAAC,EAEL,EACA,CAACtC,CAAQ,CAAA,EAIL6C,GAAYrB,EAAAA,YAAY,SAAY,CACxC,GAAI1B,EAAQ,SAAW,YAGvB,GAAI,CACF,MAAMA,EAAQ,OAAOO,EAAiBN,CAAU,EAChDoB,GAAS,IAAI,CACf,OAAS2B,EAAG,CACV3B,GAAS2B,aAAa,MAAQA,EAAI,IAAI,MAAM,OAAOA,CAAC,CAAC,CAAC,CACxD,CACF,EAAG,CAAChD,EAASO,EAAiBN,CAAU,CAAC,EAEnCgD,GAAcvB,EAAAA,YAAY,SAAY,CAC1C,GAAI1B,EAAQ,SAAW,YAGvB,GAAI,CACF,MAAMA,EAAQ,OAAOQ,EAAmBP,CAAU,CACpD,MAAQ,CAER,CACF,EAAG,CAACD,EAASQ,EAAmBP,CAAU,CAAC,EAG3CiD,OAAAA,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAcnD,EAAQ,eAAgBoD,GAAc,CACxD9D,EAAU8D,CAAS,EACfA,IAAc,aAChBL,GAAA,CAEJ,CAAC,EAED,OAAA/C,EAAQ,GAAGS,EAAiBgC,CAAY,EAGpCzC,EAAQ,SAAW,aACrB+C,GAAA,EAGK,IAAM,CACXI,EAAA,EACAnD,EAAQ,IAAIS,EAAiBgC,CAAY,EACzCQ,GAAA,EAGAzB,EAAiB,QAAQ,QAASe,GAAY,aAAaA,CAAO,CAAC,EACnEf,EAAiB,QAAQ,MAAA,CAC3B,CACF,EAAG,CAACxB,EAASS,EAAiBgC,EAAcM,GAAWE,EAAW,CAAC,EAGnEC,EAAAA,UAAU,IAAM,CACdpC,EAAQX,CAAW,CACrB,EAAG,CAACA,CAAW,CAAC,EAET,CACL,KAAAU,EACA,OAAAzB,EACA,YAAA4B,EACA,YAAAE,EACA,MAAAE,EACA,kBAAAsB,EACA,sBAAAC,GACA,qBAAAE,EACA,sBAAAC,EACA,YAAaC,EAAA,CAEjB,CA2BO,SAASM,GACdpF,EACA,CACA,KAAM,CACJ,QAAA+B,EACA,WAAAC,EACA,SAAAC,EACA,YAAAoD,EAAc,cACd,aAAAC,EAAe,eACf,aAAAC,EAAe,eACf,aAAAC,EAAe,cAAA,EACbxF,EAEJ,MAAO,CAEL,MAAM,SAAS8B,EAA4E,CACzF,OAAOC,EAAQ,OAAOsD,EAAarD,EAAYF,CAAO,CACxD,EAGA,MAAM,OAAOc,EAA8B,CACzC,OAAOb,EAAQ,OAAOuD,EAActD,EAAYY,CAAI,CACtD,EAGA,MAAM,OAAOiB,EAAcE,EAAiC,CAC1D,OAAOhC,EAAQ,OAAOwD,EAAcvD,EAAY6B,EAAKE,CAAO,CAC9D,EAGA,MAAM,OAAOF,EAA6B,CACxC,OAAO9B,EAAQ,OAAOyD,EAAcxD,EAAY6B,CAAG,CACrD,EAGA,aAAgC,CAC9B,OAAO5B,CACT,CAAA,CAEJ,CAiBO,SAASwD,GACd7C,EACAmB,EACA9B,EACK,CACL,MAAMyD,EAAU,IAAI,IAAgB9C,EAAK,IAAKoB,GAAQ,CAACA,EAAI/B,CAAQ,EAAG+B,CAAG,CAAC,CAAC,EAE3E,UAAWL,KAAUI,EACnB,OAAQJ,EAAO,UAAA,CACb,IAAK,SACCA,EAAO,MAAQ,CAAC+B,EAAQ,IAAI/B,EAAO,GAAG,GACxC+B,EAAQ,IAAI/B,EAAO,IAAKA,EAAO,IAAI,EAErC,MACF,IAAK,SACH,GAAI+B,EAAQ,IAAI/B,EAAO,GAAG,EAAG,CAC3B,MAAMgC,EAAWD,EAAQ,IAAI/B,EAAO,GAAG,EACvC+B,EAAQ,IAAI/B,EAAO,IAAKA,EAAO,MAAQ,CAAE,GAAGgC,EAAU,GAAGhC,EAAO,OAAA,CAAS,CAC3E,CACA,MACF,IAAK,SACH+B,EAAQ,OAAO/B,EAAO,GAAG,EACzB,KAAA,CAIN,OAAO,MAAM,KAAK+B,EAAQ,OAAA,CAAQ,CACpC,CC1VO,SAASE,GAAkB9D,EAAwD,CACxF,KAAM,CACJ,QAAAC,EACA,WAAA8D,EACA,SAAAC,EACA,YAAAC,EACA,cAAAC,EAAgB,UAChB,kBAAAC,EAAoB,IACpB,YAAAC,EAAc,IACd,YAAAC,EAAc,IACd,WAAAC,EAAa,iBACb,YAAAC,EAAc,kBACd,aAAAd,EAAe,uBACf,kBAAAe,EAAoB,wBACpB,gBAAAC,EAAkB,mBAClB,cAAAC,EAAgB,iBAChB,iBAAAC,EACA,eAAAC,CAAA,EACE5E,EAEE,CAAC6E,EAAUC,CAAW,EAAI9D,EAAAA,SAA+B,CAAA,CAAE,EAC3D,CAAC+D,EAAUC,CAAgB,EAAIhE,EAAAA,SAA+BkD,CAAa,EAC3E,CAACe,EAAcC,CAAe,EAAIlE,WAAA,EAClC,CAACmE,GAAkBC,CAAmB,EAAIpE,EAAAA,SAAwBf,EAAQ,MAAM,EAEhFoF,EAAkB7D,EAAAA,OAAe,KAAK,IAAA,CAAK,EAC3C8D,EAAe9D,EAAAA,OAA8C,IAAI,EACjE+D,EAAe/D,EAAAA,OAA6C,IAAI,EAChEgE,EAAehE,EAAAA,OAA6C,IAAI,EAEhEiE,EAAY,MAAM,QAAQzB,CAAQ,EAAIA,EAAW,CAACA,CAAQ,EAC1D0B,GAAY,GAAG3B,CAAU,IAAI0B,EAAU,OAAO,KAAK,GAAG,CAAC,GAGvDE,EAAahE,EAAAA,YACjB,MAAOtC,EAA8BuG,EAAgBC,IAAwB,CAC3E,GAAI5F,EAAQ,SAAW,YAGvB,GAAI,CACF,MAAMA,EAAQ,OAAOwD,EAAc,CACjC,WAAAM,EACA,UAAA0B,EACA,OAAQxB,EAAY,GACpB,OAAA5E,EACA,aAAcuG,EACd,UAAAC,EACA,UAAW,IAAI,KAAA,EAAO,YAAA,CAAY,CACnC,CACH,MAAQ,CAER,CACF,EACA,CAAC5F,EAASwD,EAAcM,EAAY0B,EAAWxB,EAAY,EAAE,CAAA,EAIzD6B,EAAcnE,EAAAA,YAClB,CAACtC,EAA8BuG,IAAmB,CAChDZ,EAAiB3F,CAAM,EACvB6F,EAAgBU,CAAK,EACrBD,EAAWtG,EAAQuG,CAAK,EACxBP,EAAgB,QAAU,KAAK,IAAA,CACjC,EACA,CAACM,CAAU,CAAA,EAIPI,GAAepE,EAAAA,YAClBiE,GAAkB,CAEjB,MAAMI,EAAiBnB,EAAS,KAC7BoB,GAAMA,EAAE,KAAK,KAAOhC,EAAY,IAAMgC,EAAE,SAAW,WAAaA,EAAE,eAAiBL,CAAA,EAElFI,IACFpB,GAAA,MAAAA,EAAiBoB,EAAe,KAAMJ,IAExCE,EAAY,UAAWF,CAAK,CAC9B,EACA,CAACf,EAAUZ,EAAY,GAAIW,EAAgBkB,CAAW,CAAA,EAIlDI,GAAcvE,EAAAA,YAAY,IAAM,CACpCmE,EAAY,UAAW,MAAS,CAClC,EAAG,CAACA,CAAW,CAAC,EAGVK,EAAkBxE,EAAAA,YACrBkE,GAA0F,CACzFF,EAAWZ,EAAUE,EAAcY,CAAS,EAC5CR,EAAgB,QAAU,KAAK,IAAA,CACjC,EACA,CAACM,EAAYZ,EAAUE,CAAY,CAAA,EAI/BmB,EAAgBzE,EAAAA,YACnBiE,GAAuC,CACtC,MAAMS,EAASxB,EAAS,KACrBoB,GAAMA,EAAE,KAAK,KAAOhC,EAAY,IAAMgC,EAAE,SAAW,WAAaA,EAAE,eAAiBL,CAAA,EAEtF,OAAOS,GAAA,YAAAA,EAAQ,OAAQ,IACzB,EACA,CAACxB,EAAUZ,EAAY,EAAE,CAAA,EAIrBqC,EAAiB3E,EAAAA,YACpBiE,GAAuC,CACtC,MAAMS,EAASxB,EAAS,KAAMoB,GAAMA,EAAE,SAAW,WAAaA,EAAE,eAAiBL,CAAK,EACtF,OAAOS,GAAA,YAAAA,EAAQ,OAAQ,IACzB,EACA,CAACxB,CAAQ,CAAA,EAIL0B,EAAU5E,EAAAA,YAAY,SAAY,CACtC,GAAI1B,EAAQ,SAAW,YAGvB,GAAI,CACF,MAAMuG,EAAS,MAAMvG,EAAQ,OAAO,oBAAqB8D,EAAY0B,CAAS,EAC1E,MAAM,QAAQe,CAAM,IACtB1B,EAAY0B,CAAM,EAClB7B,GAAA,MAAAA,EAAmB6B,GAEvB,MAAQ,CAER,CACF,EAAG,CAACvG,EAAS8D,EAAY0B,EAAWd,CAAgB,CAAC,EAG/C8B,EAAiB9E,EAAAA,YAAY,IAAM,CACvC0D,EAAgB,QAAU,KAAK,IAAA,EAG3BE,EAAa,SACf,aAAaA,EAAa,OAAO,EAE/BC,EAAa,SACf,aAAaA,EAAa,OAAO,GAI/BT,IAAa,QAAUA,IAAa,SACtCe,EAAYb,EAAe,UAAY,UAAWA,CAAY,EAIhEM,EAAa,QAAU,WAAW,IAAM,CACtCO,EAAY,OAAQb,CAAY,CAClC,EAAGb,CAAW,EAGdoB,EAAa,QAAU,WAAW,IAAM,CACtCM,EAAY,OAAQb,CAAY,CAClC,EAAGZ,CAAW,CAChB,EAAG,CAACU,EAAUE,EAAcb,EAAaC,EAAayB,CAAW,CAAC,EAG5DY,EAAuB/E,EAAAA,YAC1Bb,GAAsF,CACjFA,EAAK,aAAeiD,GAIpB,CADoBjD,EAAK,UAAU,KAAMhC,GAAO2G,EAAU,SAAS3G,CAAE,CAAC,IAK1EgG,EAAYhE,EAAK,SAAS,OAAQmF,GAAMA,EAAE,KAAK,KAAOhC,EAAY,EAAE,CAAC,EACrEU,GAAA,MAAAA,EAAmB7D,EAAK,UAC1B,EACA,CAACiD,EAAY0B,EAAWxB,EAAY,GAAIU,CAAgB,CAAA,EAIpDgC,EAAmBhF,EAAAA,YACtBb,GAAoF,CAC/EA,EAAK,aAAeiD,GAIpB,CADoBjD,EAAK,UAAU,KAAMhC,GAAO2G,EAAU,SAAS3G,CAAE,CAAC,GAClDgC,EAAK,SAAS,KAAK,KAAOmD,EAAY,IAI9Da,EAAavC,GAAS,CACpB,MAAMsB,EAAWtB,EAAK,UAAW0D,GAAMA,EAAE,KAAK,KAAOnF,EAAK,SAAS,KAAK,EAAE,EAC1E,GAAI+C,GAAY,EAAG,CACjB,MAAMpB,EAAO,CAAC,GAAGF,CAAI,EACrB,OAAAE,EAAKoB,CAAQ,EAAI/C,EAAK,SACf2B,CACT,CACA,MAAO,CAAC,GAAGF,EAAMzB,EAAK,QAAQ,CAChC,CAAC,CACH,EACA,CAACiD,EAAY0B,EAAWxB,EAAY,EAAE,CAAA,EAIlC2C,EAAiBjF,EAAAA,YACpBb,GAAsE,CACjEA,EAAK,aAAeiD,GAIpB,CADoBjD,EAAK,UAAU,KAAMhC,GAAO2G,EAAU,SAAS3G,CAAE,CAAC,GAK1EgG,EAAavC,GAASA,EAAK,OAAQ0D,GAAMA,EAAE,KAAK,KAAOnF,EAAK,MAAM,CAAC,CACrE,EACA,CAACiD,EAAY0B,CAAS,CAAA,EAIxBtC,EAAAA,UAAU,IAAM,CACd,MAAMC,EAAcnD,EAAQ,eAAemF,CAAmB,EAE9DnF,EAAQ,GAAGuE,EAAmBkC,CAAoB,EAClDzG,EAAQ,GAAGwE,EAAiBkC,CAAgB,EAC5C1G,EAAQ,GAAGyE,EAAekC,CAAc,EAGxC,MAAMC,EAAO,SAAY,CACvB,GAAI5G,EAAQ,SAAW,YAGvB,GAAI,CACF,MAAMA,EAAQ,OAAOqE,EAAY,CAC/B,WAAAP,EACA,UAAA0B,EACA,KAAMxB,EACN,OAAQc,CAAA,CACT,EACD,MAAMwB,EAAA,CACR,MAAQ,CAER,CACF,EAEA,OAAItG,EAAQ,SAAW,aACrB4G,EAAA,EAGK,IAAM,CACXzD,EAAA,EACAnD,EAAQ,IAAIuE,EAAmBkC,CAAoB,EACnDzG,EAAQ,IAAIwE,EAAiBkC,CAAgB,EAC7C1G,EAAQ,IAAIyE,EAAekC,CAAc,EAGrC3G,EAAQ,SAAW,aACrBA,EACG,OAAOsE,EAAa,CACnB,WAAAR,EACA,UAAA0B,EACA,OAAQxB,EAAY,EAAA,CACrB,EACA,MAAM,IAAM,CAAC,CAAC,CAErB,CACF,EAAG,CACDhE,EACAyF,GACApB,EACAC,EACAC,EACAC,EACAC,EACAgC,EACAC,EACAC,EACAL,EACAtC,EACAc,EACAhB,EACA0B,CAAA,CACD,EAGDtC,EAAAA,UAAU,KACRmC,EAAa,QAAU,YAAY,IAAM,CACvCK,EAAWZ,EAAUE,CAAY,CACnC,EAAGd,CAAiB,EAEb,IAAM,CACPmB,EAAa,SACf,cAAcA,EAAa,OAAO,CAEtC,GACC,CAACnB,EAAmBwB,EAAYZ,EAAUE,CAAY,CAAC,EAG1D9B,EAAAA,UAAU,IAAM,CACd,MAAM2D,EAAS,CAAC,YAAa,UAAW,YAAa,aAAc,QAAQ,EAC3E,OAAAA,EAAO,QAASnH,GAAU,OAAO,iBAAiBA,EAAO8G,EAAgB,CAAE,QAAS,EAAA,CAAM,CAAC,EAG3FlB,EAAa,QAAU,WAAW,IAAM,CACtCO,EAAY,OAAQb,CAAY,CAClC,EAAGb,CAAW,EAEdoB,EAAa,QAAU,WAAW,IAAM,CACtCM,EAAY,OAAQb,CAAY,CAClC,EAAGZ,CAAW,EAEP,IAAM,CACXyC,EAAO,QAASnH,GAAU,OAAO,oBAAoBA,EAAO8G,CAAc,CAAC,EACvElB,EAAa,SACf,aAAaA,EAAa,OAAO,EAE/BC,EAAa,SACf,aAAaA,EAAa,OAAO,CAErC,CACF,EAAG,CAACiB,EAAgBrC,EAAaC,EAAaY,EAAca,CAAW,CAAC,EAGxE,MAAMiB,GAAUlC,EACb,OAAQoB,GAAMA,EAAE,SAAW,WAAaA,EAAE,SAAW,MAAM,EAC3D,IAAKA,GAAMA,EAAE,IAAI,EACde,GAAUnC,EAAS,OAAQoB,GAAMA,EAAE,SAAW,SAAS,EAAE,IAAKA,GAAMA,EAAE,IAAI,EAC1EgB,GAAmBD,GAAQ,OAAS,EAE1C,MAAO,CACL,SAAAnC,EACA,QAAAkC,GACA,QAAAC,GACA,SAAAjC,EACA,YAAAe,EACA,aAAAC,GACA,YAAAG,GACA,gBAAAC,EACA,iBAAAhB,GACA,cAAAiB,EACA,iBAAAa,GACA,eAAAX,EACA,QAAAC,CAAA,CAEJ,CAOO,SAASW,GAAkBC,EAAwB,CACxD,MAAMC,EAAS,CACb,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SAAA,EAEF,IAAIC,EAAO,EACX,QAASC,EAAI,EAAGA,EAAIH,EAAO,OAAQG,IACjCD,EAAOF,EAAO,WAAWG,CAAC,IAAMD,GAAQ,GAAKA,GAE/C,OAAOD,EAAO,KAAK,IAAIC,CAAI,EAAID,EAAO,MAAM,CAC9C,CAGO,SAASG,GAAqBlI,EAAsC,CACzE,OAAQA,EAAA,CACN,IAAK,UACH,MAAO,UACT,IAAK,UACH,MAAO,UACT,IAAK,OACH,MAAO,OACT,IAAK,OACH,MAAO,OACT,QACE,MAAO,SAAA,CAEb,CAgCO,SAASmI,GACdxH,EAC0B,CAC1B,KAAM,CAAE,QAAAC,EAAS,WAAA8D,EAAY,YAAAE,EAAa,eAAAwD,EAAiB,uBAA0BzH,EAE/E,CAAC0H,EAAkBC,CAAmB,EAAI3G,EAAAA,aAC1C,GAAI,EAIJ4G,EAAoBjG,EAAAA,YACvBb,GAAyF,CACpFA,EAAK,aAAeiD,GAGxB4D,EAAoB,IAAI,IAAI,OAAO,QAAQ7G,EAAK,gBAAgB,CAAC,CAAC,CACpE,EACA,CAACiD,CAAU,CAAA,EAGbZ,EAAAA,UAAU,KACRlD,EAAQ,GAAGwH,EAAgBG,CAAiB,EAErC,IAAM,CACX3H,EAAQ,IAAIwH,EAAgBG,CAAiB,CAC/C,GACC,CAAC3H,EAASwH,EAAgBG,CAAiB,CAAC,EAE/C,MAAMC,EAAclG,EAAAA,YACjBqC,GACQ0D,EAAiB,IAAI1D,CAAQ,GAAK,CAAA,EAE3C,CAAC0D,CAAgB,CAAA,EAGbI,EAAsBnG,EAAAA,YACzBqC,GAA8B,CAC7B,MAAMa,EAAW6C,EAAiB,IAAI1D,CAAQ,EAC9C,OAAOa,GAAA,YAAAA,EAAU,KAAMoB,GAAMA,EAAE,SAAW,WAAaA,EAAE,KAAK,KAAOhC,EAAY,MAAO,EAC1F,EACA,CAACyD,EAAkBzD,EAAY,EAAE,CAAA,EAG7B8D,EAAmBpG,EAAAA,YACtBqC,GAAqC,CACpC,MAAMa,EAAW6C,EAAiB,IAAI1D,CAAQ,EAC9C,OAAOa,GAAA,YAAAA,EAAU,OAAQoB,GAAMA,EAAE,SAAW,WAAW,IAAKA,GAAMA,EAAE,QAAS,CAAA,CAC/E,EACA,CAACyB,CAAgB,CAAA,EAGnB,MAAO,CACL,iBAAAA,EACA,YAAAG,EACA,oBAAAC,EACA,iBAAAC,CAAA,CAEJ,CC5YA,SAASC,EAAQC,EAA4B,CAC3C,MAAO,GAAG,OAAOA,EAAK,MAAM,CAAC,IAAIA,EAAK,KAAK,EAC7C,CAEA,SAASC,GAAeC,EAAyC,CAC/D,OAAKA,EAGD,OAAOA,GAAO,SACTA,EAEF,IAAI,KAAKA,CAAE,EAAE,QAAA,EALX,KAAK,IAAA,CAMhB,CAMO,SAASC,GACdpI,EAC+B,CAC/B,KAAM,CACJ,QAAAC,EACA,WAAA8D,EACA,SAAA5D,EACA,YAAA8D,EACA,YAAA7D,EAAc,CAAA,EACd,iBAAAiI,EAAmB,kBACnB,cAAAC,EACA,WAAA/H,EACA,eAAAgI,EACA,cAAAC,EACA,kBAAAC,EAAoB,GACpB,YAAAC,EAAc,IACd,oBAAAC,EAAsB,GACtB,eAAAC,EAAiB,GACjB,kBAAA/H,EAAoB,GACpB,aAAAF,EAAe,GACf,cAAAC,EAAgB,IAChB,gBAAAiI,EAAkB,CAAA,CAAC,EACjB7I,EAGE,CAAC8I,EAAWC,CAAY,EAAI/H,EAAAA,SAEhC,IAAI,GAAK,EACL,CAACgI,EAAcC,CAAe,EAAIjI,EAAAA,SAAmC,IAAI,GAAK,EAC9E,CAACkI,GAAWC,CAAY,EAAInI,EAAAA,SAAyB,CAAA,CAAE,EAGvD,CAACoI,EAAaC,CAAc,EAAIrI,EAAAA,SAA8B,IAAI,EAClE,CAACsI,EAAeC,CAAgB,EAAIvI,EAAAA,SAAyB,CAAA,CAAE,EAC/D,CAACwI,EAAcC,EAAe,EAAIzI,EAAAA,SAAoB,CAAA,CAAE,EACxD,CAAC0I,EAAaC,CAAc,EAAI3I,EAAAA,SAA8B,IAAI,EAGlE,CAAC4I,GAAgBC,EAAiB,EAAI7I,EAAAA,SAA0B,CAAA,CAAE,EAElE8I,EAAoBtI,EAAAA,OAA6C,IAAI,EACrEuI,EAAiBvI,EAAAA,OAA4B,IAAI,GAAK,EAGtDwI,EAAerI,cAAawF,IAC3B4C,EAAe,QAAQ,IAAI5C,CAAM,GACpC4C,EAAe,QAAQ,IAAI5C,EAAQD,GAAkBC,CAAM,CAAC,EAEvD4C,EAAe,QAAQ,IAAI5C,CAAM,GACvC,CAAA,CAAE,EAGC8C,EAAqBtI,EAAAA,YACzB,CAACuI,EAAUC,EAAWtI,IAAgE,CACpF,GAAI,CAACA,EAAO,QACV,OAAOsI,EAIT,MAAMC,EAAcpB,EAAa,IAC/BhB,EAAQ,CAAE,OAAQnG,EAAO,IAAK,MAAO,OAAO,KAAKA,EAAO,OAAO,EAAE,CAAC,EAAG,CAAA,EAEvE,GAAI,CAACuI,EACH,OAAOD,EAGT,MAAME,EAAiBD,EAAY,UAC7BE,EAAkBpC,GAAerG,EAAO,SAAS,EAEvD,OAAQwG,EAAA,CACN,IAAK,kBACH,OAAOgC,EAAiBC,EAAkB,QAAU,SAEtD,IAAK,mBACH,OAAOD,EAAiBC,EAAkB,QAAU,SAEtD,IAAK,QACH,OAAIhC,EACKA,EAAc4B,EAAOC,EAAQ,OAAO,KAAKtI,EAAO,OAAO,EAAE,CAAC,CAAC,EAE7D,QAET,IAAK,WAEH,MAAM0I,EAAyB,CAC7B,KAAM,CAAE,OAAQ1I,EAAO,IAAK,MAAO,OAAO,KAAKA,EAAO,OAAO,EAAE,CAAC,CAAA,EAChE,WAAYuI,EAAY,SACxB,YAAavI,EAAO,QAAQ,OAAO,KAAKA,EAAO,OAAO,EAAE,CAAC,CAAY,EACrE,UAAWoC,EACX,WAAYpC,EAAO,UACf,CAAE,GAAIA,EAAO,UAAW,KAAMA,EAAO,WACrC,CAAE,GAAI,UAAW,KAAM,SAAA,EAC3B,eAAAwI,EACA,gBAAAC,CAAA,EAEF,OAAAnB,EAAc5G,IAAS,CAAC,GAAGA,GAAMgI,CAAQ,CAAC,EACnC,QAET,IAAK,SACH,MAAO,SAET,QACE,MAAO,QAAA,CAEb,EACA,CAACvB,EAAcX,EAAkBC,EAAerE,CAAW,CAAA,EAIvDuG,EAAWzK,GAAsB,CACrC,QAAAE,EACA,WAAY8D,EACZ,SAAA5D,EACA,YAAAC,EACA,aAAAO,EACA,cAAAC,EACA,kBAAAC,EACA,WAAYoJ,EACZ,GAAGpB,CAAA,CACJ,EAGKhE,EAAWf,GAAkB,CACjC,QAAA7D,EACA,WAAA8D,EACA,SAAU,OACV,YAAAE,EACA,cAAe,SAAA,CAChB,EAGKwG,EAAmB9I,EAAAA,YAAY,IAAM,CACrC,CAACgH,GAAuB1I,EAAQ,SAAW,aAI/CA,EACG,OAAO,sBAAuB,CAC7B,WAAA8D,EACA,OAAQE,EAAY,GACpB,YAAAmF,EACA,cAAAE,EACA,aAAAE,EACA,YAAAE,CAAA,CACD,EACA,MAAM,IAAM,CAAC,CAAC,CACnB,EAAG,CACDzJ,EACA0I,EACA5E,EACAE,EAAY,GACZmF,EACAE,EACAE,EACAE,CAAA,CACD,EAGDvG,EAAAA,UAAU,IAAM,CACd,GAAKwF,EAIL,OAAImB,EAAkB,SACpB,aAAaA,EAAkB,OAAO,EAGxCA,EAAkB,QAAU,WAAWW,EAAkB7B,CAAc,EAEhE,IAAM,CACPkB,EAAkB,SACpB,aAAaA,EAAkB,OAAO,CAE1C,CACF,EAAG,CACDnB,EACA8B,EACA7B,EACAQ,EACAE,EACAE,EACAE,CAAA,CACD,EAGDvG,EAAAA,UAAU,IAAM,CACd,GAAI,CAACwF,EACH,OAGF,MAAM/I,EAAWkB,GASX,CACAA,EAAK,aAAeiD,GAAcjD,EAAK,SAAWmD,EAAY,IAIlE4F,GAAmBtH,GAAS,CAC1B,MAAMsB,EAAWtB,EAAK,UAAW/C,GAAMA,EAAE,KAAK,KAAOsB,EAAK,MAAM,EAC1D+E,EAA2B,CAC/B,KAAM,CAAE,GAAI/E,EAAK,OAAQ,KAAMA,EAAK,SAAU,UAAWA,EAAK,SAAA,EAC9D,YAAaA,EAAK,YAClB,cAAeA,EAAK,cACpB,aAAcA,EAAK,aACnB,YAAaA,EAAK,YAClB,MAAOkJ,EAAalJ,EAAK,MAAM,CAAA,EAGjC,GAAI+C,GAAY,EAAG,CACjB,MAAMpB,EAAO,CAAC,GAAGF,CAAI,EACrB,OAAAE,EAAKoB,CAAQ,EAAIgC,EACVpD,CACT,CACA,MAAO,CAAC,GAAGF,EAAMsD,CAAS,CAC5B,CAAC,CACH,EAEA,OAAA5F,EAAQ,GAAG,uBAAwBL,CAAO,EAEnC,IAAM,CACXK,EAAQ,IAAI,uBAAwBL,CAAO,CAC7C,CACF,EAAG,CAACK,EAAS0I,EAAqB5E,EAAYE,EAAY,GAAI+F,CAAY,CAAC,EAG3E7G,EAAAA,UAAU,IAAM,CACd,GAAI,CAACsF,EACH,OAGF,MAAMiC,EAAe5J,GAKf,CACAA,EAAK,aAAeiD,GAGxBgF,EAAcxG,GAAS,CACrB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,IAAIuF,EAAQlH,EAAK,IAAI,EAAG,CAAE,KAAMA,EAAK,KAAM,UAAWA,EAAK,SAAA,CAAW,EACpE2B,CACT,CAAC,CACH,EAEMkI,EAAiB7J,GAAqD,CACtEA,EAAK,aAAeiD,GAGxBgF,EAAcxG,GAAS,CACrB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOuF,EAAQlH,EAAK,IAAI,CAAC,EACvB2B,CACT,CAAC,CACH,EAEA,OAAAxC,EAAQ,GAAG,aAAcyK,CAAW,EACpCzK,EAAQ,GAAG,eAAgB0K,CAAa,EAEjC,IAAM,CACX1K,EAAQ,IAAI,aAAcyK,CAAW,EACrCzK,EAAQ,IAAI,eAAgB0K,CAAa,CAC3C,CACF,EAAG,CAAC1K,EAASwI,EAAmB1E,CAAU,CAAC,EAG3CZ,EAAAA,UAAU,IAAM,CACd,GAAI,CAACsF,EACH,OAGF,MAAMmC,EAAW,YAAY,IAAM,CACjC,MAAMC,EAAM,KAAK,IAAA,EACjB9B,EAAcxG,GAAS,CACrB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,IAAIuI,EAAU,GACd,SAAW,CAAC/I,EAAKgJ,CAAI,IAAKtI,EACpBoI,EAAME,EAAK,UAAYrC,IACzBjG,EAAK,OAAOV,CAAG,EACf+I,EAAU,IAGd,OAAOA,EAAUrI,EAAOF,CAC1B,CAAC,CACH,EAAG,GAAK,EAER,MAAO,IAAM,cAAcqI,CAAQ,CACrC,EAAG,CAACnC,EAAmBC,CAAW,CAAC,EAGnC,MAAMsC,EAAerJ,EAAAA,YAClBsG,GAAgC,CAC/B,GAAI,CAACQ,EACH,MAAO,GAET,MAAMsC,EAAOjC,EAAU,IAAId,EAAQC,CAAI,CAAC,EACxC,OAAK8C,EAGEA,EAAK,KAAK,KAAO9G,EAAY,IAAM,KAAK,IAAA,EAAQ8G,EAAK,UAAYrC,EAF/D,EAGX,EACA,CAACD,EAAmBK,EAAW7E,EAAY,GAAIyE,CAAW,CAAA,EAItDuC,GAAgBtJ,EAAAA,YACnBsG,GAA4C,CAC3C,MAAM8C,EAAOjC,EAAU,IAAId,EAAQC,CAAI,CAAC,EAIxC,MAHI,CAAC8C,GAGD,KAAK,IAAA,EAAQA,EAAK,UAAYrC,EACzB,KAEFqC,EAAK,IACd,EACA,CAACjC,EAAWJ,CAAW,CAAA,EAInBwC,GAAgBvJ,EAAAA,YACnBsG,GACK+C,EAAa/C,CAAI,EACZ,IAGT0B,EAAe1B,CAAI,EACnBpD,EAAS,aAAa,GAAGoD,EAAK,MAAM,IAAIA,EAAK,KAAK,EAAE,EAGhDQ,GAAqBxI,EAAQ,SAAW,cAC1CA,EACG,OAAO,kBAAmB,CACzB,WAAA8D,EACA,KAAAkE,EACA,KAAMhE,EACN,UAAW,KAAK,IAAA,CAAI,CACrB,EACA,MAAM,IAAM,CAAC,CAAC,EAEjB8E,EAAcxG,GAAS,CACrB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,IAAIuF,EAAQC,CAAI,EAAG,CAAE,KAAMhE,EAAa,UAAW,KAAK,IAAA,CAAI,CAAG,EAC7DxB,CACT,CAAC,GAGI,IAET,CAACuI,EAAcnG,EAAU4D,EAAmBxI,EAAS8D,EAAYE,CAAW,CAAA,EAIxEkH,GAAiBxJ,EAAAA,YACrB,MAAOsG,EAAoBmD,IAAwC,CAEjE,MAAMlJ,EAAMsI,EAAS,KAAK,KAAMa,GAAMA,EAAElL,CAAQ,IAAM8H,EAAK,MAAM,EACjE,GAAI,CAAC/F,EACH,MAAO,GAGT,MAAMoJ,EAAWpJ,EAAI+F,EAAK,KAAgB,EAG1C,GAAIM,GAEE,CADY,MAAMA,EAAeN,EAAMqD,EAAUF,CAAQ,EAE3D,OAAAG,EAAetD,CAAI,EACZ,GAKX,MAAMuD,EAAoB,CACxB,KAAAvD,EACA,cAAeqD,EACf,SAAAF,EACA,UAAW,KAAK,IAAA,CAAI,EAEtBnC,EAAiB1G,GAAS,CACxB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,IAAIuF,EAAQC,CAAI,EAAGuD,CAAI,EACrB/I,CACT,CAAC,EAGG5B,GACF2J,EAAS,sBAAsBvC,EAAK,OAAQ,CAAE,CAACA,EAAK,KAAK,EAAGmD,EAAwB,EAItF,GAAI,CACF,aAAMnL,EAAQ,OAAO,oBAAqB,CACxC,WAAA8D,EACA,SAAUkE,EAAK,OACf,MAAOA,EAAK,MACZ,MAAOmD,EACP,OAAQnH,EAAY,GACpB,UAAWuH,EAAK,SAAA,CACjB,EAEDhB,EAAS,qBAAqBvC,EAAK,MAAM,EACzCgB,EAAiB1G,GAAS,CACxB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOuF,EAAQC,CAAI,CAAC,EAClBxF,CACT,CAAC,EAED+F,GAAA,MAAAA,EAAgBP,EAAMmD,GAGlB3C,IACFxI,EAAQ,OAAO,kBAAmB,CAAE,WAAA8D,EAAY,KAAAkE,EAAM,EAAE,MAAM,IAAM,CAAC,CAAC,EACtEc,EAAcxG,GAAS,CACrB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOuF,EAAQC,CAAI,CAAC,EAClBxF,CACT,CAAC,GAGHkH,EAAe,IAAI,EACnB9E,EAAS,YAAA,EAEF,EACT,OAASxD,EAAO,CAEd,MAAAmJ,EAAS,sBAAsBvC,EAAK,MAAM,EAC1CgB,EAAiB1G,GAAS,CACxB,MAAME,GAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,GAAK,OAAOuF,EAAQC,CAAI,CAAC,EAClBxF,EACT,CAAC,EAEKpB,CACR,CACF,EACA,CACEmJ,EACArK,EACAoI,EACA1H,EACAZ,EACA8D,EACAE,EAAY,GACZuE,EACAC,EACA5D,CAAA,CACF,EAII0G,EAAiB5J,EAAAA,YACpBsG,GAAuB,CACtB0B,EAAe,IAAI,EACnB9E,EAAS,YAAA,EAGL4D,GAAqBxI,EAAQ,SAAW,cAC1CA,EAAQ,OAAO,kBAAmB,CAAE,WAAA8D,EAAY,KAAAkE,EAAM,EAAE,MAAM,IAAM,CAAC,CAAC,EACtEc,EAAcxG,GAAS,CACrB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOuF,EAAQC,CAAI,CAAC,EAClBxF,CACT,CAAC,GAIHwG,EAAiB1G,GAAS,CACxB,MAAME,EAAO,IAAI,IAAIF,CAAI,EACzB,OAAAE,EAAK,OAAOuF,EAAQC,CAAI,CAAC,EAClBxF,CACT,CAAC,CACH,EACA,CAACoC,EAAU4D,EAAmBxI,EAAS8D,CAAU,CAAA,EAI7C0H,EAAkB9J,EAAAA,YACtB,CAACsG,EAAoByD,IAA6C,CAChE,MAAMnB,EAAWrB,GAAU,KAAMyC,GAAM3D,EAAQ2D,EAAE,IAAI,IAAM3D,EAAQC,CAAI,CAAC,EACnEsC,IAIDmB,IAAe,UAEjBlB,EAAS,kBAAkB,CACzB,UAAW,SACX,WAAYzG,EACZ,IAAKkE,EAAK,OACV,QAAS,CAAE,CAACA,EAAK,KAAK,EAAGsC,EAAS,WAAA,CAAY,CAC/C,EAKHpB,EAAc5G,GAASA,EAAK,OAAQoJ,GAAM3D,EAAQ2D,EAAE,IAAI,IAAM3D,EAAQC,CAAI,CAAC,CAAC,EAC9E,EACA,CAACiB,GAAWsB,EAAUzG,CAAU,CAAA,EAI5B6H,EAAYjK,EAAAA,YAChB,MAAOb,GAAoC,CACzC,MAAMb,EAAQ,OAAO,eAAgB8D,EAAYjD,CAAI,CACvD,EACA,CAACb,EAAS8D,CAAU,CAAA,EAGhB8H,EAAYlK,EAAAA,YAChB,MAAOI,EAAcE,IAAuC,CACtDpB,GACF2J,EAAS,sBAAsBzI,EAAKE,CAAO,EAE7C,GAAI,CACF,MAAMhC,EAAQ,OAAO,eAAgB8D,EAAYhC,EAAKE,CAAO,EAC7DuI,EAAS,qBAAqBzI,CAAG,CACnC,OAASV,EAAO,CACd,MAAAmJ,EAAS,sBAAsBzI,CAAG,EAC5BV,CACR,CACF,EACA,CAACpB,EAAS8D,EAAYlD,EAAmB2J,CAAQ,CAAA,EAG7CsB,EAAYnK,EAAAA,YAChB,MAAOI,GAAgC,CACrC,MAAM9B,EAAQ,OAAO,eAAgB8D,EAAYhC,CAAG,CACtD,EACA,CAAC9B,EAAS8D,CAAU,CAAA,EAIhBgI,EAA6BC,EAAAA,QACjC,KAAO,CACL,KAAM/H,EACN,YAAAmF,EACA,cAAAE,EACA,aAAAE,EACA,YAAAE,EACA,MAAOM,EAAa/F,EAAY,EAAE,CAAA,GAEpC,CAACA,EAAamF,EAAaE,EAAeE,EAAcE,EAAaM,CAAY,CAAA,EAGnF,MAAO,CACL,KAAMQ,EAAS,KACf,SAAU3F,EAAS,SACnB,eAAA+E,GACA,YAAAmC,EACA,YAAavB,EAAS,YACtB,aAAAxB,EACA,iBAAkBwB,EAAS,OAC3B,iBAAkB3F,EAAS,SAG3B,eAAAwE,EACA,iBAAAE,EACA,gBAAAE,GAGA,cAAAyB,GACA,eAAAC,GACA,eAAAI,EACA,aAAAP,EACA,cAAAC,GACA,aAAAjB,EAGA,UAAA4B,EACA,UAAAC,EACA,UAAAC,EAGA,gBAAAL,EACA,UAAAvC,EAAA,CAEJ,CCnyBO,MAAM+C,EAAe,CAK1B,YAAY/N,EAA8B,CAH1C,KAAQ,YAA6B,KACrC,KAAQ,aAA8B,KAYtC,KAAA,aAAe,SAA6B,CAC1C,GAAI,CAAC,KAAK,YACR,MAAM,IAAI,MAAM,mBAAmB,EAErC,OAAO,KAAK,WACd,EAdE,KAAK,IAAMA,CACb,CAGA,gBAAgC,CAC9B,OAAO,KAAK,WACd,CAUA,MAAc,KAAQC,EAAc+N,EAA2B,CAE7D,MAAM1N,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB,GAAG,KAAK,IAAI,OAAO,GAAGL,CAAI,GAAI,CAChD,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAM,KAAK,UAAU+N,CAAI,CAAA,CAC1B,EACD,GAAI,CAAC1N,EAAI,GACP,MAAM,IAAI,MAAM,wBAAwBA,EAAI,MAAM,EAAE,EAEtD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,MAAMC,EAAiD,SAC3D,MAAM+H,EAAS,MAAM,KAAK,KAAuB,SAAU/H,CAAG,EAC9D,OAAI+H,EAAO,QACT,KAAK,YAAcA,EAAO,MAC1B,KAAK,aAAeA,EAAO,cAAgB,MAC3C5H,GAAAD,EAAA,KAAK,KAAI,WAAT,MAAAC,EAAA,KAAAD,EAAoB6H,EAAO,MAAOA,EAAO,cAAgB,KAEpDA,CACT,CAGA,MAAM,SAAqC,SACzC,GAAI,CAAC,KAAK,aACR,MAAM,IAAI,MAAM,kBAAkB,EAEpC,MAAMA,EAAS,MAAM,KAAK,KAAuB,WAAY,CAC3D,aAAc,KAAK,YAAA,CACpB,EACD,OAAIA,EAAO,QACT,KAAK,YAAcA,EAAO,MAC1B,KAAK,aAAeA,EAAO,cAAgB,KAAK,cAChD5H,GAAAD,EAAA,KAAK,KAAI,WAAT,MAAAC,EAAA,KAAAD,EAAoB6H,EAAO,MAAO,KAAK,eAElCA,CACT,CAGA,MAAM,IAAoC,CAExC,MAAMhI,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB,GAAG,KAAK,IAAI,OAAO,MAAO,CAC5C,QAAS,CAAE,cAAe,UAAU,KAAK,WAAW,EAAA,CAAG,CACxD,EACD,GAAI,CAACA,EAAI,GACP,MAAM,IAAI,MAAM,oBAAoBA,EAAI,MAAM,EAAE,EAElD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,QAAwB,CAC5B,GAAI,CAEF,MADU,KAAK,IAAI,OAAS,WAAW,OAC/B,GAAG,KAAK,IAAI,OAAO,UAAW,CACpC,OAAQ,OACR,QAAS,CAAE,cAAe,UAAU,KAAK,WAAW,EAAA,CAAG,CACxD,CACH,QAAA,CACE,KAAK,YAAc,KACnB,KAAK,aAAe,IACtB,CACF,CACF,CC5EO,MAAM2N,EAAe,CAG1B,YAAYjO,EAA8B,CACxC,KAAK,IAAMA,CACb,CAEA,MAAc,SAA2C,CACvD,MAAMkO,EAA4B,CAAA,EAClC,OAAI,KAAK,IAAI,eACXA,EAAE,cAAmB,UAAU,MAAM,KAAK,IAAI,cAAc,IAEvDA,CACT,CAGA,MAAM,OAAOC,EAAYC,EAAoD,CAC3E,MAAMjO,EAAI,KAAK,IAAI,OAAS,WAAW,MACjCkO,EAAO,IAAI,SACjBA,EAAK,OAAO,OAAQF,CAAI,EACpBC,GACFC,EAAK,OAAO,SAAUD,CAAM,EAE9B,MAAM9N,EAAM,MAAMH,EAAE,KAAK,IAAI,QAAS,CACpC,OAAQ,OACR,QAAS,MAAM,KAAK,QAAA,EACpB,KAAMkO,CAAA,CACP,EACD,GAAI,CAAC/N,EAAI,GACP,MAAM,IAAI,MAAM,uBAAuBA,EAAI,MAAM,EAAE,EAErD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,eAAegO,EAAeF,EAAsD,CACxF,MAAMjO,EAAI,KAAK,IAAI,OAAS,WAAW,MACjCkO,EAAO,IAAI,SACjBC,EAAM,QAASH,GAASE,EAAK,OAAO,QAASF,CAAI,CAAC,EAC9CC,GACFC,EAAK,OAAO,SAAUD,CAAM,EAE9B,MAAM9N,EAAM,MAAMH,EAAE,GAAG,KAAK,IAAI,OAAO,SAAU,CAC/C,OAAQ,OACR,QAAS,MAAM,KAAK,QAAA,EACpB,KAAMkO,CAAA,CACP,EACD,GAAI,CAAC/N,EAAI,GACP,MAAM,IAAI,MAAM,wBAAwBA,EAAI,MAAM,EAAE,EAEtD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,QAAQM,EAA+C,CAE3D,MAAMN,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB,GAAG,KAAK,IAAI,OAAO,IAAIM,CAAE,QAAS,CACpD,QAAS,CAAE,GAAI,MAAM,KAAK,QAAA,EAAY,eAAgB,kBAAA,CAAmB,CAC1E,EACD,GAAI,CAACN,EAAI,GACP,MAAM,IAAI,MAAM,qBAAqBA,EAAI,MAAM,EAAE,EAEnD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,eAAeM,EAA6B,CAEhD,MAAMN,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB,GAAG,KAAK,IAAI,OAAO,IAAIM,CAAE,GAAI,CAC/C,QAAS,MAAM,KAAK,QAAA,CAAQ,CAC7B,EACD,GAAI,CAACN,EAAI,GACP,MAAM,IAAI,MAAM,yBAAyBA,EAAI,MAAM,EAAE,EAEvD,MAAMiO,EAAO,MAAMjO,EAAI,KAAA,EACvB,OAAO,IAAI,gBAAgBiO,CAAI,CACjC,CAGA,MAAM,OAAO3N,EAAwC,CAEnD,MAAMN,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB,GAAG,KAAK,IAAI,OAAO,IAAIM,CAAE,GAAI,CAC/C,OAAQ,SACR,QAAS,CAAE,GAAI,MAAM,KAAK,QAAA,EAAY,eAAgB,kBAAA,CAAmB,CAC1E,EACD,GAAI,CAACN,EAAI,GACP,MAAM,IAAI,MAAM,uBAAuBA,EAAI,MAAM,EAAE,EAErD,OAAOA,EAAI,KAAA,CACb,CAMA,MAAM,mBACJ6N,EACAK,EACAJ,EACAK,EACmC,CACnC,GAAI,OAAO,eAAmB,IAC5B,OAAO,KAAK,OAAON,EAAMC,CAAM,EAGjC,MAAMhO,EAAU,MAAM,KAAK,QAAA,EAC3B,OAAO,IAAI,QAAQ,CAACsO,EAASC,IAAW,CACtC,MAAMC,EAAM,IAAI,eACVP,EAAO,IAAI,SAOjB,GANAA,EAAK,OAAO,OAAQF,CAAI,EACpBC,GACFC,EAAK,OAAO,SAAUD,CAAM,EAI1BK,EAAQ,CACV,GAAIA,EAAO,QAAS,CAClBE,EAAO,IAAI,aAAa,UAAW,YAAY,CAAC,EAChD,MACF,CACAF,EAAO,iBAAiB,QAAS,IAAM,CACrCG,EAAI,MAAA,EACJD,EAAO,IAAI,aAAa,UAAW,YAAY,CAAC,CAClD,CAAC,CACH,CAEAC,EAAI,OAAO,iBAAiB,WAAa7J,GAAM,CAC7CyJ,EAAW,CACT,OAAQzJ,EAAE,OACV,MAAOA,EAAE,MACT,QAASA,EAAE,iBAAmBA,EAAE,OAASA,EAAE,MAAQ,GAAA,CACpD,CACH,CAAC,EAED6J,EAAI,iBAAiB,OAAQ,IAAM,CACjC,GAAIA,EAAI,QAAU,KAAOA,EAAI,OAAS,IACpC,GAAI,CACFF,EAAQ,KAAK,MAAME,EAAI,YAAY,CAAC,CACtC,MAAQ,CACND,EAAO,IAAI,MAAM,uBAAuB,CAAC,CAC3C,MAEAA,EAAO,IAAI,MAAM,uBAAuBC,EAAI,MAAM,EAAE,CAAC,CAEzD,CAAC,EAEDA,EAAI,iBAAiB,QAAS,IAAMD,EAAO,IAAI,MAAM,6BAA6B,CAAC,CAAC,EACpFC,EAAI,iBAAiB,QAAS,IAAMD,EAAO,IAAI,aAAa,UAAW,YAAY,CAAC,CAAC,EAErFC,EAAI,KAAK,OAAQ,KAAK,IAAI,OAAO,EACjC,OAAO,QAAQxO,CAAO,EAAE,QAAQ,CAAC,CAACyO,EAAGC,CAAC,IAAMF,EAAI,iBAAiBC,EAAGC,CAAC,CAAC,EACtEF,EAAI,KAAKP,CAAI,CACf,CAAC,CACH,CAMA,MAAM,2BACJC,EACAE,EACAJ,EACAK,EACqC,CACrC,MAAMM,EAAsC,CAAA,EAC5C,QAAS3F,EAAI,EAAGA,EAAIkF,EAAM,OAAQlF,IAAK,CACrC,GAAIqF,GAAA,MAAAA,EAAQ,QACV,MAAM,IAAI,aAAa,UAAW,YAAY,EAEhD,MAAMnO,EAAM,MAAM,KAAK,mBACrBgO,EAAMlF,CAAC,EACN4F,GAAQR,EAAWpF,EAAG4F,CAAG,EAC1BZ,EACAK,CAAA,EAEFM,EAAQ,KAAKzO,CAAG,CAClB,CACA,OAAOyO,CACT,CACF,CChMO,MAAME,EAAiB,CAG5B,YAAYjP,EAAgC,CAC1C,KAAK,IAAMA,CACb,CAEA,MAAc,SAA2C,CACvD,MAAMkO,EAA4B,CAAE,eAAgB,kBAAA,EACpD,OAAI,KAAK,IAAI,eACXA,EAAE,cAAmB,UAAU,MAAM,KAAK,IAAI,cAAc,IAEvDA,CACT,CAGA,MAAM,cAAc3N,EAAgE,CAElF,MAAMD,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB,KAAK,IAAI,QAAS,CACpC,OAAQ,OACR,QAAS,MAAM,KAAK,QAAA,EACpB,KAAM,KAAK,UAAUC,CAAG,CAAA,CACzB,EACD,GAAI,CAACD,EAAI,GACP,MAAM,IAAI,MAAM,0BAA0BA,EAAI,MAAM,EAAE,EAExD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,SAAS4O,EAAoC,CAEjD,MAAM5O,EAAM,MADF,KAAK,IAAI,OAAS,WAAW,OACnB4O,EAAa,CAC/B,QAAS,MAAM,KAAK,QAAA,CAAQ,CAC7B,EACD,GAAI,CAAC5O,EAAI,GACP,MAAM,IAAI,MAAM,2BAA2BA,EAAI,MAAM,EAAE,EAEzD,OAAOA,EAAI,KAAA,CACb,CAGA,MAAM,eAAeC,EAAuB4O,EAAkC,OAC5E,MAAM7G,EAAS,MAAM,KAAK,cAAc/H,CAAG,EAC3C,GAAI,GAACE,EAAA6H,EAAO,OAAP,MAAA7H,EAAa,aAChB,MAAM,IAAI,MAAM,0BAA0B,EAE5C,MAAM8N,EAAO,MAAM,KAAK,SAASjG,EAAO,KAAK,WAAW,EAClD8G,EAAM,IAAI,gBAAgBb,CAAI,EAC9Bc,EAAI,SAAS,cAAc,GAAG,EACpCA,EAAE,KAAOD,EACTC,EAAE,SAAWF,GAAY,UAAU5O,EAAI,MAAM,GAC7C,SAAS,KAAK,YAAY8O,CAAC,EAC3BA,EAAE,MAAA,EACF,SAAS,KAAK,YAAYA,CAAC,EAC3B,IAAI,gBAAgBD,CAAG,CACzB,CACF,CCrCO,SAASE,GACdC,EAC4C,CAC5C,OAAQC,GAA6B,CACnC,MAAMC,EAAYD,GAAa,WAAW,MAwB1C,MAtBmC,CAACE,EAAOxP,IAAU,CAQnD,MAAMyP,EAAyB,CAAE,IAN/B,OAAOD,GAAU,SACbA,EACAA,aAAiB,IACfA,EAAM,SAAA,EACLA,EAAkB,IAEW,KADNxP,GAAQ,CAAA,EACgB,KAAM,EAAC,EAG/D,IAAIwB,EAAW+L,GAA4CgC,EAAUhC,EAAE,IAAKA,EAAE,IAAI,EAElF,QAASrE,EAAImG,EAAY,OAAS,EAAGnG,GAAK,EAAGA,IAAK,CAChD,MAAMwG,EAAKL,EAAYnG,CAAC,EAClByG,EAAcnO,EACpBA,EAAW+L,GAAMmC,EAAGnC,EAAGoC,CAAW,CACpC,CAEA,OAAOnO,EAAQiO,CAAG,CACpB,CAGF,CACF,CAKO,SAASG,IAAmC,CACjD,MAAO,OAAOH,EAAKpL,IAAS,CAC1B,MAAMwL,EAAQ,YAAY,IAAA,EACpBpO,GAAUgO,EAAI,KAAK,QAAU,OAAO,YAAA,EAC1C,GAAI,CACF,MAAMrP,EAAM,MAAMiE,EAAKoL,CAAG,EACpBK,GAAM,YAAY,IAAA,EAAQD,GAAO,QAAQ,CAAC,EAChD,eAAQ,MAAM,SAASpO,CAAM,IAAIgO,EAAI,GAAG,MAAMrP,EAAI,MAAM,KAAK0P,CAAE,KAAK,EAC7D1P,CACT,OAAS2P,EAAK,CACZ,MAAMD,GAAM,YAAY,IAAA,EAAQD,GAAO,QAAQ,CAAC,EAChD,cAAQ,MAAM,SAASpO,CAAM,IAAIgO,EAAI,GAAG,YAAYK,CAAE,MAAOC,CAAG,EAC1DA,CACR,CACF,CACF,CAYO,SAASC,GAAgBC,EAAoC,CAClE,MAAMC,GAAaD,GAAA,YAAAA,EAAM,aAAc,EACjCE,GAAYF,GAAA,YAAAA,EAAM,YAAa,IAC/BG,EAAU,IAAI,KAAIH,GAAA,YAAAA,EAAM,UAAW,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,GAAG,CAAC,EAEvE,MAAO,OAAOR,EAAKpL,IAAS,CAC1B,IAAIgM,EACJ,QAASC,EAAU,EAAGA,GAAWJ,EAAYI,IAAW,CACtD,GAAI,CACF,MAAMlQ,EAAM,MAAMiE,EAAKoL,CAAG,EAC1B,GAAIrP,EAAI,IAAM,CAACgQ,EAAQ,IAAIhQ,EAAI,MAAM,GAAKkQ,IAAYJ,EACpD,OAAO9P,EAGTiQ,EAAY,IAAI,MAAM,QAAQjQ,EAAI,MAAM,EAAE,CAC5C,OAAS2P,EAAK,CAEZ,GADAM,EAAYN,EACRO,IAAYJ,EACd,MAAMH,CAEV,CAEA,MAAMQ,EAAQJ,EAAY,KAAK,IAAI,EAAGG,CAAO,GAAK,GAAM,KAAK,OAAA,EAAW,IACxE,MAAM,IAAI,QAASrD,GAAM,WAAWA,EAAGsD,CAAK,CAAC,CAC/C,CACA,MAAMF,CACR,CACF,CAGO,SAASG,GACdtQ,EAGe,CACf,MAAO,OAAOuP,EAAKpL,IAAS,CAC1B,MAAMoM,EAAQ,OAAOvQ,GAAY,WAAa,MAAMA,IAAYA,EAChE,OAAAuP,EAAI,KAAK,QAAU,CAAE,GAAIA,EAAI,KAAK,QAAoC,GAAGgB,CAAA,EAClEpM,EAAKoL,CAAG,CACjB,CACF,CCzGO,MAAMiB,EAAe,CAY1B,YAAY5Q,EAA+B,CAN3C,KAAQ,SAAW,EACnB,KAAQ,WAAuB,CAAA,EAC/B,KAAQ,MAAsB,CAAA,EAC9B,KAAQ,WAAa,GACrB,KAAQ,WAAa,GAGnB,KAAK,eAAgBA,GAAA,YAAAA,EAAQ,gBAAiB,EAC9C,KAAK,cAAeA,GAAA,YAAAA,EAAQ,eAAgB,GAC5C,KAAK,cAAeA,GAAA,YAAAA,EAAQ,eAAgB,IAC5C,KAAK,WAAYA,GAAA,YAAAA,EAAQ,YAAa,WAAW,MAAM,KAAK,UAAU,EAGtE,KAAK,MAAQ,KAAK,MAAM,KAAK,IAAI,CACnC,CAGA,MAAM0P,EAA0BxP,EAAuC,CACrE,OAAO,IAAI,QAAkB,CAACwO,EAASC,IAAW,CAChD,KAAK,MAAM,KAAK,CAAE,QAAAD,EAAS,OAAAC,EAAQ,MAAAe,EAAO,KAAAxP,EAAM,EAChD,KAAK,MAAA,CACP,CAAC,CACH,CAGA,IAAI,SAAkB,CACpB,OAAO,KAAK,MAAM,MACpB,CAGA,IAAI,QAAiB,CACnB,OAAO,KAAK,QACd,CAEQ,OAAc,CACpB,GAAI,KAAK,WACP,OAEF,KAAK,WAAa,GAElB,MAAM2Q,EAAU,IAAM,CACpB,GAAI,KAAK,MAAM,SAAW,GAAK,KAAK,WAAY,CAC9C,KAAK,WAAa,GAClB,MACF,CAGA,GAAI,KAAK,UAAY,KAAK,cAAe,CACvC,KAAK,WAAa,GAClB,MACF,CAGA,MAAMlE,EAAM,KAAK,IAAA,EAEjB,GADA,KAAK,WAAa,KAAK,WAAW,OAAQmE,GAAMnE,EAAMmE,EAAI,GAAI,EAC1D,KAAK,WAAW,QAAU,KAAK,aAAc,CAC/C,MAAMC,EAAiB,KAAK,WAAW,CAAC,EAClCC,EAAS,KAAQrE,EAAMoE,GAAkB,EAC/C,WAAW,IAAM,CACf,KAAK,WAAa,GAClB,KAAK,MAAA,CACP,EAAGC,CAAM,EACT,MACF,CAEA,MAAMC,EAAQ,KAAK,MAAM,MAAA,EACzB,KAAK,WACL,KAAK,WAAW,KAAKtE,CAAG,EAExB,KAAK,UAAUsE,EAAM,MAAOA,EAAM,IAAI,EACnC,KAAM3Q,GAAQ,CAEb,GADA,KAAK,WACDA,EAAI,SAAW,IAAK,CAEtB,KAAK,MAAM,QAAQ2Q,CAAK,EACxB,KAAK,WACL,KAAK,WACL,KAAK,WAAa,GAClB,WAAW,IAAM,CACf,KAAK,WAAa,GAClB,KAAK,MAAA,CACP,EAAG,KAAK,YAAY,EACpB,MACF,CACAA,EAAM,QAAQ3Q,CAAG,EAEjB,eAAeuQ,CAAO,CACxB,CAAC,EACA,MAAOZ,GAAQ,CACd,KAAK,WACLgB,EAAM,OAAOhB,CAAG,EAChB,eAAeY,CAAO,CACxB,CAAC,EAGH,eAAeA,CAAO,CACxB,EAEAA,EAAA,CACF,CACF,CC3GO,MAAMK,EAAgF,CAQ3F,YAAYnP,EAA4BE,EAAW,KAAM,CANzD,KAAQ,MAAa,CAAA,EAErB,KAAQ,UAAkC,CAAA,EAC1C,KAAQ,cAAgB,IACxB,KAAQ,eAAiB,EAGvB,KAAK,QAAUF,EACf,KAAK,SAAWE,CAClB,CAGA,UAAUkP,EAA6C,CACrD,YAAK,UAAU,IAAIA,CAAQ,EACpB,IAAM,KAAK,UAAU,OAAOA,CAAQ,CAC7C,CAEQ,QAAe,CACrB,MAAMC,EAAW,CAAC,GAAG,KAAK,KAAK,EACzBnN,EAAU,CAAC,GAAG,KAAK,SAAS,EAClC,KAAK,UAAU,QAASoN,GAAOA,EAAGD,EAAUnN,CAAO,CAAC,CACtD,CAGA,UAAgB,CACd,MAAO,CAAC,GAAG,KAAK,KAAK,CACvB,CAGA,YAAmC,CACjC,OAAO,KAAK,UAAU,OAAQqN,GAAMA,EAAE,SAAW,SAAS,CAC5D,CAGA,SAASvQ,EAAkB,CACzB,KAAK,MAAQ,CAAC,GAAGA,CAAK,EACtB,KAAK,OAAA,CACP,CAGA,MAAM,QAAQa,EAAwE,CACpF,MAAMtB,EAAM,MAAM,KAAK,QAAQ,KAAK,GAAGsB,CAAI,EAC3C,OAAItB,EAAI,UACN,KAAK,MAAQA,EAAI,KACjB,KAAK,UAAY,CAAA,EACjB,KAAK,OAAA,GAEAA,CACT,CAGA,MAAM,OAAOO,EAA2C,CACtD,MAAM0Q,EAAU,UAAU,KAAK,cAAc,GACvCC,EAAa,CAAE,GAAG3Q,EAAM,CAAC,KAAK,QAAQ,EAAG0Q,CAAA,EAEzCE,EAA+B,CACnC,GAFY,OAAO,KAAK,gBAAgB,EAGxC,KAAM,SACN,eAAgBD,EAChB,UAAW,KAAK,IAAA,EAChB,OAAQ,SAAA,EAIV,KAAK,MAAQ,CAAC,GAAG,KAAK,MAAOA,CAAU,EACvC,KAAK,UAAU,KAAKC,CAAQ,EAC5B,KAAK,OAAA,EAEL,GAAI,CACF,MAAMnR,EAAM,MAAM,KAAK,QAAQ,OAAOO,CAAI,EAC1C,OAAIP,EAAI,SAEN,KAAK,MAAQ,KAAK,MAAM,IAAKoR,GAC1BA,EAA+B,KAAK,QAAQ,IAAMH,EAAUjR,EAAI,KAAOoR,CAAA,EAE1ED,EAAS,OAAS,YAClBA,EAAS,SAAYnR,EAAI,KAAiC,KAAK,QAAQ,IAGvE,KAAK,MAAQ,KAAK,MAAM,OACrBoR,GAAQA,EAA+B,KAAK,QAAQ,IAAMH,CAAA,EAE7DE,EAAS,OAAS,SAClBA,EAAS,MAAQnR,EAAI,OAEvB,KAAK,OAAA,EACEA,CACT,OAAS2P,EAAK,CAEZ,WAAK,MAAQ,KAAK,MAAM,OACrByB,GAAQA,EAA+B,KAAK,QAAQ,IAAMH,CAAA,EAE7DE,EAAS,OAAS,SAClBA,EAAS,MAAQxB,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAChE,KAAK,OAAA,EACCA,CACR,CACF,CAGA,MAAM,OAAOrP,EAAqBmD,EAA8C,CAC9E,MAAM4N,EAAM,KAAK,MAAM,UAAWD,GAAQA,EAA+B,KAAK,QAAQ,IAAM9Q,CAAE,EACxFgR,EAAWD,GAAO,EAAI,CAAE,GAAG,KAAK,MAAMA,CAAG,CAAA,EAAM,OAE/CF,EAA+B,CACnC,GAFY,OAAO,KAAK,gBAAgB,EAGxC,KAAM,SACN,SAAU7Q,EACV,aAAcgR,EACd,UAAW,KAAK,IAAA,EAChB,OAAQ,SAAA,EAIND,GAAO,IACT,KAAK,MAAQ,KAAK,MAAM,IAAI,CAACD,EAAItI,IAAOA,IAAMuI,EAAM,CAAE,GAAGD,EAAI,GAAG3N,CAAA,EAAY2N,CAAG,GAEjF,KAAK,UAAU,KAAKD,CAAQ,EAC5B,KAAK,OAAA,EAEL,GAAI,CACF,MAAMnR,EAAM,MAAM,KAAK,QAAQ,OAAOM,EAAImD,CAAO,EACjD,OAAIzD,EAAI,SAEN,KAAK,MAAQ,KAAK,MAAM,IAAKoR,GAC1BA,EAA+B,KAAK,QAAQ,IAAM9Q,EAAKN,EAAI,KAAOoR,CAAA,EAErED,EAAS,OAAS,cAGdG,GAAYD,GAAO,IACrB,KAAK,MAAQ,KAAK,MAAM,IAAKD,GAC1BA,EAA+B,KAAK,QAAQ,IAAM9Q,EAAKgR,EAAWF,CAAA,GAGvED,EAAS,OAAS,SAClBA,EAAS,MAAQnR,EAAI,OAEvB,KAAK,OAAA,EACEA,CACT,OAAS2P,EAAK,CAEZ,MAAI2B,GAAYD,GAAO,IACrB,KAAK,MAAQ,KAAK,MAAM,IAAKD,GAC1BA,EAA+B,KAAK,QAAQ,IAAM9Q,EAAKgR,EAAWF,CAAA,GAGvED,EAAS,OAAS,SAClBA,EAAS,MAAQxB,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAChE,KAAK,OAAA,EACCA,CACR,CACF,CAGA,MAAM,OAAOrP,EAAiD,CAC5D,MAAM+Q,EAAM,KAAK,MAAM,UAAWD,GAAQA,EAA+B,KAAK,QAAQ,IAAM9Q,CAAE,EACxFgR,EAAWD,GAAO,EAAI,CAAE,GAAG,KAAK,MAAMA,CAAG,CAAA,EAAM,OAE/CF,EAA+B,CACnC,GAFY,OAAO,KAAK,gBAAgB,EAGxC,KAAM,SACN,SAAU7Q,EACV,aAAcgR,EACd,UAAW,KAAK,IAAA,EAChB,OAAQ,SAAA,EAIV,KAAK,MAAQ,KAAK,MAAM,OAAQF,GAAQA,EAA+B,KAAK,QAAQ,IAAM9Q,CAAE,EAC5F,KAAK,UAAU,KAAK6Q,CAAQ,EAC5B,KAAK,OAAA,EAEL,GAAI,CACF,MAAMnR,EAAM,MAAM,KAAK,QAAQ,OAAOM,CAAE,EACxC,OAAIN,EAAI,QACNmR,EAAS,OAAS,aAGdG,GACF,KAAK,MAAM,OAAOD,EAAK,EAAGC,CAAQ,EAEpCH,EAAS,OAAS,SAClBA,EAAS,MAAQnR,EAAI,OAEvB,KAAK,OAAA,EACEA,CACT,OAAS2P,EAAK,CACZ,MAAI2B,GACF,KAAK,MAAM,OAAOD,EAAK,EAAGC,CAAQ,EAEpCH,EAAS,OAAS,SAClBA,EAAS,MAAQxB,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,EAChE,KAAK,OAAA,EACCA,CACR,CACF,CACF,CC3LO,MAAM4B,EAAgB,CAUzB,YAAY7R,EAAgC,CAR5C,KAAQ,MAAsB,CAAA,EAE9B,KAAQ,QAAU,GAElB,KAAQ,OAAS,EAKb,KAAK,OAASA,GAAU,CAAA,EACxB,KAAK,WAAYA,GAAA,YAAAA,EAAQ,YAAa,WAAW,MAAM,KAAK,UAAU,EACtE,KAAK,YAAaA,GAAA,YAAAA,EAAQ,aAAc,eAExC,KAAK,MAAQ,KAAK,MAAM,KAAK,IAAI,EAGjC,KAAK,aAAA,EAGL,KAAK,YAAc,IAAM,UACrBU,GAAAD,EAAA,KAAK,QAAO,iBAAZ,MAAAC,EAAA,KAAAD,EAA6B,IAC7B,KAAK,KAAA,CACT,EACA,KAAK,aAAe,IAAM,UACtBC,GAAAD,EAAA,KAAK,QAAO,iBAAZ,MAAAC,EAAA,KAAAD,EAA6B,GACjC,EAEI,OAAO,OAAW,MAClB,OAAO,iBAAiB,SAAU,KAAK,WAAW,EAClD,OAAO,iBAAiB,UAAW,KAAK,YAAY,EAE5D,CAGA,MAAMiP,EAA0BxP,EAAuC,SACnE,MAAMkP,EAAM,OAAOM,GAAU,SAAWA,EAAQA,aAAiB,IAAMA,EAAM,SAAA,EAAcA,EAAkB,IACvG/N,IAAUzB,GAAA,YAAAA,EAAM,SAAU,OAAO,YAAA,EAGvC,GAAI,KAAK,WACL,OAAO,KAAK,UAAUwP,EAAOxP,CAAI,EAIrC,GAAIyB,IAAW,OAASA,IAAW,OAC/B,OAAO,QAAQ,OAAO,IAAI,MAAM,sCAAsC,CAAC,EAI3E,MAAMsP,EAAoB,CACtB,GAAI,KAAK,KAAK,QAAQ,GACtB,IAAA7B,EACA,OAAAzN,EACA,QAAS,CAAE,GAAIzB,GAAA,YAAAA,EAAM,OAAA,EACrB,KAAM,OAAOA,GAAA,YAAAA,EAAM,OAAS,SAAWA,EAAK,KAAO,OACnD,UAAW,KAAK,IAAA,CAAI,EAGxB,YAAK,MAAM,KAAK+Q,CAAK,EACrB,KAAK,aAAA,GACLvQ,GAAAD,EAAA,KAAK,QAAO,WAAZ,MAAAC,EAAA,KAAAD,EAAuBwQ,GAGhB,QAAQ,QAAQ,IAAI,SACvB,KAAK,UAAU,CAAE,OAAQ,GAAM,QAASA,EAAM,GAAI,EAClD,CAAE,OAAQ,IAAK,QAAS,CAAE,eAAgB,mBAAmB,CAAE,CAClE,CACL,CAGA,UAAoB,CAChB,OAAO,OAAO,UAAc,KAAe,UAAU,MACzD,CAGA,IAAI,cAAuB,CACvB,OAAO,KAAK,MAAM,MACtB,CAGA,UAAyB,CACrB,MAAO,CAAC,GAAG,KAAK,KAAK,CACzB,CAGA,OAAc,CACV,KAAK,MAAQ,CAAA,EACb,KAAK,aAAA,CACT,CAGA,MAAM,MAA8B,SAChC,GAAI,KAAK,SAAW,KAAK,MAAM,SAAW,GAAK,CAAC,KAAK,WACjD,MAAO,CAAA,EAEX,KAAK,QAAU,GAEf,MAAMlC,EAAwB,CAAA,EAE9B,KAAO,KAAK,MAAM,OAAS,GAAK,KAAK,YAAY,CAC7C,MAAMkC,EAAQ,KAAK,MAAM,CAAC,EAC1B,GAAI,CACA,MAAM3Q,EAAM,MAAM,KAAK,UAAU2Q,EAAM,IAAK,CACxC,OAAQA,EAAM,OACd,QAASA,EAAM,QACf,KAAMA,EAAM,IAAA,CACf,EACDlC,EAAQ,KAAK,CACT,MAAAkC,EACA,QAAS3Q,EAAI,GACb,OAAQA,EAAI,OACZ,MAAOA,EAAI,GAAK,OAAY,QAAQA,EAAI,MAAM,EAAA,CACjD,EAED,KAAK,MAAM,MAAA,CACf,OAAS2P,EAAK,CACVlB,EAAQ,KAAK,CACT,MAAAkC,EACA,QAAS,GACT,MAAOhB,aAAe,MAAQA,EAAI,QAAU,OAAOA,CAAG,CAAA,CACzD,EAED,KACJ,CACJ,CAEA,YAAK,aAAA,EACL,KAAK,QAAU,GACXlB,EAAQ,OAAS,KACjBrO,GAAAD,EAAA,KAAK,QAAO,SAAZ,MAAAC,EAAA,KAAAD,EAAqBsO,IAElBA,CACX,CAGA,SAAgB,CACR,OAAO,OAAW,MAClB,OAAO,oBAAoB,SAAU,KAAK,WAAW,EACrD,OAAO,oBAAoB,UAAW,KAAK,YAAY,EAE/D,CAEQ,cAAqB,CACzB,GAAI,CACI,OAAO,aAAiB,KACxB,aAAa,QACT,GAAG,KAAK,UAAU,QAClB,KAAK,UAAU,KAAK,KAAK,CAAA,CAGrC,MAAQ,CAER,CACJ,CAEQ,cAAqB,CACzB,GAAI,CACA,GAAI,OAAO,aAAiB,IAAa,CACrC,MAAM+C,EAAM,aAAa,QAAQ,GAAG,KAAK,UAAU,OAAO,EACtDA,IACA,KAAK,MAAQ,KAAK,MAAMA,CAAG,EAC3B,KAAK,OAAS,KAAK,MAAM,OACrB,CAACC,EAAKhN,IAAM,KAAK,IAAIgN,EAAK,SAAShN,EAAE,GAAG,QAAQ,KAAM,EAAE,EAAG,EAAE,GAAK,CAAC,EACnE,CAAA,EACA,EAEZ,CACJ,MAAQ,CACJ,KAAK,MAAQ,CAAA,CACjB,CACJ,CACJ,CCjNO,MAAMiN,GAA+C,CACxD,CACI,KAAM,kBACN,MAAO,eACP,SAAU,QACV,KAAM,QACN,aAAc,CAAE,IAAK,GAAI,SAAU,GAAO,aAAc,EAAA,EACxD,gBAAiB,CACb,CAAE,KAAM,MAAO,KAAM,SAAU,MAAO,kBAAA,EACtC,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EACvE,CAAE,KAAM,OAAQ,KAAM,UAAW,MAAO,OAAQ,aAAc,EAAA,EAC9D,CAAE,KAAM,eAAgB,KAAM,UAAW,MAAO,gBAAiB,aAAc,EAAA,EAC/E,CAAE,KAAM,eAAgB,KAAM,UAAW,MAAO,gBAAiB,aAAc,EAAA,EAC/E,CAAE,KAAM,iBAAkB,KAAM,SAAU,MAAO,kBAAmB,QAAS,CACzE,CAAE,MAAO,OAAQ,MAAO,MAAA,EACxB,CAAE,MAAO,OAAQ,MAAO,MAAA,EACxB,CAAE,MAAO,OAAQ,MAAO,MAAA,EACxB,CAAE,MAAO,SAAU,MAAO,QAAA,CAAS,EACpC,aAAc,MAAA,EACjB,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,SAAU,MAAO,QAAA,CAAS,CACrE,EAEJ,CACI,KAAM,eACN,MAAO,WACP,SAAU,QACV,KAAM,WACN,aAAc,CAAE,MAAO,GAAI,MAAO,SAAA,EAClC,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,OAAQ,MAAO,wBAAA,EACtC,CAAE,KAAM,QAAS,KAAM,QAAS,MAAO,iBAAkB,aAAc,SAAA,EACvE,CAAE,KAAM,gBAAiB,KAAM,QAAS,MAAO,iBAAkB,aAAc,SAAA,EAC/E,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,iBAAkB,IAAK,EAAG,IAAK,GAAI,KAAM,EAAG,aAAc,CAAA,EACrG,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,SAAU,MAAO,QAAA,EACxD,CAAE,KAAM,cAAe,KAAM,UAAW,MAAO,gBAAiB,aAAc,EAAA,CAAK,CACvF,EAEJ,CACI,KAAM,gBACN,MAAO,aACP,SAAU,QACV,KAAM,QACN,aAAc,CAAE,MAAO,CAAA,EAAI,QAAS,EAAG,YAAa,CAAA,EACpD,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,OAAQ,MAAO,mBAAA,EACtC,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,kBAAmB,IAAK,EAAG,IAAK,EAAG,aAAc,CAAA,EAC3F,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,eAAgB,IAAK,EAAG,IAAK,EAAG,aAAc,CAAA,EAC5F,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,WAAY,QAAS,CAC5D,CAAE,MAAO,MAAO,MAAO,GAAA,EACvB,CAAE,MAAO,MAAO,MAAO,GAAA,EACvB,CAAE,MAAO,OAAQ,MAAO,IAAA,EACxB,CAAE,MAAO,OAAQ,MAAO,IAAA,CAAK,EAC9B,aAAc,IAAA,EACjB,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EACvE,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,SAAU,MAAO,QAAA,CAAS,CACrE,EAEJ,CACI,KAAM,cACN,MAAO,UACP,SAAU,QACV,KAAM,aACN,aAAc,CAAE,OAAQ,GAAI,SAAU,EAAA,EACtC,gBAAiB,CACb,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,kBAAA,EAC3C,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,qCAAA,EACvC,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,aAAc,aAAc,EAAA,EACzE,CAAE,KAAM,iBAAkB,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EACnF,CAAE,KAAM,iBAAkB,KAAM,QAAS,MAAO,kBAAmB,aAAc,SAAA,EACjF,CAAE,KAAM,WAAY,KAAM,OAAQ,MAAO,YAAa,MAAO,YAAA,CAAa,CAC9E,CAER,ECxEaC,GAA8C,CACvD,CACI,KAAM,gBACN,MAAO,aACP,SAAU,OACV,KAAM,SACN,aAAc,CAAE,aAAc,GAAM,mBAAoB,EAAA,EACxD,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,aAAc,aAAc,SAAA,EACpE,CAAE,KAAM,eAAgB,KAAM,UAAW,MAAO,qBAAsB,aAAc,EAAA,EACpF,CAAE,KAAM,qBAAsB,KAAM,UAAW,MAAO,yBAA0B,aAAc,EAAA,EAC9F,CAAE,KAAM,mBAAoB,KAAM,UAAW,MAAO,qBAAsB,aAAc,EAAA,EACxF,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EACvE,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,WAAY,MAAO,UAAA,EAC7D,CAAE,KAAM,eAAgB,KAAM,QAAS,MAAO,gBAAiB,aAAc,UAAW,MAAO,UAAA,EAC/F,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,eAAgB,aAAc,MAAA,CAAO,CACpF,EAEJ,CACI,KAAM,cACN,MAAO,UACP,SAAU,OACV,KAAM,SACN,aAAc,CAAE,SAAU,YAAa,KAAM,QAAA,EAC7C,gBAAiB,CACb,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,WAAY,QAAS,CAC5D,CAAE,MAAO,eAAgB,MAAO,WAAA,EAChC,CAAE,MAAO,eAAgB,MAAO,cAAA,EAChC,CAAE,MAAO,WAAY,MAAO,UAAA,EAC5B,CAAE,MAAO,YAAa,MAAO,WAAA,CAAY,EAC1C,aAAc,WAAA,EACjB,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,UAAA,EAC1C,CAAE,KAAM,OAAQ,KAAM,SAAU,MAAO,OAAQ,QAAS,CACpD,CAAE,MAAO,SAAU,MAAO,QAAA,EAC1B,CAAE,MAAO,UAAW,MAAO,SAAA,EAC3B,CAAE,MAAO,YAAa,MAAO,WAAA,CAAY,EAC1C,aAAc,QAAA,EACjB,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,QAAS,QAAS,CACtD,CAAE,MAAO,QAAS,MAAO,OAAA,EACzB,CAAE,MAAO,OAAQ,MAAO,MAAA,CAAO,EAChC,aAAc,OAAA,CAAQ,CAC7B,EAEJ,CACI,KAAM,eACN,MAAO,YACP,SAAU,OACV,KAAM,MACN,aAAc,CAAE,OAAQ,MAAA,EACxB,gBAAiB,CACb,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,aAAc,QAAS,CAC5D,CAAE,MAAO,uBAAwB,MAAO,MAAA,EACxC,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,QAAS,MAAO,OAAA,CAAQ,EAClC,aAAc,MAAA,EACjB,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,cAAe,aAAc,UAAA,EACtE,CAAE,KAAM,kBAAmB,KAAM,UAAW,MAAO,oBAAqB,aAAc,EAAA,EACtF,CAAE,KAAM,SAAU,KAAM,UAAW,MAAO,eAAgB,aAAc,EAAA,EACxE,CAAE,KAAM,aAAc,KAAM,SAAU,MAAO,cAAe,IAAK,EAAG,IAAK,EAAG,aAAc,CAAA,CAAE,CAChG,CAER,EC7DaC,GAAkD,CAC3D,CACI,KAAM,cACN,MAAO,iBACP,SAAU,WACV,KAAM,UACN,aAAc,CAAE,QAAS,IAAA,EACzB,gBAAiB,CACb,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,SAAU,MAAO,SAAA,EAC1D,CAAE,KAAM,OAAQ,KAAM,SAAU,MAAO,OAAQ,MAAO,SAAA,EACtD,CAAE,KAAM,aAAc,KAAM,SAAU,MAAO,cAAe,MAAO,SAAA,EACnE,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,UAAW,QAAS,CAC1D,CAAE,MAAO,SAAU,MAAO,IAAA,EAC1B,CAAE,MAAO,UAAW,MAAO,IAAA,EAC3B,CAAE,MAAO,MAAO,MAAO,IAAA,EACvB,CAAE,MAAO,KAAM,MAAO,IAAA,EACtB,CAAE,MAAO,SAAU,MAAO,IAAA,CAAK,EAChC,aAAc,KAAM,MAAO,SAAA,EAC9B,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EAC7E,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,EAEJ,CACI,KAAM,YACN,MAAO,cACP,SAAU,WACV,KAAM,QACN,aAAc,CAAE,YAAa,KAAA,EAC7B,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,cAAA,EACxC,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,uBAAwB,aAAc,KAAA,EACpF,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,oBAAqB,aAAc,EAAA,EAC/E,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,SAAU,QAAS,CACxD,CAAE,MAAO,gBAAiB,MAAO,eAAA,EACjC,CAAE,MAAO,WAAY,MAAO,UAAA,EAC5B,CAAE,MAAO,QAAS,MAAO,MAAA,CAAO,EACjC,aAAc,eAAA,EACjB,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,EAEJ,CACI,KAAM,eACN,MAAO,iBACP,SAAU,WACV,KAAM,cACN,aAAc,CAAE,SAAU,MAAO,SAAU,CAAA,EAC3C,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,SAAU,aAAc,CAAA,EAChE,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,WAAY,QAAS,CAC5D,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,MAAO,MAAO,KAAA,CAAM,EAC9B,aAAc,KAAA,EACjB,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,iBAAkB,IAAK,EAAG,IAAK,EAAG,aAAc,CAAA,EAC3F,CAAE,KAAM,qBAAsB,KAAM,UAAW,MAAO,cAAe,aAAc,EAAA,EACnF,CAAE,KAAM,oBAAqB,KAAM,UAAW,MAAO,qBAAsB,aAAc,EAAA,EACzF,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,EAEJ,CACI,KAAM,UACN,MAAO,YACP,SAAU,WACV,KAAM,UACN,aAAc,CAAE,KAAM,GAAI,QAAS,IAAA,EACnC,gBAAiB,CACb,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,YAAa,aAAc,EAAG,MAAO,QAAA,EAChF,CAAE,KAAM,OAAQ,KAAM,SAAU,MAAO,eAAgB,IAAK,EAAG,IAAK,IAAK,aAAc,GAAI,MAAO,QAAA,EAClG,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,UAAW,QAAS,CAC1D,CAAE,MAAO,SAAU,MAAO,IAAA,EAC1B,CAAE,MAAO,UAAW,MAAO,IAAA,EAC3B,CAAE,MAAO,SAAU,MAAO,IAAA,CAAK,EAChC,aAAc,IAAA,EACjB,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EAC/E,CAAE,KAAM,gBAAiB,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EAClF,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,EAEJ,CACI,KAAM,mBACN,MAAO,gBACP,SAAU,WACV,KAAM,YACN,aAAc,CAAE,MAAO,GAAI,SAAU,KAAA,EACrC,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,OAAQ,MAAO,sBAAA,EACtC,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,WAAY,QAAS,CAC5D,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,MAAO,MAAO,KAAA,CAAM,EAC9B,aAAc,KAAA,EACjB,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EAC5E,CAAE,KAAM,eAAgB,KAAM,UAAW,MAAO,uBAAwB,aAAc,EAAA,EACtF,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EAC9E,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,WAAY,aAAc,EAAA,EACtE,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,CAER,ECnGaC,GAAsD,CAC/D,CACI,KAAM,kBACN,MAAO,cACP,SAAU,eACV,KAAM,SACN,aAAc,CAAE,WAAY,GAAI,QAAS,EAAA,EACzC,gBAAiB,CACb,CAAE,KAAM,aAAc,KAAM,OAAQ,MAAO,oBAAA,EAC3C,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,mBAAoB,IAAK,EAAG,IAAK,IAAK,aAAc,EAAA,EAC9F,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EAC9E,CAAE,KAAM,aAAc,KAAM,UAAW,MAAO,cAAe,aAAc,EAAA,EAC3E,CAAE,KAAM,uBAAwB,KAAM,UAAW,MAAO,yBAA0B,aAAc,EAAA,EAChG,CAAE,KAAM,aAAc,KAAM,SAAU,MAAO,mBAAoB,aAAc,OAAA,EAC/E,CAAE,KAAM,YAAa,KAAM,SAAU,MAAO,kBAAmB,aAAc,MAAA,EAC7E,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,eAAgB,aAAc,SAAA,CAAU,CACvF,EAEJ,CACI,KAAM,kBACN,MAAO,oBACP,SAAU,eACV,KAAM,QACN,aAAc,CAAE,MAAO,cAAe,SAAU,EAAA,EAChD,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,oBAAqB,aAAc,aAAA,EAC3E,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,aAAA,EAC9C,CAAE,KAAM,OAAQ,KAAM,SAAU,MAAO,UAAA,EACvC,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,WAAY,aAAc,EAAA,EACtE,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,mBAAoB,IAAK,EAAG,IAAK,IAAK,aAAc,CAAA,EAC/F,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,SAAU,QAAS,CACxD,CAAE,MAAO,SAAU,MAAO,QAAA,EAC1B,CAAE,MAAO,OAAQ,MAAO,MAAA,EACxB,CAAE,MAAO,OAAQ,MAAO,MAAA,EACxB,CAAE,MAAO,YAAa,MAAO,WAAA,CAAY,EAC1C,aAAc,QAAA,EACjB,CAAE,KAAM,OAAQ,KAAM,OAAQ,MAAO,aAAc,MAAO,QAAA,CAAS,CACvE,EAEJ,CACI,KAAM,YACN,MAAO,gBACP,SAAU,eACV,KAAM,MACN,aAAc,CAAE,MAAO,QAAS,MAAO,CAAA,CAAC,EACxC,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,cAAe,aAAc,OAAA,EACrE,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,mBAAA,EAC9C,CAAE,KAAM,QAAS,KAAM,OAAQ,MAAO,0CAAA,EACtC,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,oBAAA,EACzC,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,YAAa,IAAK,EAAG,aAAc,GAAA,EAC9E,CAAE,KAAM,eAAgB,KAAM,UAAW,MAAO,oBAAqB,aAAc,EAAA,EACnF,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,eAAgB,aAAc,SAAA,CAAU,CACvF,EAEJ,CACI,KAAM,YACN,MAAO,kBACP,SAAU,eACV,KAAM,MACN,aAAc,CAAE,UAAW,EAAG,MAAO,IAAM,MAAO,CAAA,EAClD,gBAAiB,CACb,CAAE,KAAM,YAAa,KAAM,SAAU,MAAO,aAAc,IAAK,EAAG,aAAc,CAAA,EAChF,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,sBAAuB,IAAK,EAAG,aAAc,GAAA,EACrF,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,gBAAiB,IAAK,EAAG,aAAc,CAAA,EAC/E,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,gBAAiB,aAAc,EAAA,EAC5E,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EAC/E,CAAE,KAAM,QAAS,KAAM,QAAS,MAAO,YAAa,aAAc,SAAA,EAClE,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EAC7E,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,aAAc,MAAO,QAAA,CAAS,CACzE,CAER,ECxEaC,GAAiD,CAC1D,CACI,KAAM,kBACN,MAAO,eACP,SAAU,WACV,KAAM,OACN,aAAc,CAAE,MAAO,GAAI,OAAQ,GAAI,QAAS,EAAC,EACjD,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,oBAAqB,IAAK,EAAG,IAAK,IAAK,aAAc,EAAA,EAC7F,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,qBAAsB,IAAK,EAAG,IAAK,IAAK,aAAc,EAAA,EAC/F,CAAE,KAAM,UAAW,KAAM,OAAQ,MAAO,4BAAA,EACxC,CAAE,KAAM,cAAe,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EAC1E,CAAE,KAAM,OAAQ,KAAM,SAAU,MAAO,aAAc,IAAK,EAAG,IAAK,GAAI,KAAM,EAAG,aAAc,CAAA,EAC7F,CAAE,KAAM,SAAU,KAAM,UAAW,MAAO,gBAAiB,aAAc,EAAA,EACzE,CAAE,KAAM,QAAS,KAAM,UAAW,MAAO,yBAA0B,aAAc,EAAA,CAAM,CAC3F,EAEJ,CACI,KAAM,mBACN,MAAO,gBACP,SAAU,WACV,KAAM,WACN,aAAc,CAAE,MAAO,IAAK,OAAQ,GAAA,EACpC,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,eAAgB,IAAK,IAAK,IAAK,KAAM,aAAc,IAAK,MAAO,QAAA,EACvG,CAAE,KAAM,SAAU,KAAM,SAAU,MAAO,gBAAiB,IAAK,IAAK,IAAK,KAAM,aAAc,IAAK,MAAO,QAAA,EACzG,CAAE,KAAM,aAAc,KAAM,UAAW,MAAO,cAAe,aAAc,EAAA,EAC3E,CAAE,KAAM,aAAc,KAAM,UAAW,MAAO,eAAgB,aAAc,EAAA,EAC5E,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,YAAa,IAAK,EAAG,IAAK,IAAK,aAAc,EAAA,EACxF,CAAE,KAAM,YAAa,KAAM,QAAS,MAAO,eAAgB,aAAc,SAAA,EACzE,CAAE,KAAM,cAAe,KAAM,QAAS,MAAO,iBAAkB,aAAc,SAAA,EAC7E,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,eAAgB,IAAK,EAAG,IAAK,GAAI,KAAM,GAAK,aAAc,CAAA,CAAE,CAC9G,EAEJ,CACI,KAAM,kBACN,MAAO,eACP,SAAU,WACV,KAAM,QACN,aAAc,CAAE,IAAK,EAAA,EACrB,gBAAiB,CACb,CAAE,KAAM,MAAO,KAAM,SAAU,MAAO,kBAAA,EACtC,CAAE,KAAM,OAAQ,KAAM,UAAW,MAAO,aAAc,aAAc,EAAA,EACpE,CAAE,KAAM,SAAU,KAAM,UAAW,MAAO,eAAgB,aAAc,EAAA,EACxE,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,eAAgB,aAAc,EAAA,EACzE,CAAE,KAAM,cAAe,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EACjF,CAAE,KAAM,OAAQ,KAAM,UAAW,MAAO,qBAAsB,aAAc,EAAA,EAC5E,CAAE,KAAM,eAAgB,KAAM,SAAU,MAAO,gBAAiB,QAAS,CACrE,CAAE,MAAO,MAAO,MAAO,KAAA,EACvB,CAAE,MAAO,OAAQ,MAAO,MAAA,EACxB,CAAE,MAAO,OAAQ,MAAO,MAAA,CAAO,EAChC,aAAc,KAAA,EACjB,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,mBAAoB,IAAK,IAAK,IAAK,KAAM,MAAO,QAAA,CAAS,CACxG,CAER,ECvDaC,GAAgD,CACzD,CACI,KAAM,eACN,MAAO,WACP,SAAU,SACV,KAAM,iBACN,aAAc,CAAE,SAAU,GAAI,aAAc,EAAA,EAC5C,gBAAiB,CACb,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,uBAAA,EAC3C,CAAE,KAAM,eAAgB,KAAM,UAAW,MAAO,yBAA0B,aAAc,EAAA,EACxF,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,oBAAqB,IAAK,EAAG,IAAK,GAAI,aAAc,CAAA,EAC/F,CAAE,KAAM,YAAa,KAAM,SAAU,MAAO,eAAgB,QAAS,CACjE,CAAE,MAAO,eAAgB,MAAO,QAAA,EAChC,CAAE,MAAO,eAAgB,MAAO,QAAA,EAChC,CAAE,MAAO,aAAc,MAAO,SAAA,CAAU,EACzC,aAAc,QAAA,EACjB,CAAE,KAAM,iBAAkB,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EACnF,CAAE,KAAM,aAAc,KAAM,UAAW,MAAO,eAAgB,aAAc,EAAA,EAC5E,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,EAEJ,CACI,KAAM,cACN,MAAO,UACP,SAAU,SACV,KAAM,OACN,aAAc,CAAE,SAAU,EAAG,UAAW,EAAA,EACxC,gBAAiB,CACb,CAAE,KAAM,QAAS,KAAM,SAAU,MAAO,iBAAkB,IAAK,EAAG,IAAK,GAAI,KAAM,GAAK,aAAc,CAAA,EACpG,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,gBAAiB,IAAK,EAAG,IAAK,GAAI,aAAc,CAAA,EAC3F,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EAC/E,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,kBAAmB,aAAc,EAAA,EAC9E,CAAE,KAAM,cAAe,KAAM,UAAW,MAAO,eAAgB,aAAc,EAAA,EAC7E,CAAE,KAAM,QAAS,KAAM,QAAS,MAAO,aAAc,aAAc,SAAA,EACnE,CAAE,KAAM,OAAQ,KAAM,OAAQ,MAAO,YAAa,MAAO,QAAA,EACzD,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,CAAM,CACjF,EAEJ,CACI,KAAM,WACN,MAAO,cACP,SAAU,SACV,KAAM,OACN,aAAc,CAAE,QAAS,GAAI,QAAS,EAAA,EACtC,gBAAiB,CACb,CAAE,KAAM,UAAW,KAAM,SAAU,MAAO,4BAAA,EAC1C,CAAE,KAAM,UAAW,KAAM,UAAW,MAAO,yBAA0B,aAAc,EAAA,EACnF,CAAE,KAAM,cAAe,KAAM,UAAW,MAAO,uBAAwB,aAAc,EAAA,EACrF,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,gBAAiB,aAAc,EAAA,EAC5E,CAAE,KAAM,cAAe,KAAM,UAAW,MAAO,wBAAyB,aAAc,EAAA,EACtF,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,eAAgB,QAAS,CACnE,CAAE,MAAO,eAAgB,MAAO,OAAA,EAChC,CAAE,MAAO,aAAc,MAAO,MAAA,EAC9B,CAAE,MAAO,eAAgB,MAAO,MAAA,CAAO,EACxC,aAAc,OAAA,EACjB,CAAE,KAAM,YAAa,KAAM,SAAU,MAAO,qBAAsB,IAAK,EAAG,aAAc,CAAA,CAAE,CAC9F,CAER,EC1DaC,GAA+C,CACxD,CACI,KAAM,kBACN,MAAO,kBACP,SAAU,KACV,KAAM,OACN,aAAc,CAAE,SAAU,EAAA,EAC1B,gBAAiB,CACb,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,0BAAA,EAC3C,CAAE,KAAM,iBAAkB,KAAM,SAAU,MAAO,cAAe,QAAS,CACrE,CAAE,MAAO,SAAU,MAAO,QAAA,EAC1B,CAAE,MAAO,UAAW,MAAO,SAAA,EAC3B,CAAE,MAAO,YAAa,MAAO,WAAA,EAC7B,CAAE,MAAO,OAAQ,MAAO,MAAA,CAAO,EAChC,aAAc,QAAA,EACjB,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EACvE,CAAE,KAAM,WAAY,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EACvE,CAAE,KAAM,gBAAiB,KAAM,UAAW,MAAO,iBAAkB,aAAc,EAAA,EACjF,CAAE,KAAM,YAAa,KAAM,UAAW,MAAO,iBAAkB,aAAc,EAAA,EAC7E,CAAE,KAAM,kBAAmB,KAAM,QAAS,MAAO,aAAc,aAAc,SAAA,EAC7E,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,kBAAmB,MAAO,QAAA,CAAS,CAC9E,EAEJ,CACI,KAAM,kBACN,MAAO,kBACP,SAAU,KACV,KAAM,MACN,aAAc,CAAE,SAAU,GAAI,WAAY,EAAA,EAC1C,gBAAiB,CACb,CAAE,KAAM,WAAY,KAAM,SAAU,MAAO,0BAAA,EAC3C,CAAE,KAAM,aAAc,KAAM,UAAW,MAAO,cAAe,aAAc,EAAA,EAC3E,CAAE,KAAM,cAAe,KAAM,SAAU,MAAO,eAAgB,IAAK,GAAK,IAAK,GAAI,KAAM,GAAK,aAAc,CAAA,EAC1G,CAAE,KAAM,OAAQ,KAAM,UAAW,MAAO,aAAc,aAAc,EAAA,EACpE,CAAE,KAAM,MAAO,KAAM,UAAW,MAAO,YAAa,aAAc,EAAA,EAClE,CAAE,KAAM,iBAAkB,KAAM,SAAU,MAAO,cAAe,QAAS,CACrE,CAAE,MAAO,SAAU,MAAO,QAAA,EAC1B,CAAE,MAAO,UAAW,MAAO,SAAA,EAC3B,CAAE,MAAO,YAAa,MAAO,WAAA,EAC7B,CAAE,MAAO,OAAQ,MAAO,MAAA,CAAO,EAChC,aAAc,QAAA,EACjB,CAAE,KAAM,kBAAmB,KAAM,QAAS,MAAO,aAAc,aAAc,SAAA,EAC7E,CAAE,KAAM,kBAAmB,KAAM,UAAW,MAAO,mBAAoB,aAAc,EAAA,EACrF,CAAE,KAAM,SAAU,KAAM,OAAQ,MAAO,kBAAmB,MAAO,QAAA,CAAS,CAC9E,CAER,EC7BaC,GAA+C,CAC1D,GAAGP,GACH,GAAGI,GACH,GAAGE,GACH,GAAGH,GACH,GAAGD,GACH,GAAGD,GACH,GAAGI,EACL,ECwOA,eAAeG,GAAkBpD,EAAatN,EAAmC,CAC/E,MAAM2Q,EAAW,MAAM,MAAMrD,EAAK,CAChC,GAAGtN,EACH,QAAS,CACP,eAAgB,mBAChB,GAAGA,GAAA,YAAAA,EAAS,OAAA,CACd,CACD,EAED,GAAI,CAAC2Q,EAAS,GAAI,CAChB,MAAMC,EAAY,MAAMD,EAAS,KAAA,EACjC,IAAIE,EAA8C,CAAA,EAClD,GAAI,CACFA,EAAS,KAAK,MAAMD,CAAS,CAC/B,MAAQ,CAER,CACA,KAAM,CACJ,KAAMC,EAAO,MAAQ,QAAQF,EAAS,MAAM,GAC5C,QAASE,EAAO,SAAWF,EAAS,WACpC,WAAYA,EAAS,MAAA,CAEzB,CAEA,OAAOA,EAAS,KAAA,CAClB,CAMO,SAASG,GAAW5S,EAAyC,CAClE,KAAM,CACJ,QAAA6S,EACA,SAAAC,EAAW,MACX,QAAAxO,EAAU,IACV,QAAAyO,EAAUP,GACV,aAAAQ,EACA,QAAAC,CAAA,EACEjT,EAGE,CAACkT,EAAQC,CAAS,EAAIrQ,EAAAA,SAAsB,CAAA,CAAE,EAC9C,CAACsQ,EAAeC,CAAgB,EAAIvQ,EAAAA,SAA2B,IAAI,EACnE,CAACwQ,EAAmBC,CAAoB,EAAIzQ,EAAAA,SAAoC,IAAI,EACpF,CAAC0Q,EAAkBC,CAAmB,EAAI3Q,EAAAA,SAAsC,IAAI,EACpF,CAAC4Q,EAAkBC,CAAmB,EAAI7Q,EAAAA,SAAsC,IAAI,EACpF,CAAC8Q,EAAgBC,CAAiB,EAAI/Q,EAAAA,SAAsC,IAAI,EAChF,CAACgR,EAAmBC,CAAoB,EAAIjR,EAAAA,SAA6B,CAAA,CAAE,EAC3E,CAACkR,EAAaC,CAAc,EAAInR,EAAAA,SAAuB,CAAA,CAAE,EACzD,CAACK,GAAOC,CAAQ,EAAIN,EAAAA,SAA8B,IAAI,EACtD,CAACoR,EAASC,CAAU,EAAIrR,WAA8B,CAC1D,OAAQ,GACR,UAAW,GACX,QAAS,GACT,MAAO,GACP,YAAa,GACb,YAAa,GACb,cAAe,GACf,kBAAmB,EAAA,CACpB,EAEKsR,EAAmB9Q,EAAAA,OAAqC,IAAI,GAAK,EAGjE+Q,EAAa5Q,EAAAA,YAAY,IAA8B,CAC3D,MAAMrD,EAAkC,CACtC,eAAgB,kBAAA,EAEZC,EAAQ2S,GAAA,YAAAA,IACd,OAAI3S,IACFD,EAAQ,cAAmB,UAAUC,CAAK,IAErCD,CACT,EAAG,CAAC4S,CAAY,CAAC,EAEXsB,EAAc7Q,EAAAA,YAClB,MACE8Q,EACAzS,EAAuB,CAAA,EACvB0S,IACe,CAEf,MAAMC,EAAqBL,EAAiB,QAAQ,IAAIG,CAAQ,EAC5DE,GACFA,EAAmB,MAAA,EAGrB,MAAMC,EAAa,IAAI,gBACvBN,EAAiB,QAAQ,IAAIG,EAAUG,CAAU,EAGjD,MAAMC,EAAY,WAAW,IAAMD,EAAW,MAAA,EAASpQ,CAAO,EAE9D6P,EAAY9P,IAAU,CAAE,GAAGA,EAAM,CAACmQ,CAAU,EAAG,EAAA,EAAO,EACtDpR,EAAS,IAAI,EAEb,GAAI,CACF,MAAMgM,EAAM,GAAGyD,CAAO,GAAG0B,CAAQ,GAUjC,OATe,MAAMxB,EAAW3D,EAAK,CACnC,GAAGtN,EACH,QAAS,CACP,GAAGuS,EAAA,EACH,GAAIvS,EAAQ,OAAA,EAEd,OAAQ4S,EAAW,MAAA,CACpB,CAGH,OAASzE,EAAK,CACZ,MAAM2E,EAAyB,CAC7B,KAAO3E,EAAqB,MAAQ,gBACpC,QAAUA,EAAc,SAAW,4BACnC,QAAUA,EAAqB,QAC/B,SAAAsE,EACA,WAAatE,EAAqB,UAAA,EAGpC,MAAKA,EAAc,OAAS,eAC1B7M,EAASwR,CAAQ,EACjB3B,GAAA,MAAAA,EAAU2B,IAGNA,CACR,QAAA,CACE,aAAaD,CAAS,EACtBP,EAAiB,QAAQ,OAAOG,CAAQ,EACxCJ,EAAY9P,IAAU,CAAE,GAAGA,EAAM,CAACmQ,CAAU,EAAG,EAAA,EAAQ,CACzD,CACF,EACA,CAAC3B,EAASvO,EAASyO,EAASsB,EAAYpB,CAAO,CAAA,EAI3C4B,GAAcpR,EAAAA,YAClB,MAAOqR,GAAqD,SAC1D,MAAMtU,EAAS,IAAI,gBACfsU,GAAA,MAAAA,EAAS,UACXtU,EAAO,IAAI,WAAYsU,EAAQ,QAAQ,EAErCA,GAAA,MAAAA,EAAS,WACXtU,EAAO,IAAI,YAAa,OAAOsU,EAAQ,SAAS,CAAC,EAE/CA,GAAA,MAAAA,EAAS,WACXtU,EAAO,IAAI,YAAa,OAAOsU,EAAQ,SAAS,CAAC,EAE/CA,GAAA,MAAAA,EAAS,SACXtU,EAAO,IAAI,UAAW,OAAOsU,EAAQ,OAAO,CAAC,EAE3CA,GAAA,MAAAA,EAAS,SACXtU,EAAO,IAAI,UAAW,OAAOsU,EAAQ,OAAO,CAAC,EAE3CA,GAAA,MAAAA,EAAS,QACXtU,EAAO,IAAI,SAAU,OAAOsU,EAAQ,MAAM,CAAC,GAEzCrU,EAAAqU,GAAA,YAAAA,EAAS,QAAT,MAAArU,EAAgB,QAClBD,EAAO,IAAI,QAASsU,EAAQ,MAAM,KAAK,GAAG,CAAC,GAEzCpU,EAAAoU,GAAA,YAAAA,EAAS,WAAT,MAAApU,EAAmB,QACrBF,EAAO,IAAI,WAAYsU,EAAQ,SAAS,KAAK,GAAG,CAAC,EAEnDtU,EAAO,IAAI,YAAYsU,GAAA,YAAAA,EAAS,WAAYhC,CAAQ,EAEpD,MAAMiC,EAAcvU,EAAO,SAAA,EACrB+T,EAAW,UAAUQ,EAAc,IAAIA,CAAW,GAAK,EAAE,GAEzDzM,EAAS,MAAMgM,EAAyBC,EAAU,CAAE,OAAQ,KAAA,EAAS,QAAQ,EACnF,OAAApB,EAAU7K,CAAM,EACTA,CACT,EACA,CAACgM,EAAaxB,CAAQ,CAAA,EAGlBkC,EAAcvR,cAAawR,GAA4B,CAC3D5B,EAAiB4B,CAAK,CACxB,EAAG,CAAA,CAAE,EAECC,EAAkBzR,EAAAA,YACtB,MAAO0R,GACEb,EAAuB,WAAWa,CAAO,GAAI,CAAE,OAAQ,KAAA,EAAS,QAAQ,EAEjF,CAACb,CAAW,CAAA,EAIRc,GAAwB3R,EAAAA,YAC5B,MAAO4R,GAA8D,CACnE,MAAM/M,EAAS,MAAMgM,EACnB,0BACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,CACnB,GAAGe,EACH,SAAUA,EAAQ,UAAYvC,CAAA,CAC/B,CAAA,EAEH,WAAA,EAEF,OAAAS,EAAqBjL,CAAM,EACpBA,CACT,EACA,CAACgM,EAAaxB,CAAQ,CAAA,EAGlBwC,GAAuB7R,EAAAA,YAC3B,MAAO8R,GAAoE,CACzE,MAAMjN,EAAS,MAAMgM,EACnB,qBACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,UAAAiB,EAAW,SAAAzC,EAAU,CAAA,EAE9C,SAAA,EAEF,OAAAW,EAAoBnL,CAAM,EACnBA,CACT,EACA,CAACgM,EAAaxB,CAAQ,CAAA,EAIlB0C,EAAoB/R,EAAAA,YACxB,MACEgS,EACAC,EACAC,IACkC,CAClC,MAAMrN,EAAS,MAAMgM,EACnB,kBACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,CACnB,cAAAmB,EACA,aAAAC,EACA,gBAAAC,EACA,SAAA7C,CAAA,CACD,CAAA,EAEH,aAAA,EAEF,OAAAe,EAAkBvL,CAAM,EACjBA,CACT,EACA,CAACgM,EAAaxB,CAAQ,CAAA,EAIlB8C,EAAoBnS,EAAAA,YACxB,MAAOoS,GAAuE,CAC5E,MAAMvN,EAAS,MAAMgM,EACnB,gBACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAUuB,CAAW,CAAA,EAElC,OAAA,EAEF,OAAAlC,EAAoBrL,CAAM,EACnBA,CACT,EACA,CAACgM,CAAW,CAAA,EAGRwB,EAAuBrS,EAAAA,YAC3B,MAAOsS,GAAyD,CAC9D,MAAMzN,EAAS,MAAMgM,EACnB,iBAAiByB,CAAa,GAC9B,CAAE,OAAQ,KAAA,EACV,OAAA,EAEF,OAAApC,EAAoBrL,CAAM,EACnBA,CACT,EACA,CAACgM,CAAW,CAAA,EAGR0B,EAAoBvS,EAAAA,YACxB,MAAOsS,GAAyC,CAC9C,MAAMzB,EAAkB,iBAAiByB,CAAa,UAAW,CAAE,OAAQ,MAAA,EAAU,OAAO,EAC5FpC,EAAoB,IAAI,CAC1B,EACA,CAACW,CAAW,CAAA,EAIR2B,EAAkBxS,EAAAA,YACtB,MACEyS,EACAC,EACA7N,EACA8N,IAC8B,CAC9B,MAAMC,EAAY,MAAM/B,EACtB,gBACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,KAAA4B,EAAM,OAAQC,EAAa,OAAA7N,EAAQ,MAAA8N,CAAA,CAAO,CAAA,EAEnE,mBAAA,EAEF,OAAArC,EAAsB1P,GAAS,CAAC,GAAGA,EAAMgS,CAAS,CAAC,EAC5CA,CACT,EACA,CAAC/B,CAAW,CAAA,EAGRgC,EAAkB7S,EAAAA,YACtB,MAAO7C,GACE0T,EACL,iBAAiB1T,CAAE,GACnB,CAAE,OAAQ,KAAA,EACV,mBAAA,EAGJ,CAAC0T,CAAW,CAAA,EAGRiC,EAAyB9S,EAAAA,YAC7B,MAAO7C,GAA8B,CACnC,MAAM0T,EAAkB,iBAAiB1T,CAAE,GAAI,CAAE,OAAQ,QAAA,EAAY,mBAAmB,EACxFmT,EAAsB1P,GAASA,EAAK,OAAQoJ,GAAMA,EAAE,KAAO7M,CAAE,CAAC,CAChE,EACA,CAAC0T,CAAW,CAAA,EAGRkC,EAAyB/S,EAAAA,YAAY,SAAyC,CAClF,MAAM6E,EAAS,MAAMgM,EACnB,gBACA,CAAE,OAAQ,KAAA,EACV,mBAAA,EAEF,OAAAP,EAAqBzL,CAAM,EACpBA,CACT,EAAG,CAACgM,CAAW,CAAC,EAGVmC,GAAqBhT,EAAAA,YACzB,MAAOiT,EAAuBC,IACrBrC,EACL,uBACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,cAAAoC,EAAe,YAAAC,EAAa,CAAA,EAErD,aAAA,EAGJ,CAACrC,CAAW,CAAA,EAGRsC,GAAwBnT,EAAAA,YAC5B,MAAOoT,EAAmBC,IACjBxC,EACL,0BACA,CACE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,MAAAuC,EAAO,WAAAC,EAAY,SAAAhE,EAAU,CAAA,EAEtD,eAAA,EAGJ,CAACwB,EAAaxB,CAAQ,CAAA,EAIlBiE,GAAmBtT,EAAAA,YAAY,SAAmC,CACtE,MAAM6E,EAAS,MAAMgM,EAA0B,UAAW,CAAE,OAAQ,KAAA,EAAS,QAAQ,EACrF,OAAAL,EAAe3L,CAAM,EACdA,CACT,EAAG,CAACgM,CAAW,CAAC,EAGV0C,EAAavT,EAAAA,YAAY,IAAM,CACnCL,EAAS,IAAI,CACf,EAAG,CAAA,CAAE,EAEC6T,EAAQxT,EAAAA,YAAY,IAAM,CAC9B0P,EAAU,CAAA,CAAE,EACZE,EAAiB,IAAI,EACrBE,EAAqB,IAAI,EACzBE,EAAoB,IAAI,EACxBE,EAAoB,IAAI,EACxBE,EAAkB,IAAI,EACtBE,EAAqB,CAAA,CAAE,EACvBE,EAAe,CAAA,CAAE,EACjB7Q,EAAS,IAAI,EACb+Q,EAAW,CACT,OAAQ,GACR,UAAW,GACX,QAAS,GACT,MAAO,GACP,YAAa,GACb,YAAa,GACb,cAAe,GACf,kBAAmB,EAAA,CACpB,CACH,EAAG,CAAA,CAAE,EAGLlP,OAAAA,EAAAA,UAAU,IACD,IAAM,CACXmP,EAAiB,QAAQ,QAASM,GAAeA,EAAW,OAAO,EACnEN,EAAiB,QAAQ,MAAA,CAC3B,EACC,CAAA,CAAE,EAEE,CAEL,OAAAlB,EACA,cAAAE,EACA,kBAAAE,EACA,iBAAAE,EACA,iBAAAE,EACA,eAAAE,EACA,kBAAAE,EACA,YAAAE,EACA,QAAAE,EACA,MAAA/Q,GAGA,YAAA0R,GACA,YAAAG,EACA,gBAAAE,EAGA,sBAAAE,GACA,qBAAAE,GAGA,kBAAAE,EAGA,kBAAAI,EACA,qBAAAE,EACA,kBAAAE,EAGA,gBAAAC,EACA,gBAAAK,EACA,uBAAAC,EACA,uBAAAC,EAGA,mBAAAC,GACA,sBAAAG,GAGA,iBAAAG,GAGA,WAAAC,EACA,MAAAC,CAAA,CAEJ,CAUO,MAAMC,EAAY,CAMvB,YAAYlX,EAAuB,CACjC,KAAK,QAAUA,EAAO,QACtB,KAAK,SAAWA,EAAO,UAAY,MACnC,KAAK,QAAUA,EAAO,SAAW,IACjC,KAAK,aAAeA,EAAO,YAC7B,CAEA,MAAc,QAAWuU,EAAkBzS,EAAuB,GAAgB,OAChF,MAAM4S,EAAa,IAAI,gBACjBC,EAAY,WAAW,IAAMD,EAAW,MAAA,EAAS,KAAK,OAAO,EAE7DtU,EAAkC,CACtC,eAAgB,kBAAA,EAEZC,GAAQI,EAAA,KAAK,eAAL,YAAAA,EAAA,WACVJ,IACFD,EAAQ,cAAmB,UAAUC,CAAK,IAG5C,GAAI,CACF,MAAMoS,EAAW,MAAM,MAAM,GAAG,KAAK,OAAO,GAAG8B,CAAQ,GAAI,CACzD,GAAGzS,EACH,QAAS,CAAE,GAAG1B,EAAS,GAAI0B,EAAQ,OAAA,EACnC,OAAQ4S,EAAW,MAAA,CACpB,EAED,GAAI,CAACjC,EAAS,GACZ,MAAM,IAAI,MAAM,QAAQA,EAAS,MAAM,KAAKA,EAAS,UAAU,EAAE,EAGnE,OAAOA,EAAS,KAAA,CAClB,QAAA,CACE,aAAakC,CAAS,CACxB,CACF,CAEA,MAAM,UAAUG,EAAkD,CAChE,MAAMtU,EAAS,IAAI,gBACfsU,GAAA,MAAAA,EAAS,UACXtU,EAAO,IAAI,WAAYsU,EAAQ,QAAQ,EAErCA,GAAA,MAAAA,EAAS,WACXtU,EAAO,IAAI,YAAa,OAAOsU,EAAQ,SAAS,CAAC,EAE/CA,GAAA,MAAAA,EAAS,WACXtU,EAAO,IAAI,YAAa,OAAOsU,EAAQ,SAAS,CAAC,EAEnDtU,EAAO,IAAI,YAAYsU,GAAA,YAAAA,EAAS,WAAY,KAAK,QAAQ,EAEzD,MAAMC,EAAcvU,EAAO,SAAA,EAC3B,OAAO,KAAK,QAAqB,UAAUuU,EAAc,IAAIA,CAAW,GAAK,EAAE,EAAE,CACnF,CAEA,MAAM,aAAanU,EAAgC,CACjD,OAAO,KAAK,QAAmB,WAAWA,CAAE,EAAE,CAChD,CAEA,MAAM,sBAAsByU,EAA2D,CACrF,OAAO,KAAK,QAA4B,0BAA2B,CACjE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,GAAGA,EAAS,SAAUA,EAAQ,UAAY,KAAK,QAAA,CAAU,CAAA,CACjF,CACH,CAEA,MAAM,iBAAiBE,EAAiE,CACtF,OAAO,KAAK,QAA8B,qBAAsB,CAC9D,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,UAAAA,EAAW,SAAU,KAAK,SAAU,CAAA,CAC5D,CACH,CAEA,MAAM,kBAAkBM,EAAoE,CAC1F,OAAO,KAAK,QAA8B,gBAAiB,CACzD,OAAQ,OACR,KAAM,KAAK,UAAUA,CAAW,CAAA,CACjC,CACH,CAEA,MAAM,qBAAqBE,EAAsD,CAC/E,OAAO,KAAK,QAA8B,iBAAiBA,CAAa,EAAE,CAC5E,CAEA,MAAM,mBACJW,EACAC,EACgC,CAChC,OAAO,KAAK,QAA+B,uBAAwB,CACjE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,cAAAD,EAAe,YAAAC,EAAa,CAAA,CACpD,CACH,CAEA,MAAM,sBACJE,EACAC,EACkC,CAClC,OAAO,KAAK,QAAiC,0BAA2B,CACtE,OAAQ,OACR,KAAM,KAAK,UAAU,CAAE,MAAAD,EAAO,WAAAC,EAAY,SAAU,KAAK,QAAA,CAAU,CAAA,CACpE,CACH,CAEA,MAAM,gBAAwC,CAC5C,OAAO,KAAK,QAAsB,SAAS,CAC7C,CACF"}
|