From f75be3212321d415ae031880bd4ce9db3945b521 Mon Sep 17 00:00:00 2001 From: DarrenT~ Date: Thu, 8 May 2025 13:39:31 +0200 Subject: [PATCH] added prompter, theme for canvas api, added endpoint to delete existing routes --- README.md | 37 +- frontend/src/assets/roc_logo.png | Bin 0 -> 41682 bytes frontend/src/components/CanvasEndpoints.tsx | 427 ++++++++++++++++++ .../CanvasEndpoints/CanvasDataTable.tsx | 372 +++++++++++++++ .../CanvasEndpoints/ChatGptModal.tsx | 120 +++++ .../CanvasEndpoints/PromptInput.tsx | 37 ++ .../CanvasEndpoints/SystemSelector.tsx | 33 ++ .../CanvasEndpoints/commandParser.ts | 83 ++++ .../src/components/CanvasEndpoints/index.tsx | 90 ++++ frontend/src/components/ChatGPTModal.tsx | 4 +- .../src/components/SystemSelectionModal.tsx | 11 +- frontend/src/config/canvas.ts | 13 + frontend/src/index.css | 24 +- frontend/src/layouts/DualModal.tsx | 21 +- frontend/src/layouts/Sidebar.tsx | 7 + frontend/src/router.tsx | 9 + frontend/src/services/canvasApi.ts | 78 ++++ frontend/src/state/stores/useCanvasStore.ts | 35 ++ frontend/src/styles/canvas-theme.css | 100 ++++ 19 files changed, 1474 insertions(+), 27 deletions(-) create mode 100644 frontend/src/assets/roc_logo.png create mode 100644 frontend/src/components/CanvasEndpoints.tsx create mode 100644 frontend/src/components/CanvasEndpoints/CanvasDataTable.tsx create mode 100644 frontend/src/components/CanvasEndpoints/ChatGptModal.tsx create mode 100644 frontend/src/components/CanvasEndpoints/PromptInput.tsx create mode 100644 frontend/src/components/CanvasEndpoints/SystemSelector.tsx create mode 100644 frontend/src/components/CanvasEndpoints/commandParser.ts create mode 100644 frontend/src/components/CanvasEndpoints/index.tsx create mode 100644 frontend/src/config/canvas.ts create mode 100644 frontend/src/services/canvasApi.ts create mode 100644 frontend/src/state/stores/useCanvasStore.ts create mode 100644 frontend/src/styles/canvas-theme.css diff --git a/README.md b/README.md index c6536bc..0a9bc33 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,15 @@ fusero-app-boilerplate/ ## Development Setup -### Option 1: Running with Docker (Recommended for Development) +### Important Note: Database Must Run in Docker +The PostgreSQL database must always run in Docker, regardless of your development setup choice. This ensures consistent database behavior across all environments. + +To start the database: +```bash +docker-compose -f docker-compose.dev.yml up db +``` + +### Option 1: Running Everything in Docker (Recommended for Development) 1. **Start the Development Environment** ```bash @@ -34,7 +42,14 @@ fusero-app-boilerplate/ ### Option 2: Running Services Separately (Recommended for Debugging) -For better debugging experience, you can run the frontend and backend in separate terminal windows: +For better debugging experience, you can run the frontend and backend in separate terminal windows, while keeping the database in Docker: + +1. **First, ensure the database is running in Docker** + ```bash + docker-compose -f docker-compose.dev.yml up db + ``` + +2. **Then, in separate terminal windows:** #### Terminal 1: Backend Service ```bash @@ -87,17 +102,22 @@ The frontend will be available at http://localhost:3000 ## Development Best Practices -1. **Running Services Separately** +1. **Database Management** + - Always run the database in Docker + - Use `docker-compose.dev.yml` for development + - Never run PostgreSQL directly on your host machine + +2. **Running Services Separately** - For development, it's recommended to run frontend and backend in separate terminal windows - This allows for better debugging and hot-reloading - You can see logs from each service clearly -2. **Code Organization** +3. **Code Organization** - Frontend code should be in the `frontend/` directory - Backend code should be in the `backend/` directory - Shared types and utilities should be in their respective directories -3. **Version Control** +4. **Version Control** - Commit `package-lock.json` files - Don't commit `.env` files - Use meaningful commit messages @@ -122,9 +142,14 @@ The backend API is documented using Swagger/OpenAPI. After starting the backend ``` 2. **Database Issues** - - Ensure PostgreSQL is running and accessible + - Ensure PostgreSQL is running in Docker - Check database connection settings in `.env` - Verify database migrations are up to date + - If database issues persist, try: + ```bash + docker-compose -f docker-compose.dev.yml down + docker-compose -f docker-compose.dev.yml up db + ``` 3. **CORS Issues** - If you see CORS errors, verify the frontend's API base URL diff --git a/frontend/src/assets/roc_logo.png b/frontend/src/assets/roc_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c11486f5ecb46a9f2adfc6551345177456198428 GIT binary patch literal 41682 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 >JhY9d=%erm7bGZqsk9)3y_U&t1`Ei!1?2ZBJ7x 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[]); + const [filters, setFilters] = useState(null); + const [globalFilterValue, setGlobalFilterValue] = useState(''); + const [showCreateModal, setShowCreateModal] = useState(false); + const [newEndpoint, setNewEndpoint] = useState({ name: '', method: '', path: '', description: '' }); + const sizeOptions: { label: string; value: TSizeOptionValue }[] = [ + { label: 'Small', value: 'small' }, + { label: 'Normal', value: 'normal' }, + { label: 'Large', value: 'large' }, + ]; + const [size, setSize] = useState(sizeOptions[0].value); + const [showCallModal, setShowCallModal] = useState(false); + const [callModalData, setCallModalData] = useState([]); + const [callModalColumns, setCallModalColumns] = useState([]); + const [callModalLoading, setCallModalLoading] = useState(false); + const [callEndpoint, setCallEndpoint] = useState(null); + const [callModalFilters, setCallModalFilters] = useState(null); + const [callModalGlobalFilterValue, setCallModalGlobalFilterValue] = useState(''); + const [callModalSize, setCallModalSize] = useState(sizeOptions[0].value); + const [callModalFirst, setCallModalFirst] = useState(0); + const [callModalRows, setCallModalRows] = useState(10); + const [showChatGptModal, setShowChatGptModal] = useState(false); + const [chatPrompt, setChatPrompt] = useState(''); + const [chatMessages, setChatMessages] = useState<{ role: 'user' | 'assistant'; content: string }[]>([]); + const [chatLoading, setChatLoading] = useState(false); + const [chatError, setChatError] = useState(null); + const toast = useRef(null); + + useEffect(() => { + initFilters(); + fetchEndpoints(); + }, []); + + const fetchEndpoints = async () => { + try { + const response = await api('get', '/api/v1/canvas-api/endpoints'); + setEndpoints(response.data); + } catch (error) { + console.error('Failed to fetch endpoints:', error); + } + }; + + const initFilters = () => { + setFilters({ + global: { value: null, matchMode: FilterMatchMode.CONTAINS }, + }); + setGlobalFilterValue(''); + }; + + const clearFilter = () => { + initFilters(); + }; + + const onGlobalFilterChange = (e: { target: { value: any } }) => { + const value = e.target.value; + let _filters = { ...filters }; + _filters['global'].value = value; + setFilters(_filters); + setGlobalFilterValue(value); + }; + + // Utility to ensure path never starts with /api/v1 + function sanitizeCanvasPath(path: string) { + return path.replace(/^\/api\/v1/, ''); + } + + const handleCreateEndpoint = async () => { + try { + const sanitizedEndpoint = { ...newEndpoint, path: sanitizeCanvasPath(newEndpoint.path) }; + await api('post', '/api/v1/canvas-api/endpoints', sanitizedEndpoint); + setShowCreateModal(false); + setNewEndpoint({ name: '', method: '', path: '', description: '' }); + fetchEndpoints(); + } catch (error) { + console.error('Failed to create endpoint:', error); + } + }; + + const handleCallEndpoint = async (endpoint: any) => { + setShowCallModal(true); + setCallModalLoading(true); + setCallEndpoint(endpoint); + try { + const res = await api('post', '/api/v1/canvas-api/proxy-external', { + path: endpoint.path, + method: endpoint.method || 'GET', + }); + let data = res.data; + if (!Array.isArray(data)) data = [data]; + setCallModalData(data); + setCallModalColumns(data.length > 0 ? Object.keys(data[0]) : []); + } catch (error) { + setCallModalData([]); + setCallModalColumns([]); + } finally { + setCallModalLoading(false); + } + }; + + const initCallModalFilters = () => { + setCallModalFilters({ + global: { value: null, matchMode: FilterMatchMode.CONTAINS }, + }); + setCallModalGlobalFilterValue(''); + }; + + const clearCallModalFilter = () => { + initCallModalFilters(); + }; + + const onCallModalGlobalFilterChange = (e: { target: { value: any } }) => { + const value = e.target.value; + let _filters = { ...callModalFilters }; + _filters['global'].value = value; + setCallModalFilters(_filters); + setCallModalGlobalFilterValue(value); + }; + + const handleChatPromptSend = async () => { + if (!chatPrompt.trim()) return; + setChatLoading(true); + setChatError(null); + setChatMessages(prev => [...prev, { role: 'user', content: chatPrompt }]); + try { + const formattedRequest = { + data: chatPrompt, + }; + const response = await api( + 'post', + '/api/v1/canvas-api/chatgpt/completions', + formattedRequest, + undefined, + 'json', + 60, + true + ); + const data = response.data; + let isEndpointJson = false; + if (data && data.responseText) { + let parsed; + try { + parsed = typeof data.responseText === 'string' ? JSON.parse(data.responseText) : data.responseText; + } catch (e) { + parsed = null; + } + if (parsed && parsed.name && parsed.method && parsed.path) { + // Auto-create endpoint + const sanitizedParsed = { ...parsed, path: sanitizeCanvasPath(parsed.path) }; + await api('post', '/api/v1/canvas-api/endpoints', sanitizedParsed); + fetchEndpoints(); + isEndpointJson = true; + if (toast.current) { + toast.current.show({ severity: 'success', summary: 'Endpoint Created', detail: `Endpoint "${parsed.name}" created successfully!` }); + } + } + } + if (!isEndpointJson) { + setChatMessages(prev => [...prev, { role: 'assistant', content: typeof data.responseText === 'string' ? data.responseText : JSON.stringify(data.responseText, null, 2) }]); + } + } catch (err) { + setChatError('Failed to get response from server.'); + setChatMessages(prev => [...prev, { role: 'assistant', content: 'Failed to get response from server.' }]); + } finally { + setChatLoading(false); + setChatPrompt(''); + } + }; + + const handleChatPromptKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleChatPromptSend(); + } + }; + + const handleDeleteEndpoint = async (endpoint: any) => { + try { + await api('delete', `/api/v1/canvas-api/endpoints/${endpoint.id}`); + fetchEndpoints(); + if (toast.current) { + toast.current.show({ severity: 'success', summary: 'Endpoint Deleted', detail: `Endpoint "${endpoint.name}" deleted successfully!` }); + } + } catch (error) { + if (toast.current) { + toast.current.show({ severity: 'error', summary: 'Delete Failed', detail: 'Failed to delete endpoint.' }); + } + } + }; + + const renderHeader = () => ( + ( +
+
+ )} + /> + ); + + const renderCallButton = (rowData: any) => ( +
+
+ ); + + const renderCallModalHeader = () => ( + handleCallEndpoint(callEndpoint)} + clearFilter={clearCallModalFilter} + /> + ); + + return ( +
+

Canvas Endpoints

+ + + + + + + + + + setShowCreateModal(false)} + header="Create New Endpoint" + footer={ +
+
+ } + > +
+
+ + setNewEndpoint({ ...newEndpoint, name: e.target.value })} /> +
+
+ + setNewEndpoint({ ...newEndpoint, method: e.value })} + placeholder="Select a method" + /> +
+
+ + setNewEndpoint({ ...newEndpoint, path: e.target.value })} /> +
+
+ + setNewEndpoint({ ...newEndpoint, description: e.target.value })} /> +
+
+
+ + setShowCallModal(false)} + header={callEndpoint ? `Response for ${callEndpoint.name}` : 'Endpoint Response'} + style={{ width: '96vw', maxWidth: 1800, minHeight: 600 }} + modal + > + {callModalLoading ? ( +
+ +
+ ) : callModalData.length > 0 ? ( +
+
+ + {callModalColumns.map((col) => ( + { + const value = rowData[col]; + if (typeof value === 'object' && value !== null) { + return {JSON.stringify(value)}; + } + return value; + }} + /> + ))} + +
+
+
+ + + Showing {callModalFirst + 1} to {Math.min(callModalFirst + callModalRows, callModalData.length)} of {callModalData.length} entries + + + + + {Math.floor(callModalFirst / callModalRows) + 1} + + + +
+
+
+ ) : ( +
No data found or error.
+ )} +
+ + setShowChatGptModal(false)} + header="ChatGPT Assistant" + style={{ width: '50vw' }} + > +
+
+ {chatMessages.length === 0 &&
Start a conversation with ChatGPT...
} + {chatMessages.map((msg, idx) => ( +
+ {msg.content} +
+ ))} +
+
+ setChatPrompt(e.target.value)} + onKeyDown={handleChatPromptKeyDown} + disabled={chatLoading} + /> +
+ {chatError &&
{chatError}
} +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/CanvasEndpoints/CanvasDataTable.tsx b/frontend/src/components/CanvasEndpoints/CanvasDataTable.tsx new file mode 100644 index 0000000..31f9908 --- /dev/null +++ b/frontend/src/components/CanvasEndpoints/CanvasDataTable.tsx @@ -0,0 +1,372 @@ +import React from 'react'; +import { DataTable } from 'primereact/datatable'; +import { Column } from 'primereact/column'; +import { Card } from 'primereact/card'; + +interface CanvasData { + [key: string]: any; +} + +interface CanvasDataTableProps { + data: CanvasData[]; + loading: boolean; + error: string | null; +} + +const CanvasDataTable: React.FC = ({ + data, + loading, + errorimport { Column } from 'primereact/column'; + import { FilterMatchMode, FilterOperator } from 'primereact/api'; + import { useEffect, useRef, useState } from 'react'; + import { api } from '../../services/api.ts'; + + import TableGroupHeader from '../../shared/components/_V1/TableGroupHeader.tsx'; + import TableGroup from '../../shared/components/_V1/TableGroup.tsx'; + + import { Toast } from 'primereact/toast'; + import ConfirmModal from '../../shared/components/modals/ConfirmModal.tsx'; + import CreateVoucherModal from './CreateVoucherModal.tsx'; + // import EditVoucherModal from './AddVoucherCodeModal.tsx'; + import LoadingPage from '../../shared/components/_V1/LoadingPage.tsx'; + import DetailModal from './DetailModal.tsx'; + import AddVoucherCodeModal, { AddVoucherCodeModalProps } from './AddVoucherCodeModal.tsx'; + import { Voucher, useVoucherStore } from '../../state/stores/Mews/useMewsVoucherStore.ts'; + import { TSizeOptionValue } from '../../types/DataTable.types.ts'; + import Button from '../../shared/components/_V2/Button.tsx'; + + export default function VoucherTableGroup() { + const { createVoucher, fetchVouchers, updateVoucher, vouchers, addVoucherCode } = + useVoucherStore(); + + const [filters, setFilters] = useState(null); + const [globalFilterValue, setGlobalFilterValue] = useState(''); + + const [showCreateVoucherModal, setShowCreateVoucherModal] = useState(false); + const [showConfirmDeleteModal, setShowConfirmDeleteModal] = useState(false); + + const [showAddCodeModal, setShowAddCodeModal] = useState(false); + const [currentVoucherForCode, setCurrentVoucherForCode] = useState(null); + + const [detailModalVisible, setDetailModalVisible] = useState(false); + const [detailContent, setDetailContent] = useState([]); + const [detailTitle, setDetailTitle] = useState(''); + + const [selectedVouchers, setSelectedVouchers] = useState(null); + + const toast = useRef(null); + + const [isLoading, setIsLoading] = useState(false); + + // Change table row size + const sizeOptions: { label: string; value: TSizeOptionValue }[] = [ + { label: 'Small', value: 'small' }, + { label: 'Normal', value: 'normal' }, + { label: 'Large', value: 'large' }, + ]; + + const [size, setSize] = useState(sizeOptions[0].value); + + useEffect(() => { + initFilters(); + // fetchVouchers; + handleRefresh(); + }, []); + + const showToastMessage = (options) => { + toast.current?.show(options); + }; + + const initFilters = () => { + setFilters({ + global: { value: null, matchMode: FilterMatchMode.CONTAINS }, + representative: { value: null, matchMode: FilterMatchMode.IN }, + createdAt: { + operator: FilterOperator.AND, + constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }], + }, + }); + setGlobalFilterValue(''); + }; + + const clearFilter = () => { + initFilters(); + }; + + const onGlobalFilterChange = (e: { target: { value: any } }) => { + const value = e.target.value; + // @ts-ignore + let _filters = { ...filters }; + _filters['global'].value = value; + setFilters(_filters); + setGlobalFilterValue(value); + }; + + // TODO: refresh data + const handleRefresh = async () => { + setIsLoading(true); + await fetchVouchers(); + setIsLoading(false); + }; + + const formatDate = (value: string | number | Date) => { + const date = new Date(value); + return date.toLocaleDateString('en-US', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }); + }; + + const dateBodyTemplate = (rowData: { createdAt: string | number | Date }) => { + return formatDate(rowData.createdAt); + }; + + const actionButtons = () => { + return ( +
+ + {/* */} +
+ ); + }; + + const editButton = (rowData: Voucher) => { + return ; + }; + + const openDetailModal = (content, title) => { + setDetailContent(content); + setDetailTitle(title); + setDetailModalVisible(true); + }; + + const closeDetailModal = () => { + setDetailModalVisible(false); + }; + + const handleCreateVoucher = async (newVoucher: Voucher) => { + await createVoucher(newVoucher); + handleRefresh(); + setShowCreateVoucherModal(false); + }; + + const handleDeleteSelectedVouchers = () => { + const URL = '/neppeURL'; + const body = selectedVouchers.map((voucher) => { + return { + id: voucher.id, + }; + }); + api('delete', URL, body); + }; + + const voucherRatesBodyTemplate = (rowData) => { + if (rowData?.voucherCodes.length < 1) { + return ''; + } + const details = rowData.rates.map((rate) => `Name: ${rate.name}, Type: ${rate.type}`); + return ( +