From 4f53ee421078cf68d4d3656cfbc5ffca1fb26cdf Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 7 Jun 2026 20:00:45 +0000 Subject: [PATCH] feat[agent]: set up TanStack Router and Loads page component for React frontend --- frontend/package.json | 22 +++++ frontend/src/App.jsx | 24 +++++ frontend/src/components/LoadsList.jsx | 132 ++++++++++++++++++++++++++ frontend/src/index.css | 28 ++++++ frontend/src/index.jsx | 21 ++++ frontend/src/main.jsx | 12 +++ frontend/src/router.jsx | 48 ++++++++++ frontend/src/supabaseClient.js | 6 ++ frontend/vite.config.js | 10 ++ 9 files changed, 303 insertions(+) create mode 100644 frontend/package.json create mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/components/LoadsList.jsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/index.jsx create mode 100644 frontend/src/main.jsx create mode 100644 frontend/src/router.jsx create mode 100644 frontend/src/supabaseClient.js create mode 100644 frontend/vite.config.js diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2fdae96 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,22 @@ +{ + "name": "freightdesk-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite --port 3000", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.3.0", + "react-dom": "^18.3.0", + "@tanstack/react-query": "^5.0.0", + "@tanstack/react-router": "^1.0.0", + "@supabase/supabase-js": "^2.45.0", + "styled-components": "^6.0.0" + }, + "devDependencies": { + "vite": "^5.0.0", + "@vitejs/plugin-react": "^4.0.0" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..71a7041 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { AppRouter } from './router'; + +// Create QueryClient instance +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + refetchOnWindowFocus: false, + retry: 1, + } + } +}); + +function App() { + return ( + + + + ); +} + +export default App; \ No newline at end of file diff --git a/frontend/src/components/LoadsList.jsx b/frontend/src/components/LoadsList.jsx new file mode 100644 index 0000000..fcc46c4 --- /dev/null +++ b/frontend/src/components/LoadsList.jsx @@ -0,0 +1,132 @@ +import React, { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { supabase } from '../supabaseClient'; + +// Utility functions (mirroring india.js logic for now) +const formatINR = (n) => { + if (n === null || n === undefined || isNaN(n)) return '—'; + return '₹' + parseFloat(n).toLocaleString('en-IN'); +}; + +const getStatusColor = (status) => { + const colors = { + 'settled': 'success', + 'completed': 'success', + 'commission received': 'success', + 'reconciled': 'success', + 'loaded / in transit': 'primary', + 'assigned': 'primary', + 'assigned vehicle': 'primary', + 'pending collection': 'warning', + 'partially pending': 'warning', + 'fully pending from shipper': 'warning', + 'commission due': 'warning', + 'cancelled': 'danger', + 'partial': 'secondary', + 'available vehicle': 'secondary', + }; + return colors[status] || 'secondary'; +}; + +function LoadsList() { + const [filterStatus, setFilterStatus] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + + const { data: loads = [], isLoading, isError, refetch } = useQuery({ + queryKey: ['loads', filterStatus, searchTerm], + queryFn: async () => { + let query = supabase + .from('loads') + .select(` + id, + date, + from_city, + to_city, + freight_charged, + commission, + status, + shipper:shippers(name), + vehicle:vehicles(number) + `) + .order('date', { ascending: false }) + .limit(100); + + if (filterStatus) query = query.eq('status', filterStatus); + if (searchTerm) { + query = query.or(`from_city.ilike.%${searchTerm}%,to_city.ilike.%${searchTerm}%`); + } + + const { data, error } = await query; + if (error) throw error; + return data; + }, + staleTime: 5 * 60 * 1000, // 5 minutes + }); + + if (isLoading) return
Loading loads...
; + if (isError) return
Error loading loads
; + + return ( +
+

Loads Management

+ + {/* Filters */} +
+
+ setSearchTerm(e.target.value)} + /> +
+
+ +
+
+ + {/* Loads Table */} + + + + + + + + + + + + + {loads.map((load) => ( + + + + + + + + + ))} + +
DateRouteShipperFreightCommissionStatus
{new Date(load.date).toLocaleDateString('en-IN')}{load.from_city} → {load.to_city}{load.shipper?.name || '—'}{formatINR(load.freight_charged)}{formatINR(load.commission)} + + {load.status} + +
+
+ ); +} + +export default LoadsList; \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..c56cc20 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,28 @@ +/* Basic styling for FreightDesk frontend */ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; + background-color: #f8f9fa; +} + +#root { + min-height: 100vh; +} + +/* Status badge colors override */ +.badge.bg-success { + background-color: #28a745 !important; +} +.badge.bg-warning { + background-color: #ffc107 !important; + color: #212529 !important; +} +.badge.bg-danger { + background-color: #dc3545 !important; +} +.badge.bg-primary { + background-color: #0d6efd !important; +} +.badge.bg-secondary { + background-color: #6c757d !important; +} \ No newline at end of file diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx new file mode 100644 index 0000000..6f9c721 --- /dev/null +++ b/frontend/src/index.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +// Create QueryClient instance +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + // We'll define query state overrides in individual components + } + } +}); + +function App() { + return ( + + + + ); +} + +export default App; \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..2dc0623 --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './index.css'; + +const root = ReactDOM.createRoot(document.getElementById('root')); + +root.render( + + + +); \ No newline at end of file diff --git a/frontend/src/router.jsx b/frontend/src/router.jsx new file mode 100644 index 0000000..2e3fb8d --- /dev/null +++ b/frontend/src/router.jsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { createRootRoute, RouterProvider, createBrowserRouter } from '@tanstack/react-router'; +import LoadsList from './components/LoadsList'; + +// Root layout – can later include a navbar or sidebar +function RootLayout({ children }) { + return ( +
+ {/* Simple header */} +
+

FreightDesk Dashboard

+
+
{children}
+
+ ); +} + +// Define the root route (layout) +const rootRoute = createRootRoute({ + component: RootLayout, +}); + +// Loads page route +const loadsRoute = rootRoute.createRoute({ + path: '/loads', + component: LoadsList, +}); + +// Default route – redirect to /loads +const indexRoute = rootRoute.createRoute({ + path: '/', + component: () => { + // Simple redirect component + React.useEffect(() => { + window.location.replace('/loads'); + }, []); + return null; + }, +}); + +// Build the router +const routeTree = rootRoute.addChildren([loadsRoute, indexRoute]); + +export const router = createBrowserRouter({ routeTree }); + +export function AppRouter() { + return ; +} diff --git a/frontend/src/supabaseClient.js b/frontend/src/supabaseClient.js new file mode 100644 index 0000000..d9b39ad --- /dev/null +++ b/frontend/src/supabaseClient.js @@ -0,0 +1,6 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = import.meta.env.VITE_SUPABASE_URL; +const supabaseKey = import.meta.env.VITE_SUPABASE_ANON_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..030fe91 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + }, +});