diff --git a/Untitled b/Untitled new file mode 100644 index 0000000..2f259b7 --- /dev/null +++ b/Untitled @@ -0,0 +1 @@ +s \ No newline at end of file diff --git a/src/api/authApi.ts b/src/api/authApi.ts index 8343584..30ce6bb 100644 --- a/src/api/authApi.ts +++ b/src/api/authApi.ts @@ -27,6 +27,8 @@ export interface RegisterData { language?: string; enabled?: boolean; privilege?: string; + registrationType?: 'personal' | 'company'; + companyName?: string; } export interface RegisterRequest { @@ -40,6 +42,8 @@ export interface RegisterRequest { authenticationAuthority: string; }; frontendUrl: string; + registrationType?: string; + companyName?: string; } export interface PasswordResetRequestResponse { @@ -172,7 +176,9 @@ export async function registerApi(registerData: RegisterData): Promise | string; + description?: Record; + config: Record; +} + export interface Automation2Workflow { id: string; label: string; graph: Automation2Graph; active?: boolean; + /** Entry points (Starts) — how this workflow may be invoked */ + invocations?: WorkflowEntryPoint[]; /** Enriched: run count */ runCount?: number; /** Enriched: has active (running/paused) run */ @@ -128,22 +143,36 @@ export async function fetchNodeTypes( * Execute an automation2 graph. * POST /api/automation2/{instanceId}/execute */ +export interface ExecuteGraphOptions { + /** Use a configured start on the saved workflow */ + entryPointId?: string; + /** Full run envelope (overrides entry point mapping) */ + runEnvelope?: Record; + /** Merged into envelope.payload */ + payload?: Record; +} + export async function executeGraph( request: ApiRequestFunction, instanceId: string, graph: Automation2Graph, - workflowId?: string + workflowId?: string, + options?: ExecuteGraphOptions ): Promise { console.log( `${LOG} executeGraph request: instanceId=${instanceId} workflowId=${workflowId} nodes=${graph.nodes.length} connections=${graph.connections.length}`, - { nodes: graph.nodes, connections: graph.connections } + { nodes: graph.nodes, connections: graph.connections, options } ); const start = performance.now(); try { + const data: Record = { graph, workflowId }; + if (options?.entryPointId) data.entryPointId = options.entryPointId; + if (options?.runEnvelope) data.runEnvelope = options.runEnvelope; + if (options?.payload && Object.keys(options.payload).length > 0) data.payload = options.payload; const result = await request({ url: `/api/automation2/${instanceId}/execute`, method: 'post', - data: { graph, workflowId }, + data, }); const ms = Math.round(performance.now() - start); console.log( @@ -167,11 +196,13 @@ export async function executeGraph( export async function fetchWorkflows( request: ApiRequestFunction, - instanceId: string + instanceId: string, + params?: { active?: boolean } ): Promise { const data = await request({ url: `/api/automation2/${instanceId}/workflows`, method: 'get', + params: params?.active !== undefined ? { active: params.active } : undefined, }); return data?.workflows ?? []; } @@ -190,7 +221,7 @@ export async function fetchWorkflow( export async function createWorkflow( request: ApiRequestFunction, instanceId: string, - body: { label: string; graph: Automation2Graph } + body: { label: string; graph: Automation2Graph; invocations?: WorkflowEntryPoint[] } ): Promise { return await request({ url: `/api/automation2/${instanceId}/workflows`, @@ -203,7 +234,12 @@ export async function updateWorkflow( request: ApiRequestFunction, instanceId: string, workflowId: string, - body: { label?: string; graph?: Automation2Graph } + body: { + label?: string; + graph?: Automation2Graph; + invocations?: WorkflowEntryPoint[]; + active?: boolean; + } ): Promise { return await request({ url: `/api/automation2/${instanceId}/workflows/${workflowId}`, @@ -243,6 +279,25 @@ export async function fetchWorkflowRuns( return data?.runs ?? []; } +export interface CompletedRun extends Automation2Run { + workflowLabel?: string; + _modifiedAt?: number; + _createdAt?: number; +} + +export async function fetchCompletedRuns( + request: ApiRequestFunction, + instanceId: string, + limit = 20 +): Promise { + const data = await request({ + url: `/api/automation2/${instanceId}/runs/completed`, + method: 'get', + params: { limit }, + }); + return data?.runs ?? []; +} + // ------------------------------------------------------------------------- // Tasks // ------------------------------------------------------------------------- @@ -258,7 +313,7 @@ export interface Automation2Task { result?: Record; /** Workflow label (enriched by API) */ workflowLabel?: string; - /** Unix timestamp ms (from _createdAt) */ + /** Unix timestamp ms (from sysCreatedAt) */ createdAt?: number; /** Optional due date - configurable in future */ dueAt?: number; @@ -354,3 +409,124 @@ export async function fetchBrowse( }); return { items: data?.items ?? [], path: data?.path ?? path, service: data?.service ?? service }; } + +/** ClickUp GET /task/{taskId} — list.id for resolving list-scoped fields when only task id is known. */ +export async function fetchClickupTask( + request: ApiRequestFunction, + connectionId: string, + taskId: string +): Promise> { + const data = await request({ + url: `/api/clickup/${connectionId}/tasks/${encodeURIComponent(taskId)}`, + method: 'get', + }); + return data && typeof data === 'object' ? (data as Record) : {}; +} + +/** ClickUp list metadata (statuses, etc.) — GET /api/clickup/{connectionId}/lists/{listId}. */ +export async function fetchClickupList( + request: ApiRequestFunction, + connectionId: string, + listId: string +): Promise> { + const data = await request({ + url: `/api/clickup/${connectionId}/lists/${listId}`, + method: 'get', + }); + return data && typeof data === 'object' ? (data as Record) : {}; +} + +/** ClickUp workspace/team (members for assignees) — GET /api/clickup/{connectionId}/teams/{teamId}. */ +export async function fetchClickupTeam( + request: ApiRequestFunction, + connectionId: string, + teamId: string +): Promise> { + const data = await request({ + url: `/api/clickup/${connectionId}/teams/${teamId}`, + method: 'get', + }); + return data && typeof data === 'object' ? (data as Record) : {}; +} + +/** ClickUp list custom fields (GET /api/clickup/{connectionId}/lists/{listId}/fields). */ +export async function fetchClickupListFields( + request: ApiRequestFunction, + connectionId: string, + listId: string +): Promise<{ fields?: unknown[] } & Record> { + const data = await request({ + url: `/api/clickup/${connectionId}/lists/${listId}/fields`, + method: 'get', + }); + return (data && typeof data === 'object' ? data : {}) as { fields?: unknown[] } & Record; +} + +/** ClickUp GET /list/{id}/task page (tasks in a list for relationship dropdowns). */ +export interface ClickupListTaskItem { + id?: string; + name?: string; +} + +export async function fetchClickupListTasks( + request: ApiRequestFunction, + connectionId: string, + listId: string, + options?: { page?: number; includeClosed?: boolean } +): Promise< + { tasks?: ClickupListTaskItem[]; last_page?: boolean } & Record +> { + const data = await request({ + url: `/api/clickup/${connectionId}/lists/${listId}/tasks`, + method: 'get', + params: { + page: options?.page ?? 0, + include_closed: options?.includeClosed ?? false, + }, + }); + return (data && typeof data === 'object' ? data : {}) as { + tasks?: ClickupListTaskItem[]; + last_page?: boolean; + } & Record; +} + +/** Paginated tasks in a list — for ClickUp relationship dropdowns and input.form „ClickUp-Aufgabe“. */ +export async function loadClickupListTasksForDropdown( + request: ApiRequestFunction, + connectionId: string, + listId: string +): Promise> { + const acc: Array<{ id: string; name: string }> = []; + const seen = new Set(); + const maxPages = 12; + const pageSizeHint = 100; + for (let page = 0; page < maxPages; page++) { + const data = await fetchClickupListTasks(request, connectionId, listId, { + page, + includeClosed: false, + }); + if (data && typeof data === 'object' && 'error' in data && (data as { error?: unknown }).error) { + const err = (data as { error?: unknown }).error; + const body = (data as { body?: string }).body; + throw new Error( + typeof err === 'string' ? err + (body ? `: ${body.slice(0, 200)}` : '') : 'ClickUp API error' + ); + } + const tasks = Array.isArray(data.tasks) ? data.tasks : []; + for (const t of tasks) { + const id = t?.id != null ? String(t.id) : ''; + if (!id || seen.has(id)) continue; + seen.add(id); + acc.push({ id, name: String(t.name ?? id) }); + } + const rawLast = (data as Record).last_page; + const last = + rawLast === true || + rawLast === 'true' || + tasks.length === 0 || + tasks.length < pageSizeHint; + if (last) break; + } + acc.sort((a, b) => a.name.localeCompare(b.name, 'de')); + return acc; +} diff --git a/src/api/automationApi.ts b/src/api/automationApi.ts index 80d44cd..955ada7 100644 --- a/src/api/automationApi.ts +++ b/src/api/automationApi.ts @@ -18,9 +18,9 @@ export interface Automation { nextExecution?: number; executionLogs?: AutomationLog[]; allowedProviders?: string[]; - _createdAt?: number; + sysCreatedAt?: number; _updatedAt?: number; - _createdByUserName?: string; + sysCreatedByUserName?: string; mandateName?: string; featureInstanceName?: string; [key: string]: any; @@ -48,9 +48,9 @@ export interface AutomationTemplate { label: TextMultilingual; overview?: TextMultilingual; template: string; // JSON string with {{KEY:...}} placeholders - _createdAt?: number; - _createdBy?: string; - _createdByUserName?: string; + sysCreatedAt?: number; + sysCreatedBy?: string; + sysCreatedByUserName?: string; } // Workflow action definition from backend @@ -301,7 +301,7 @@ export async function fetchAutomationTemplateById( */ export async function createAutomationTemplateApi( request: ApiRequestFunction, - templateData: Omit + templateData: Omit ): Promise { return await request({ url: '/api/automation-templates', diff --git a/src/api/billingApi.ts b/src/api/billingApi.ts index 403ba89..76f79fa 100644 --- a/src/api/billingApi.ts +++ b/src/api/billingApi.ts @@ -4,14 +4,12 @@ import { ApiRequestOptions } from '../hooks/useApi'; // TYPES & INTERFACES // ============================================================================ -export type BillingModel = 'PREPAY_MANDATE' | 'PREPAY_USER'; export type TransactionType = 'CREDIT' | 'DEBIT' | 'ADJUSTMENT'; -export type ReferenceType = 'WORKFLOW' | 'PAYMENT' | 'ADMIN' | 'SYSTEM'; +export type ReferenceType = 'WORKFLOW' | 'PAYMENT' | 'ADMIN' | 'SYSTEM' | 'STORAGE' | 'SUBSCRIPTION'; export interface BillingBalance { mandateId: string; mandateName: string; - billingModel: BillingModel; balance: number; currency: string; warningThreshold: number; @@ -41,19 +39,21 @@ export interface BillingTransaction { export interface BillingSettings { id: string; mandateId: string; - billingModel: BillingModel; - defaultUserCredit: number; warningThresholdPercent: number; notifyOnWarning: boolean; notifyEmails: string[]; + autoRechargeEnabled?: boolean; + rechargeAmountCHF?: number; + rechargeMaxPerMonth?: number; } export interface BillingSettingsUpdate { - billingModel?: BillingModel; - defaultUserCredit?: number; warningThresholdPercent?: number; notifyOnWarning?: boolean; notifyEmails?: string[]; + autoRechargeEnabled?: boolean; + rechargeAmountCHF?: number; + rechargeMaxPerMonth?: number; } export interface UsageReport { @@ -69,7 +69,6 @@ export interface AccountSummary { id: string; mandateId: string; userId?: string; - accountType: string; balance: number; warningThreshold: number; enabled: boolean; @@ -305,10 +304,8 @@ export async function fetchUsersForMandateAdmin( export interface MandateBalance { mandateId: string; mandateName: string; - billingModel: BillingModel; totalBalance: number; userCount: number; - defaultUserCredit: number; warningThresholdPercent: number; } diff --git a/src/api/commcoachApi.ts b/src/api/commcoachApi.ts index ef9b0be..df0ed6c 100644 --- a/src/api/commcoachApi.ts +++ b/src/api/commcoachApi.ts @@ -50,18 +50,6 @@ export interface CoachingPersona { isActive: boolean; } -export interface CoachingDocument { - id: string; - contextId: string; - fileName: string; - mimeType: string; - fileSize: number; - extractedText?: string; - summary?: string; - fileRef?: string; - createdAt?: string; -} - export interface CoachingBadge { id: string; userId: string; @@ -110,8 +98,6 @@ export interface CoachingScore { export interface CoachingUserProfile { id: string; userId: string; - preferredLanguage: string; - preferredVoice?: string; dailyReminderTime?: string; dailyReminderEnabled: boolean; emailSummaryEnabled: boolean; @@ -299,6 +285,13 @@ export async function cancelSessionApi(request: ApiRequestFunction, instanceId: // Streaming Chat API // ============================================================================ +export interface SendMessageOptions { + fileIds?: string[]; + dataSourceIds?: string[]; + featureDataSourceIds?: string[]; + allowedProviders?: string[]; +} + export async function sendMessageStreamApi( instanceId: string, sessionId: string, @@ -307,6 +300,7 @@ export async function sendMessageStreamApi( onError?: (error: Error) => void, onComplete?: () => void, signal?: AbortSignal, + options?: SendMessageOptions, ): Promise { try { const baseURL = api.defaults.baseURL || ''; @@ -318,10 +312,16 @@ export async function sendMessageStreamApi( if (!getCSRFToken()) generateAndStoreCSRFToken(); addCSRFTokenToHeaders(headers); + const body: Record = { content }; + if (options?.fileIds?.length) body.fileIds = options.fileIds; + if (options?.dataSourceIds?.length) body.dataSourceIds = options.dataSourceIds; + if (options?.featureDataSourceIds?.length) body.featureDataSourceIds = options.featureDataSourceIds; + if (options?.allowedProviders?.length) body.allowedProviders = options.allowedProviders; + const response = await fetch(url, { method: 'POST', headers, - body: JSON.stringify({ content }), + body: JSON.stringify(body), credentials: 'include', signal, }); @@ -494,27 +494,6 @@ export async function updateProfileApi(request: ApiRequestFunction, instanceId: return data.profile; } -// ============================================================================ -// Voice API -// ============================================================================ - -export async function getVoiceLanguagesApi(request: ApiRequestFunction, instanceId: string): Promise { - const data = await request({ url: `/api/commcoach/${instanceId}/voice/languages`, method: 'get' }); - return data.languages || []; -} - -export async function getVoiceVoicesApi(request: ApiRequestFunction, instanceId: string, language: string = 'de-DE'): Promise { - const data = await request({ url: `/api/commcoach/${instanceId}/voice/voices`, method: 'get', params: { language } }); - return data.voices || []; -} - -export async function testVoiceApi(request: ApiRequestFunction, instanceId: string, body: { - text?: string; language?: string; voiceId?: string; -}): Promise<{ success: boolean; audio?: string; format?: string; text?: string }> { - const data = await request({ url: `/api/commcoach/${instanceId}/voice/tts`, method: 'post', data: body }); - return data; -} - // ============================================================================ // Persona API (Iteration 2) // ============================================================================ @@ -535,42 +514,6 @@ export async function deletePersonaApi(request: ApiRequestFunction, instanceId: await request({ url: `/api/commcoach/${instanceId}/personas/${personaId}`, method: 'delete' }); } -// ============================================================================ -// Document API (Iteration 2) -// ============================================================================ - -export async function getDocumentsApi(request: ApiRequestFunction, instanceId: string, contextId: string): Promise { - const data = await request({ url: `/api/commcoach/${instanceId}/contexts/${contextId}/documents`, method: 'get' }); - return data.documents || []; -} - -export async function uploadDocumentApi(instanceId: string, contextId: string, file: File): Promise { - const baseURL = api.defaults.baseURL || ''; - const url = `${baseURL}/api/commcoach/${instanceId}/contexts/${contextId}/documents`; - const formData = new FormData(); - formData.append('file', file); - - const headers: Record = {}; - const authToken = localStorage.getItem('authToken'); - if (authToken) headers['Authorization'] = `Bearer ${authToken}`; - const pathMatch = window.location.pathname.match(/^\/mandates\/([^/]+)\/([^/]+)\/([^/]+)/); - if (pathMatch) { - headers['X-Mandate-Id'] = pathMatch[1]; - headers['X-Instance-Id'] = pathMatch[3]; - } - if (!getCSRFToken()) generateAndStoreCSRFToken(); - addCSRFTokenToHeaders(headers); - - const response = await fetch(url, { method: 'POST', headers, body: formData, credentials: 'include' }); - if (!response.ok) throw new Error(`Upload failed: ${response.status}`); - const data = await response.json(); - return data.document; -} - -export async function deleteDocumentApi(request: ApiRequestFunction, instanceId: string, documentId: string): Promise { - await request({ url: `/api/commcoach/${instanceId}/documents/${documentId}`, method: 'delete' }); -} - // ============================================================================ // Badge API (Iteration 2) // ============================================================================ diff --git a/src/api/connectionApi.ts b/src/api/connectionApi.ts index cb1b83e..7263e95 100644 --- a/src/api/connectionApi.ts +++ b/src/api/connectionApi.ts @@ -7,7 +7,7 @@ import { ApiRequestOptions } from '../hooks/useApi'; export interface Connection { id: string; userId: string; - authority: 'local' | 'google' | 'msft'; + authority: 'local' | 'google' | 'msft' | 'clickup'; externalId: string; externalUsername: string; externalEmail?: string; @@ -52,8 +52,8 @@ export interface PaginatedResponse { export interface CreateConnectionData { id?: string; userId?: string; - authority?: 'msft' | 'google'; - type?: 'msft' | 'google'; // Backend expects this field + authority?: 'msft' | 'google' | 'clickup'; + type?: 'msft' | 'google' | 'clickup'; // Backend maps type → authority externalId?: string; externalUsername?: string; externalEmail?: string; diff --git a/src/api/mandateApi.ts b/src/api/mandateApi.ts index b29d138..9f4076d 100644 --- a/src/api/mandateApi.ts +++ b/src/api/mandateApi.ts @@ -122,7 +122,7 @@ export async function createMandate( } /** - * Delete a mandate + * Soft-delete a mandate (sets enabled=false, 30-day retention) * Endpoint: DELETE /api/mandates/{mandateId} */ export async function deleteMandate( @@ -134,3 +134,22 @@ export async function deleteMandate( method: 'delete' }); } + +/** + * Hard-delete a mandate with full cascade (irreversible) + * Endpoint: DELETE /api/mandates/{mandateId}?force=true + */ +export async function hardDeleteMandate( + request: ApiRequestFunction, + mandateId: string, + confirmName: string +): Promise { + await request({ + url: `/api/mandates/${mandateId}`, + method: 'delete', + params: { force: true }, + additionalConfig: { + headers: { 'X-Confirm-Name': confirmName } + } + }); +} diff --git a/src/api/promptApi.ts b/src/api/promptApi.ts index 3b5a000..094dcdc 100644 --- a/src/api/promptApi.ts +++ b/src/api/promptApi.ts @@ -9,7 +9,7 @@ export interface Prompt { mandateId: string; content: string; name: string; - _createdBy?: string; + sysCreatedBy?: string; _hideDelete?: boolean; [key: string]: any; // Allow additional properties } diff --git a/src/api/realEstateApi.ts b/src/api/realEstateApi.ts index 08d8ecc..c043485 100644 --- a/src/api/realEstateApi.ts +++ b/src/api/realEstateApi.ts @@ -23,8 +23,8 @@ export interface RealEstateProject { featureInstanceId?: string; perimeter?: any; parzellen?: RealEstateParcel[]; - _createdAt?: number; - _modifiedAt?: number; + sysCreatedAt?: number; + sysModifiedAt?: number; [key: string]: any; } @@ -38,8 +38,8 @@ export interface RealEstateParcel { plz?: string; perimeter?: any; bauzone?: string; - _createdAt?: number; - _modifiedAt?: number; + sysCreatedAt?: number; + sysModifiedAt?: number; [key: string]: any; } diff --git a/src/api/storeApi.ts b/src/api/storeApi.ts index c4a3b7d..78b0768 100644 --- a/src/api/storeApi.ts +++ b/src/api/storeApi.ts @@ -7,14 +7,21 @@ import api from '../api'; +export interface StoreFeatureInstance { + instanceId: string; + mandateId: string; + mandateName: string; + label: string; + isActive: boolean; +} + export interface StoreFeature { featureCode: string; label: Record; icon: string; description: Record; - isActive: boolean; + instances: StoreFeatureInstance[]; canActivate: boolean; - instanceId: string | null; } export interface StoreActivateResponse { @@ -31,17 +38,44 @@ export interface StoreDeactivateResponse { deactivated: boolean; } +export interface UserMandate { + id: string; + name: string; + label: string; +} + +export interface SubscriptionInfo { + plan: string | null; + status: string | null; + maxDataVolumeMB: number | null; + maxFeatureInstances: number | null; + budgetAiCHF: number | null; + currentFeatureInstances: number; + trialEndsAt: string | null; +} + export async function fetchStoreFeatures(): Promise { const response = await api.get('/api/store/features'); return response.data; } -export async function activateStoreFeature(featureCode: string): Promise { - const response = await api.post('/api/store/activate', { featureCode }); +export async function fetchUserMandates(): Promise { + const response = await api.get('/api/store/mandates'); return response.data; } -export async function deactivateStoreFeature(featureCode: string): Promise { - const response = await api.post('/api/store/deactivate', { featureCode }); +export async function fetchSubscriptionInfo(mandateId?: string): Promise { + const params = mandateId ? { mandateId } : {}; + const response = await api.get('/api/store/subscription-info', { params }); + return response.data; +} + +export async function activateStoreFeature(featureCode: string, mandateId?: string): Promise { + const response = await api.post('/api/store/activate', { featureCode, mandateId }); + return response.data; +} + +export async function deactivateStoreFeature(featureCode: string, mandateId: string, instanceId: string): Promise { + const response = await api.post('/api/store/deactivate', { featureCode, mandateId, instanceId }); return response.data; } diff --git a/src/api/subscriptionApi.ts b/src/api/subscriptionApi.ts index 9fefe9f..b476433 100644 --- a/src/api/subscriptionApi.ts +++ b/src/api/subscriptionApi.ts @@ -19,6 +19,8 @@ export interface SubscriptionPlan { autoRenew: boolean; maxUsers: number | null; maxFeatureInstances: number | null; + maxDataVolumeMB?: number | null; + budgetAiCHF?: number; trialDays: number | null; successorPlanKey: string | null; } diff --git a/src/api/trusteeApi.ts b/src/api/trusteeApi.ts index 4230612..cc8920b 100644 --- a/src/api/trusteeApi.ts +++ b/src/api/trusteeApi.ts @@ -18,10 +18,10 @@ export interface TrusteeOrganisation { label: string; enabled: boolean; mandateId?: string; - _createdAt?: number; - _modifiedAt?: number; - _createdBy?: string; - _modifiedBy?: string; + sysCreatedAt?: number; + sysModifiedAt?: number; + sysCreatedBy?: string; + sysModifiedBy?: string; [key: string]: any; } @@ -29,10 +29,10 @@ export interface TrusteeRole { id: string; desc: string; mandateId?: string; - _createdAt?: number; - _modifiedAt?: number; - _createdBy?: string; - _modifiedBy?: string; + sysCreatedAt?: number; + sysModifiedAt?: number; + sysCreatedBy?: string; + sysModifiedBy?: string; [key: string]: any; } @@ -43,10 +43,10 @@ export interface TrusteeAccess { userId: string; contractId?: string | null; mandateId?: string; - _createdAt?: number; - _modifiedAt?: number; - _createdBy?: string; - _modifiedBy?: string; + sysCreatedAt?: number; + sysModifiedAt?: number; + sysCreatedBy?: string; + sysModifiedBy?: string; [key: string]: any; } @@ -56,10 +56,10 @@ export interface TrusteeContract { label: string; enabled: boolean; mandateId?: string; - _createdAt?: number; - _modifiedAt?: number; - _createdBy?: string; - _modifiedBy?: string; + sysCreatedAt?: number; + sysModifiedAt?: number; + sysCreatedBy?: string; + sysModifiedBy?: string; [key: string]: any; } @@ -71,10 +71,10 @@ export interface TrusteeDocument { documentMimeType: string; documentData?: any; mandateId?: string; - _createdAt?: number; - _modifiedAt?: number; - _createdBy?: string; - _modifiedBy?: string; + sysCreatedAt?: number; + sysModifiedAt?: number; + sysCreatedBy?: string; + sysModifiedBy?: string; [key: string]: any; } @@ -98,10 +98,10 @@ export interface TrusteePosition { costCenter?: string; bookingReference?: string; mandateId?: string; - _createdAt?: number; - _modifiedAt?: number; - _createdBy?: string; - _modifiedBy?: string; + sysCreatedAt?: number; + sysModifiedAt?: number; + sysCreatedBy?: string; + sysModifiedBy?: string; [key: string]: any; } @@ -696,8 +696,8 @@ export interface TrusteePositionDocument { documentId: string; mandateId?: string; featureInstanceId?: string; - _createdAt?: number; - _modifiedAt?: number; + sysCreatedAt?: number; + sysModifiedAt?: number; [key: string]: any; } diff --git a/src/components/Automation2FlowEditor/Automation2FlowEditor.module.css b/src/components/Automation2FlowEditor/Automation2FlowEditor.module.css deleted file mode 100644 index 828453e..0000000 --- a/src/components/Automation2FlowEditor/Automation2FlowEditor.module.css +++ /dev/null @@ -1,499 +0,0 @@ -/** - * Automation2 Flow Editor Styles - * Sidebar with node list + canvas area. - */ - -.container { - display: flex; - flex: 1; - min-height: 0; - overflow: hidden; -} - -/* ============================================================================= - SIDEBAR - Node List - ============================================================================= */ - -.sidebar { - flex-shrink: 0; - width: 280px; - display: flex; - flex-direction: column; - background: var(--bg-secondary, #f8f9fa); - border-right: 1px solid var(--border-color, #e0e0e0); - overflow: hidden; -} - -.sidebarHeader { - padding: 1rem; - border-bottom: 1px solid var(--border-color, #e0e0e0); - background: var(--bg-primary, #fff); -} - -.sidebarTitle { - margin: 0; - font-size: 1rem; - font-weight: 600; - color: var(--text-primary, #1a1a1a); -} - -.sidebarSearch { - margin-top: 0.75rem; - width: 100%; - padding: 0.5rem 0.75rem; - border: 1px solid var(--border-color, #e0e0e0); - border-radius: 6px; - font-size: 0.875rem; - background: var(--bg-primary, #fff); - color: var(--text-primary, #333); -} - -.sidebarSearch::placeholder { - color: var(--text-tertiary, #999); -} - -.sidebarSearch:focus { - outline: none; - border-color: var(--primary-color, #007bff); - box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.15); -} - -.nodeList { - flex: 1; - overflow-y: auto; - padding: 0.5rem; -} - -/* Category Groups */ -.categoryGroup { - margin-bottom: 1rem; -} - - -.categoryHeader { - display: flex; - align-items: center; - padding: 0.5rem 0.75rem; - font-size: 0.75rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--text-secondary, #666); -} - -.categoryIcon { - margin-right: 0.5rem; - font-size: 0.875rem; -} - -.categoryLabel { - flex: 1; -} - -.categoryCount { - background: var(--bg-tertiary, #e9ecef); - color: var(--text-secondary, #666); - padding: 0.125rem 0.5rem; - border-radius: 10px; - font-size: 0.7rem; -} - -/* Node Items */ -.nodeItem { - display: flex; - align-items: center; - padding: 0.5rem 0.75rem; - margin-bottom: 0.25rem; - border-radius: 6px; - cursor: grab; - transition: background 0.15s; - border: 1px solid transparent; -} - -.nodeItem:hover { - background: var(--bg-hover, #e9ecef); -} - -.nodeItem:active { - cursor: grabbing; -} - -.nodeItemIcon { - flex-shrink: 0; - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 6px; - margin-right: 0.75rem; - font-size: 0.875rem; -} - -.nodeItemInfo { - flex: 1; - min-width: 0; -} - -.nodeItemLabel { - display: block; - font-size: 0.875rem; - font-weight: 500; - color: var(--text-primary, #333); -} - -.nodeItemDesc { - display: block; - font-size: 0.75rem; - color: var(--text-secondary, #666); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -/* Loading / Error */ -.loading, -.error { - padding: 2rem; - text-align: center; - color: var(--text-secondary, #666); -} - -.error { - color: var(--danger-color, #dc3545); -} - -.retryButton { - margin-top: 0.75rem; - padding: 0.5rem 1rem; - background: var(--primary-color, #007bff); - color: white; - border: none; - border-radius: 6px; - cursor: pointer; - font-size: 0.875rem; -} - -.retryButton:hover { - background: var(--primary-hover, #0056b3); -} - -.spinner { - animation: spin 1s linear infinite; -} - -@keyframes spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -/* ============================================================================= - CANVAS - ============================================================================= */ - -.canvas { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; - background: var(--canvas-bg, #fafafa); -} - -.canvasHeader { - flex-shrink: 0; - padding: 0.75rem 1rem; - border-bottom: 1px solid var(--border-color, #e0e0e0); - background: var(--bg-primary, #fff); -} - -.canvasTitle { - margin: 0; - font-size: 0.875rem; - font-weight: 500; - color: var(--text-secondary, #666); -} - -.canvasArea { - flex: 1; - padding: 2rem; - min-height: 400px; - overflow: hidden; -} - -.canvasDropZone { - position: relative; - min-height: 100%; - height: 100%; - overflow: hidden; - border-radius: 8px; - /* Infinite grid: on viewport, moves with pan/zoom via inline style */ - background-image: radial-gradient(circle, var(--border-color, #e0e0e0) 1px, transparent 1px); - background-repeat: repeat; -} - -.canvasContent { - position: absolute; - left: 0; - top: 0; - will-change: transform; - background: transparent; -} - -.canvasGrab { - cursor: grab; -} - -.canvasPanning { - cursor: grabbing; - user-select: none; -} - -.canvasPlaceholder { - position: absolute; - left: 2rem; - top: 2rem; - text-align: center; - color: var(--text-tertiary, #999); - border: 2px dashed var(--border-color, #dee2e6); - border-radius: 8px; - padding: 2rem 3rem; -} - -.canvasPlaceholder p { - margin: 0.25rem 0; - font-size: 0.875rem; -} - -/* Canvas Nodes */ -.canvasNode { - position: absolute; - border-radius: 8px; - border: 2px solid; - cursor: grab; - overflow: visible; -} - -.canvasNode:active { - cursor: grabbing; -} - -.canvasNodeSelected { - box-shadow: 0 0 0 2px var(--primary-color, #007bff); -} - -.canvasNodeContent { - display: flex; - align-items: flex-start; - gap: 0.5rem; - padding: 0.4rem 0.6rem; - height: 100%; - box-sizing: border-box; -} - -.canvasNodeIcon { - flex-shrink: 0; - width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 6px; - font-size: 0.9rem; -} - -.canvasNodeText { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 0.15rem; -} - -.canvasNodeTitle { - font-size: 0.875rem; - font-weight: 500; - color: var(--text-primary, #333); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: text; -} - -.canvasNodeTitle:hover { - text-decoration: underline; -} - -.canvasNodeComment { - font-size: 0.7rem; - color: var(--text-tertiary, #999); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: text; - min-height: 1em; -} - -.canvasNodeComment:hover { - text-decoration: underline; -} - -.canvasNodeInput { - width: 100%; - padding: 0.15rem 0.25rem; - font-size: 0.875rem; - border: 1px solid var(--primary-color, #007bff); - border-radius: 4px; - outline: none; -} - -/* Connection Handles */ -.handle { - position: absolute; - border-radius: 50%; - background: var(--bg-primary, #fff); - border: 2px solid var(--border-color, #666); - cursor: crosshair; - z-index: 2; -} - -.handle:hover, -.handleConnectable { - border-color: var(--primary-color, #007bff); - background: var(--primary-color, #007bff); -} - -.handleInput { - cursor: copy; -} - -/* Node Config Panel */ -.nodeConfigPanel { - padding: 1rem; - background: var(--bg-primary, #fff); - border-left: 1px solid var(--border-color, #e0e0e0); - width: 280px; - flex-shrink: 0; - overflow-y: auto; - min-width: 0; -} - -.nodeConfigPanel h4 { - margin: 0 0 0.75rem 0; - font-size: 0.9rem; -} - -.nodeConfigPanel label { - display: block; - font-size: 0.75rem; - color: var(--text-secondary, #666); - margin-top: 0.5rem; - margin-bottom: 0.25rem; -} - -.nodeConfigPanel input[type='text'], -.nodeConfigPanel input[type='number'], -.nodeConfigPanel select, -.nodeConfigPanel textarea { - width: 100%; - padding: 0.4rem; - font-size: 0.875rem; - border: 1px solid var(--border-color, #e0e0e0); - border-radius: 4px; -} - -.nodeConfigPanel textarea { - min-height: 60px; -} - -.nodeConfigPanel button { - margin-top: 0.5rem; - padding: 0.4rem 0.75rem; - font-size: 0.8rem; - background: var(--primary-color, #007bff); - color: white; - border: none; - border-radius: 4px; - cursor: pointer; -} - -/* Form fields editor (input.form) */ -.formFieldsList { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.formFieldRow { - display: flex; - flex-direction: column; - gap: 0.35rem; - padding: 0.5rem; - background: var(--bg-secondary, #f8f9fa); - border-radius: 6px; - border: 1px solid var(--border-color, #e0e0e0); -} - -.formFieldRowHeader { - display: flex; - align-items: flex-start; - gap: 0.35rem; -} - -.formFieldDragHandle { - flex-shrink: 0; - padding: 0.25rem; - cursor: grab; - color: var(--text-tertiary, #999); - align-self: stretch; - display: flex; - align-items: center; -} - -.formFieldDragHandle:active { - cursor: grabbing; -} - -.formFieldDragHandle:hover { - color: var(--text-secondary, #666); -} - -.formFieldInputs { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 0.35rem; -} - -.formFieldRowFooter { - display: flex; - align-items: center; - gap: 0.5rem; - flex-wrap: wrap; -} - -.formFieldRequiredLabel { - display: inline-flex; - align-items: center; - gap: 0.35rem; - font-size: 0.75rem; - color: var(--text-secondary, #666); - cursor: pointer; -} - -.formFieldRemoveButton { - margin-left: auto; - padding: 0.25rem 0.4rem; - border: none; - background: transparent; - color: var(--text-tertiary, #999); - cursor: pointer; - border-radius: 4px; - display: flex; - align-items: center; -} - -.formFieldRemoveButton:hover { - color: var(--danger-color, #dc3545); - background: rgba(220, 53, 69, 0.1); -} diff --git a/src/components/Automation2FlowEditor/NodeConfigPanel.tsx b/src/components/Automation2FlowEditor/NodeConfigPanel.tsx deleted file mode 100644 index 36d7d1c..0000000 --- a/src/components/Automation2FlowEditor/NodeConfigPanel.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * NodeConfigPanel - Configures parameters for input, ai, email, sharepoint nodes. - * Delegates to config components from configs/. - */ - -import React, { useState, useEffect } from 'react'; -import type { CanvasNode } from './FlowCanvas'; -import type { NodeType } from '../../api/automation2Api'; -import type { ApiRequestFunction } from '../../api/automation2Api'; -import { getLabel } from './utils'; -import { NODE_CONFIG_REGISTRY } from './configs'; -import styles from './Automation2FlowEditor.module.css'; - -interface NodeConfigPanelProps { - node: CanvasNode | null; - nodeType: NodeType | undefined; - language: string; - onParametersChange: (nodeId: string, parameters: Record) => void; - instanceId?: string; - request?: ApiRequestFunction; -} - -const CONFIGURABLE_PREFIXES = ['input.', 'ai.', 'email.', 'sharepoint.']; - -export const NodeConfigPanel: React.FC = ({ - node, - nodeType, - language, - onParametersChange, - instanceId, - request, -}) => { - const [params, setParams] = useState>({}); - - useEffect(() => { - setParams(node?.parameters ?? {}); - }, [node?.id, node?.parameters]); - - const updateParam = (key: string, value: unknown) => { - const next = { ...params, [key]: value }; - setParams(next); - if (node) onParametersChange(node.id, next); - }; - - const isConfigurable = node && CONFIGURABLE_PREFIXES.some((p) => node.type.startsWith(p)); - if (!node || !isConfigurable) return null; - - const ConfigRenderer = NODE_CONFIG_REGISTRY[node.type]; - if (!ConfigRenderer) { - return ( -
-

{getLabel(nodeType?.label, language) || node.type}

-

No configuration for {node.type}

-
- ); - } - - return ( -
-

{getLabel(nodeType?.label, language) || node.type}

- -
- ); -}; diff --git a/src/components/Automation2FlowEditor/configs/AiNodeConfig.tsx b/src/components/Automation2FlowEditor/configs/AiNodeConfig.tsx deleted file mode 100644 index cc7b76c..0000000 --- a/src/components/Automation2FlowEditor/configs/AiNodeConfig.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/** - * AI node config - prompt, query, document options per node type. - */ - -import React from 'react'; -import type { NodeConfigRendererProps } from './types'; - -const AI_FIELD_CONFIG: Record = { - 'ai.prompt': [ - { label: 'Prompt', key: 'prompt', type: 'textarea' }, - { label: 'Output format', key: 'resultType', type: 'select', options: ['txt', 'json', 'md', 'html', 'csv'] }, - ], - 'ai.webResearch': [{ label: 'Query', key: 'query', type: 'textarea' }], - 'ai.summarizeDocument': [ - { label: 'Summary length', key: 'summaryLength', type: 'select', options: ['short', 'medium', 'long'] }, - ], - 'ai.translateDocument': [{ label: 'Target language', key: 'targetLanguage', type: 'input' }], - 'ai.convertDocument': [ - { label: 'Target format', key: 'targetFormat', type: 'select', options: ['pdf', 'docx', 'txt', 'md'] }, - ], - 'ai.generateDocument': [ - { label: 'Prompt', key: 'prompt', type: 'textarea' }, - { label: 'Format', key: 'format', type: 'select', options: ['docx', 'txt', 'md'] }, - ], - 'ai.generateCode': [ - { label: 'Prompt', key: 'prompt', type: 'textarea' }, - { label: 'Language', key: 'language', type: 'select', options: ['python', 'javascript', 'typescript', 'sql'] }, - ], -}; - -export const AiNodeConfig: React.FC = ({ params, updateParam, nodeType = 'ai.prompt' }) => { - const fields = AI_FIELD_CONFIG[nodeType] ?? AI_FIELD_CONFIG['ai.prompt']; - - return ( - <> - {fields.map((f) => ( -
- - {f.type === 'textarea' ? ( -