Compare commits

...

2 commits

2 changed files with 194 additions and 22 deletions

View file

@ -0,0 +1,164 @@
import React, { useState, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';
import { supabase } from '../supabaseClient';
import { formatINR } from '../lib/india';
import { getStatusColor } from '../lib/india';
function ShipperDashboard() {
const [selectedLoadId, setSelectedLoadId] = useState(null);
const [bidModalOpen, setBidModalOpen] = useState(false);
const { data: loads, isLoading, isError } = useQuery({
queryKey: ['loads'],
queryFn: async () => {
const { data, error } = await supabase
.from('loads')
.select('*, shipper:shippers(name), vehicle:vehicles(number)')
.order('date', { ascending: false })
.limit(100);
if (error) throw error;
return data;
},
});
const { data: bids } = useQuery({
queryKey: ['bids', selectedLoadId],
queryFn: async () => {
const { data, error } = await supabase
.from('bids')
.select('*, driver:portal_users(username)')
.eq('load_id', selectedLoadId)
.order('created_at', { ascending: false });
if (error) throw error;
return data;
},
});
const handleAccept = async (bidId) => {
await fetch('/api/update-bid-status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bidId, newStatus: 'accepted' }),
});
// Refresh data after update
await refetch();
};
const { refetch } = useQuery({
queryKey: ['loads'],
queryFn: async () => {
const { data, error } = await supabase
.from('loads')
.select('*, shipper:shippers(name), vehicle:vehicles(number)')
.order('date', { ascending: false })
.limit(100);
if (error) throw error;
return data;
},
);
return (
<div className="container mt-4">
<h2>Shipper Dashboard</h2>
{isLoading ? (
<div className="text-center py-5">Loading loads...</div>
) : isError ? (
<div className="text-center py-5 text-danger">{isError}</div>
) : (
<>
<div className="d-flex justify-content-between mb-3">
<button className="btn btn-outline-secondary" onClick={() => setBidModalOpen(true)}>
New Bid
</button>
{selectedLoadId && (
<button className="btn btn-outline-primary" onClick={() => setBidModalOpen(true)}>
New Bid
</button>
</button>
</div>
<table className="table table-striped">
<thead>
<tr>
<th>Date</th>
<th>Route</th>
<th>Shipper</th>
<th>Freight</th>
<th>Status</th>
<th>Bids</th>
<th>Actions</th>
</tr>
</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>
<span className={`badge bg-${getStatusColor(load.status)}`}>
{load.status}
</span>
</td>
<td>
{bids ? (
<div>
{bids.map((b) => (
<div key={bid.id} className="badge bg-secondary mb-1">
{bid.driver?.username || ' — '}: {formatINR(bid.bid_amount)}
</div>
))}
</div>
) : ' — '}
{selectedLoadId === load.id && (
<div>
{bids.length > 0 && (
<>
<button
className="btn btn-sm btn-outline-success"
onClick={() => handleAccept(bid.bid_id)}
disabled={bid.status !== 'pending'}
>
Accept
</button>
<button
className="btn btn-sm btn-outline-danger ms-1"
onClick={() => {
if (confirm('Reject this bid?')) {
await fetch('/api/update-bid-status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bidId: bid.id, newStatus: 'rejected' })
});
await refetch();
}
}
}
</button>
}
</div>
}
</td>
</tr>
</tr>
))}
</tbody>
</table>
{/* Modal for creating a new bid */}
{bidModalOpen && (
<BidSubmissionModal
loadId={selectedLoadId}
onClose={() => {
setBidModalOpen(false);
setSelectedLoadId(null);
}}
/>
)}
</div>
);
}
export default ShipperDashboard;

View file

@ -1,26 +1,35 @@
import React from 'react'; import React from 'react';
import { createRootRoute, RouterProvider, createBrowserRouter, Link } from '@tanstack/react-router'; import { createRootRoute, RouterProvider, createBrowserRouter } from '@tanstack/react-router';
import LoadsList from './components/LoadsList'; import LoadsList from './components/LoadsList';
import ShippersList from './components/ShippersList'; import ShippersList from './components/ShippersList';
import VehicleMap from './components/VehicleMap'; import ShipperDashboard from './components/ShipperDashboard';
// Root layout can later include a navbar or sidebar // Root layout can later include a navbar or sidebar
function RootLayout({ children }) { function RootLayout({ children }) {
return ( return (
<div> <div className="min-vh-100 bg-light">
{/* Simple header */} {/* Navigation Header */}
<header className="bg-primary text-white p-3"> <header className="bg-primary text-white p-3 shadow-sm">
<h1 className="mb-0" style={{ fontSize: '1.5rem' }}>FreightDesk Dashboard</h1> <div className="container d-flex justify-content-between align-items-center">
</header> <h1 className="mb-0" style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>FreightDesk Dashboard</h1>
<nav>
{/* Navigation */} <ul className="nav">
<nav className="mb-4"> <li className="nav-item">
<Link href="/loads" className="nav-link">Loads</Link> <a className="nav-link text-white" href="/loads">Loads</a>
<Link href="/shippers" className="nav-link">Shippers</Link> </li>
<Link href="/vehicles" className="nav-link">Vehicles</Link> <li className="nav-item">
<a className="nav-link text-white" href="/shippers">Shippers</a>
</li>
<li className="nav-item">
<a className="nav-link text-white" href="/shipper-dashboard">Shipper Dashboard</a>
</li>
</ul>
</nav> </nav>
</div>
<main>{children}</main> </header>
<main className="container py-4">
{children}
</main>
</div> </div>
); );
} }
@ -42,17 +51,16 @@ const shippersRoute = rootRoute.createRoute({
component: ShippersList, component: ShippersList,
}); });
// Vehicle tracking page route // Shipper Dashboard route
const vehiclesRoute = rootRoute.createRoute({ const shipperDashboardRoute = rootRoute.createRoute({
path: '/vehicles', path: '/shipper-dashboard',
component: VehicleMap, component: ShipperDashboard,
}); });
// Default route redirect to /loads // Default route redirect to /loads
const indexRoute = rootRoute.createRoute({ const indexRoute = rootRoute.createRoute({
path: '/', path: '/',
component: () => { component: () => {
// Simple redirect component
React.useEffect(() => { React.useEffect(() => {
window.location.replace('/loads'); window.location.replace('/loads');
}, []); }, []);
@ -61,7 +69,7 @@ const indexRoute = rootRoute.createRoute({
}); });
// Build the router // Build the router
const routeTree = rootRoute.addChildren([loadsRoute, shippersRoute, vehiclesRoute, indexRoute]); const routeTree = rootRoute.addChildren([loadsRoute, shippersRoute, shipperDashboardRoute, indexRoute]);
export const router = createBrowserRouter({ routeTree }); export const router = createBrowserRouter({ routeTree });