Compare commits
No commits in common. "d8b41e613be0c1f1370dd0a390d30f198c0f3772" and "f1c75faba198253d1a59c689cef685c3717e3a10" have entirely different histories.
d8b41e613b
...
f1c75faba1
10 changed files with 0 additions and 400 deletions
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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 (
|
|
||||||
<QueryClientProvider value={queryClient}>
|
|
||||||
<AppRouter />
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
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 <div className="text-center py-5">Loading loads...</div>;
|
|
||||||
if (isError) return <div className="text-center py-5 text-danger">Error loading loads</div>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mt-4">
|
|
||||||
<h2>Loads Management</h2>
|
|
||||||
|
|
||||||
{/* Filters */}
|
|
||||||
<div className="row mb-3 g-2">
|
|
||||||
<div className="col-md-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Search cities..."
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-4">
|
|
||||||
<select
|
|
||||||
className="form-select"
|
|
||||||
value={filterStatus}
|
|
||||||
onChange={(e) => setFilterStatus(e.target.value)}
|
|
||||||
>
|
|
||||||
<option value="">All Statuses</option>
|
|
||||||
<option value="settled">Settled</option>
|
|
||||||
<option value="loaded / in transit">In Transit</option>
|
|
||||||
<option value="pending collection">Pending Collection</option>
|
|
||||||
<option value="cancelled">Cancelled</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Loads Table */}
|
|
||||||
<table className="table table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Route</th>
|
|
||||||
<th>Shipper</th>
|
|
||||||
<th>Freight</th>
|
|
||||||
<th>Commission</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{loads.map((load) => (
|
|
||||||
<tr key={load.id}>
|
|
||||||
<td>{new Date(load.date).toLocaleDateString('en-IN')}</td>
|
|
||||||
<td>{load.from_city} → {load.to_city}</td>
|
|
||||||
<td>{load.shipper?.name || '—'}</td>
|
|
||||||
<td>{formatINR(load.freight_charged)}</td>
|
|
||||||
<td>{formatINR(load.commission)}</td>
|
|
||||||
<td>
|
|
||||||
<span className={`badge bg-${getStatusColor(load.status)}`}>
|
|
||||||
{load.status}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoadsList;
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { supabase } from '../supabaseClient';
|
|
||||||
|
|
||||||
function ShippersList() {
|
|
||||||
const [filterName, setFilterName] = useState('');
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
|
||||||
|
|
||||||
const { data: shippers = [], isLoading, isError } = useQuery({
|
|
||||||
queryKey: ['shippers', filterName, searchTerm],
|
|
||||||
queryFn: async () => {
|
|
||||||
let query = supabase
|
|
||||||
.from('shippers')
|
|
||||||
.select('id, name, phone, email, city, state')
|
|
||||||
.order('name');
|
|
||||||
|
|
||||||
if (filterName) {
|
|
||||||
query = query.eq('name', filterName);
|
|
||||||
}
|
|
||||||
if (searchTerm) {
|
|
||||||
query = query.or(`name.ilike.%${searchTerm}%,email.ilike.%${searchTerm}%`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data, error } = await query;
|
|
||||||
if (error) throw error;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
staleTime: 5 * 60 * 1000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isLoading) return <div className="text-center py-5">Loading shippers...</div>;
|
|
||||||
if (isError) return <div className="text-center py-5 text-danger">Error loading shippers</div>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="container mt-4">
|
|
||||||
<h2>Shippers</h2>
|
|
||||||
|
|
||||||
<div className="row mb-3 g-2">
|
|
||||||
<div className="col-md-6">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Search shippers..."
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-md-4">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder="Filter by name"
|
|
||||||
value={filterName}
|
|
||||||
onChange={(e) => setFilterName(e.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table className="table table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Phone</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>City</th>
|
|
||||||
<th>State</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{shippers.map((shipper) => (
|
|
||||||
<tr key={shipper.id}>
|
|
||||||
<td>{shipper.name}</td>
|
|
||||||
<td>{shipper.phone}</td>
|
|
||||||
<td>{shipper.email}</td>
|
|
||||||
<td>{shipper.city}</td>
|
|
||||||
<td>{shipper.state}</td>
|
|
||||||
<td>
|
|
||||||
<button className="btn btn-sm btn-outline-primary">View</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ShippersList;
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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 (
|
|
||||||
<QueryClientProvider value={queryClient}>
|
|
||||||
<supabaseClient />
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
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(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>
|
|
||||||
);
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { createRootRoute, RouterProvider, createBrowserRouter } from '@tanstack/react-router';
|
|
||||||
import LoadsList from './components/LoadsList';
|
|
||||||
import ShippersList from './components/ShippersList';
|
|
||||||
|
|
||||||
// Root layout – can later include a navbar or sidebar
|
|
||||||
function RootLayout({ children }) {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{/* Simple header */}
|
|
||||||
<header className="bg-primary text-white p-3">
|
|
||||||
<h1 className="mb-0" style={{ fontSize: '1.5rem' }}>FreightDesk Dashboard</h1>
|
|
||||||
</header>
|
|
||||||
<main>{children}</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the root route (layout)
|
|
||||||
const rootRoute = createRootRoute({
|
|
||||||
component: RootLayout,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Loads page route
|
|
||||||
const loadsRoute = rootRoute.createRoute({
|
|
||||||
path: '/loads',
|
|
||||||
component: LoadsList,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Shippers page route
|
|
||||||
const shippersRoute = rootRoute.createRoute({
|
|
||||||
path: '/shippers',
|
|
||||||
component: ShippersList,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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, shippersRoute, indexRoute]);
|
|
||||||
|
|
||||||
export const router = createBrowserRouter({ routeTree });
|
|
||||||
|
|
||||||
export function AppRouter() {
|
|
||||||
return <RouterProvider router={router} />;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import { defineConfig } from 'vite';
|
|
||||||
import react from '@vitejs/plugin-react';
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react()],
|
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
Reference in a new issue