diff --git a/src/api/connectionApi.ts b/src/api/connectionApi.ts index 83e6dfc..cb1b83e 100644 --- a/src/api/connectionApi.ts +++ b/src/api/connectionApi.ts @@ -193,29 +193,31 @@ export async function updateConnection( /** * Refresh Microsoft token - * Endpoint: POST /api/connections/{connectionId}/refresh-microsoft-token + * Endpoint: POST /api/msft/refresh */ export async function refreshMicrosoftToken( request: ApiRequestFunction, connectionId: string ): Promise { return await request({ - url: `/api/connections/${connectionId}/refresh-microsoft-token`, - method: 'post' + url: '/api/msft/refresh', + method: 'post', + data: { connectionId } }); } /** * Refresh Google token - * Endpoint: POST /api/connections/{connectionId}/refresh-google-token + * Endpoint: POST /api/google/refresh */ export async function refreshGoogleToken( request: ApiRequestFunction, connectionId: string ): Promise { return await request({ - url: `/api/connections/${connectionId}/refresh-google-token`, - method: 'post' + url: '/api/google/refresh', + method: 'post', + data: { connectionId } }); } diff --git a/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx b/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx index 92b14a7..4439c77 100644 --- a/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx +++ b/src/components/FormGenerator/FormGeneratorControls/FormGeneratorControls.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useLanguage } from '../../../providers/language/LanguageContext'; import styles from './FormGeneratorControls.module.css'; import { Button } from '../../UiComponents/Button'; diff --git a/src/core/PageManager/data/pages/connections.ts b/src/core/PageManager/data/pages/connections.ts index cc2811d..3af8f48 100644 --- a/src/core/PageManager/data/pages/connections.ts +++ b/src/core/PageManager/data/pages/connections.ts @@ -1,6 +1,6 @@ -import { useCallback } from 'react'; +import React, { useCallback } from 'react'; import { GenericPageData } from '../../pageInterface'; -import { FaGoogle, FaMicrosoft, FaLink } from 'react-icons/fa'; +import { FaGoogle, FaMicrosoft, FaLink, FaSync, FaPlug } from 'react-icons/fa'; import { useConnections } from '../../../../hooks/useConnections'; // Helper function to convert attribute definitions to column config @@ -29,6 +29,8 @@ const createConnectionsHook = () => { createGoogleConnectionAndAuth, createMicrosoftConnectionAndAuth, connectWithPopup, + refreshMicrosoftToken, + refreshGoogleToken, isConnecting, isLoading, error, @@ -92,9 +94,13 @@ const createConnectionsHook = () => { connectWithPopup, createGoogleConnectionAndAuth, createMicrosoftConnectionAndAuth, + // Token refresh operations + refreshMicrosoftToken, + refreshGoogleToken, // Loading states isConnecting, deletingConnections: new Set(), // Placeholder for consistency with other pages + refreshingConnections: new Set(), // Track which connections are refreshing // Attributes and permissions for dynamic column/button generation attributes, permissions, @@ -192,28 +198,14 @@ export const connectionsPageData: GenericPageData = { tableConfig: { hookFactory: createConnectionsHook, // Columns are generated dynamically from attributes via hookData.columns + // Standard action buttons actionButtons: [ - { - type: 'connect', - title: 'connections.action.connect', - idField: 'id', - statusField: 'status', - operationName: 'connectWithPopup', - loadingStateName: 'isConnecting', - // Only show if user has update permission (connect = update operation) - disabled: (hookData: any) => { - if (!hookData?.permissions) return { disabled: false }; - const hasUpdate = hookData.permissions.update !== 'n' && hookData.permissions.view; - return { disabled: !hasUpdate, message: 'No permission to connect' }; - } - }, { type: 'delete', title: 'connections.action.delete', idField: 'id', operationName: 'handleDelete', loadingStateName: 'deletingConnections', - // Only show if user has delete permission disabled: (hookData: any) => { if (!hookData?.permissions) return { disabled: false }; const hasDelete = hookData.permissions.delete !== 'n' && hookData.permissions.view; @@ -221,6 +213,50 @@ export const connectionsPageData: GenericPageData = { } } ], + // Custom action buttons (entity-specific) + customActions: [ + { + id: 'connect', + icon: React.createElement(FaPlug), + title: 'connections.action.connect', + onClick: async (row: any, hookData: any) => { + if (hookData?.connectWithPopup) { + await hookData.connectWithPopup(row.id); + } + }, + // Only show connect button if status is not 'active' + visible: (row: any) => row.status !== 'active', + disabled: (_row: any, hookData: any) => { + if (!hookData?.permissions) return { disabled: false, message: '' }; + const hasUpdate = hookData.permissions.update !== 'n' && hookData.permissions.view; + return { disabled: !hasUpdate, message: 'No permission to connect' }; + }, + loading: (_row: any, hookData: any) => hookData?.isConnecting || false + }, + { + id: 'refresh', + icon: React.createElement(FaSync), + title: 'connections.action.refresh', + onClick: async (row: any, hookData: any) => { + // Determine which refresh function to use based on authority + if (row.authority === 'msft' && hookData?.refreshMicrosoftToken) { + await hookData.refreshMicrosoftToken(row.id); + if (hookData?.refetch) await hookData.refetch(); + } else if (row.authority === 'google' && hookData?.refreshGoogleToken) { + await hookData.refreshGoogleToken(row.id); + if (hookData?.refetch) await hookData.refetch(); + } + }, + // Only show refresh button if status is 'active' (already connected) + visible: (row: any) => row.status === 'active', + disabled: (_row: any, hookData: any) => { + if (!hookData?.permissions) return { disabled: false, message: '' }; + const hasUpdate = hookData.permissions.update !== 'n' && hookData.permissions.view; + return { disabled: !hasUpdate, message: 'No permission to refresh token' }; + }, + loading: (row: any, hookData: any) => hookData?.refreshingConnections?.has(row.id) || false + } + ], searchable: true, filterable: true, sortable: true, diff --git a/src/core/PageManager/data/pages/files.ts b/src/core/PageManager/data/pages/files.ts index ba494d8..1526e05 100644 --- a/src/core/PageManager/data/pages/files.ts +++ b/src/core/PageManager/data/pages/files.ts @@ -257,7 +257,7 @@ export const filesPageData: GenericPageData = { await hookData.handleDownload(row.id, row.fileName, row.mimeType); } }, - disabled: (row: any, hookData: any) => { + disabled: (_row: any, hookData: any) => { if (!hookData?.permissions) return { disabled: false, message: '' }; const hasRead = hookData.permissions.read !== 'n' && hookData.permissions.view; return { disabled: !hasRead, message: 'No permission to download files' }; diff --git a/src/core/PageManager/pageInterface.ts b/src/core/PageManager/pageInterface.ts index 147f853..18d8447 100644 --- a/src/core/PageManager/pageInterface.ts +++ b/src/core/PageManager/pageInterface.ts @@ -9,7 +9,7 @@ export type PrivilegeChecker = () => boolean | Promise; export interface ButtonFormField { key: string; label: string | LanguageText; - type: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'readonly' | 'multiselect'; + type: 'string' | 'boolean' | 'email' | 'textarea' | 'date' | 'enum' | 'readonly' | 'multiselect' | 'number'; required?: boolean; placeholder?: string | LanguageText; minRows?: number; @@ -17,6 +17,7 @@ export interface ButtonFormField { validator?: (value: any) => string | null; defaultValue?: any; options?: string[] | Array<{ value: string | number; label: string }>; // For enum/multiselect fields + optionsReference?: string; // Reference to a data source for dynamic options (e.g., 'TrusteeOrganisation', 'User') } // Dropdown configuration for header dropdown buttons @@ -262,22 +263,26 @@ export interface GenericDataHook { [key: string]: any; // Allow additional properties for dynamic data sources } -// Standard action button configuration (built-in actions: edit, delete, view, copy) +// Standard action button configuration (built-in actions: edit, delete, view, copy, connect, play) export interface ActionButtonConfig { - type: 'view' | 'edit' | 'delete' | 'copy'; + type: 'view' | 'edit' | 'delete' | 'copy' | 'connect' | 'play'; onAction?: (row: any) => Promise | void; // Optional for delete buttons since they handle their own logic title?: string | LanguageText; - disabled?: (row: any) => boolean | { disabled: boolean; message?: string }; - loading?: (row: any) => boolean; + disabled?: (row: any, hookData?: any) => boolean | { disabled: boolean; message?: string }; + loading?: (row: any, hookData?: any) => boolean; // Field mappings for flexible data access idField?: string; // Field name for the unique identifier (default: 'id') nameField?: string; // Field name for display name (default: 'name' or 'file_name') typeField?: string; // Field name for type/mime type (default: 'type' or 'mime_type') contentField?: string; // Field name for content (default: 'content') + statusField?: string; // Field name for status (used by connect action) // Operation and loading state names operationName?: string; // Name of the operation function in hookData loadingStateName?: string; // Name of the loading state in hookData fetchItemFunctionName?: string; // Name of the function in hookData to fetch a single item by ID (for edit button) + // Navigation and mode (for play action) + navigateTo?: string; // Path to navigate to after action + mode?: string; // Mode to set (e.g., 'prompt', 'workflow') } // Custom action button configuration (for entity-specific actions like download, connect, play, sendPasswordLink) diff --git a/src/hooks/useTrustee.ts b/src/hooks/useTrustee.ts index 7aff5a1..9d68038 100644 --- a/src/hooks/useTrustee.ts +++ b/src/hooks/useTrustee.ts @@ -1,6 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; import { useApiRequest } from './useApi'; -import { getUserDataCache } from '../utils/userCache'; import api from '../api'; import { usePermissions, type UserPermissions } from './usePermissions'; import {