Compare commits
No commits in common. "c4343e69582474261dea455d1dc54a6debb6b26b" and "c1c680d92b4928a2ffed858f4f5eb7783f90c35e" have entirely different histories.
c4343e6958
...
c1c680d92b
2 changed files with 22 additions and 194 deletions
|
|
@ -1,164 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
@ -1,35 +1,26 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRootRoute, RouterProvider, createBrowserRouter } from '@tanstack/react-router';
|
import { createRootRoute, RouterProvider, createBrowserRouter, Link } 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 ShipperDashboard from './components/ShipperDashboard';
|
import VehicleMap from './components/VehicleMap';
|
||||||
|
|
||||||
// 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 className="min-vh-100 bg-light">
|
<div>
|
||||||
{/* Navigation Header */}
|
{/* Simple header */}
|
||||||
<header className="bg-primary text-white p-3 shadow-sm">
|
<header className="bg-primary text-white p-3">
|
||||||
<div className="container d-flex justify-content-between align-items-center">
|
<h1 className="mb-0" style={{ fontSize: '1.5rem' }}>FreightDesk Dashboard</h1>
|
||||||
<h1 className="mb-0" style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>FreightDesk Dashboard</h1>
|
|
||||||
<nav>
|
|
||||||
<ul className="nav">
|
|
||||||
<li className="nav-item">
|
|
||||||
<a className="nav-link text-white" href="/loads">Loads</a>
|
|
||||||
</li>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
<main className="container py-4">
|
|
||||||
{children}
|
{/* Navigation */}
|
||||||
</main>
|
<nav className="mb-4">
|
||||||
|
<Link href="/loads" className="nav-link">Loads</Link>
|
||||||
|
<Link href="/shippers" className="nav-link">Shippers</Link>
|
||||||
|
<Link href="/vehicles" className="nav-link">Vehicles</Link>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>{children}</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -51,16 +42,17 @@ const shippersRoute = rootRoute.createRoute({
|
||||||
component: ShippersList,
|
component: ShippersList,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Shipper Dashboard route
|
// Vehicle tracking page route
|
||||||
const shipperDashboardRoute = rootRoute.createRoute({
|
const vehiclesRoute = rootRoute.createRoute({
|
||||||
path: '/shipper-dashboard',
|
path: '/vehicles',
|
||||||
component: ShipperDashboard,
|
component: VehicleMap,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -69,10 +61,10 @@ const indexRoute = rootRoute.createRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build the router
|
// Build the router
|
||||||
const routeTree = rootRoute.addChildren([loadsRoute, shippersRoute, shipperDashboardRoute, indexRoute]);
|
const routeTree = rootRoute.addChildren([loadsRoute, shippersRoute, vehiclesRoute, indexRoute]);
|
||||||
|
|
||||||
export const router = createBrowserRouter({ routeTree });
|
export const router = createBrowserRouter({ routeTree });
|
||||||
|
|
||||||
export function AppRouter() {
|
export function AppRouter() {
|
||||||
return <RouterProvider router={router} />;
|
return <RouterProvider router={router} />;
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue