diff --git a/frontend/src/components/BidFeed.jsx b/frontend/src/components/BidFeed.jsx
new file mode 100644
index 0000000..0347554
--- /dev/null
+++ b/frontend/src/components/BidFeed.jsx
@@ -0,0 +1,71 @@
+import React from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { supabase } from '../supabaseClient';
+
+function BidFeed({ loadId }) {
+ const { data: bids = [], isLoading, isError, refetch } = useQuery({
+ queryKey: ['bids', loadId],
+ queryFn: async () => {
+ const { data, error } = await supabase
+ .from('bids')
+ .select('*, driver:portal_users(username)')
+ .eq('load_id', loadId)
+ .order('created_at', { ascending: false });
+ if (error) throw error;
+ return data;
+ },
+ refetchInterval: 10000, // Poll every 10 seconds for updates
+ });
+
+ if (isLoading) return
Loading bids...
;
+ if (isError) return Error loading bids
;
+
+ return (
+
+
Active Bids
+ {bids.length === 0 ? (
+
No bids yet. Be the first to offer!
+ ) : (
+
+
+
+ | Driver |
+ Bid Amount |
+ Status |
+ Time |
+ Actions |
+
+
+
+ {bids.map((bid) => (
+
+ | {bid.driver?.username || 'Unknown'} |
+ ₹{parseFloat(bid.bid_amount).toLocaleString('en-IN')} |
+
+
+ {bid.status}
+
+ |
+ {new Date(bid.created_at).toLocaleString()} |
+
+ {/* Action buttons will be added for shipper to accept/reject */}
+
+ |
+
+ ))}
+
+
+ )}
+
+ );
+}
+
+export default BidFeed;
\ No newline at end of file
diff --git a/frontend/src/components/BidSubmissionModal.jsx b/frontend/src/components/BidSubmissionModal.jsx
new file mode 100644
index 0000000..9b69b27
--- /dev/null
+++ b/frontend/src/components/BidSubmissionModal.jsx
@@ -0,0 +1,91 @@
+import React, { useState } from 'react';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { supabase } from '../supabaseClient';
+
+function BidSubmissionModal({ loadId, onClose, onSuccess }) {
+ const [bidAmount, setBidAmount] = useState('');
+ const [notes, setNotes] = useState('');
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const queryClient = useQueryClient();
+
+ const mutation = useMutation({
+ mutationFn: async (bidData) => {
+ const { data, error } = await supabase
+ .from('bids')
+ .insert({
+ load_id: loadId,
+ driver_id: (await supabase.auth.getUser()).data.user?.id,
+ bid_amount: parseFloat(bidAmount),
+ notes: notes,
+ status: 'pending',
+ })
+ .select();
+ if (error) throw error;
+ return data[0];
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['bids', loadId] });
+ onSuccess?.();
+ onClose();
+ },
+ });
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setIsSubmitting(true);
+ try {
+ await mutation.mutateAsync();
+ } catch (err) {
+ console.error('Bid submission failed:', err.message);
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default BidSubmissionModal;
\ No newline at end of file
diff --git a/frontend/src/components/LoadsList.jsx b/frontend/src/components/LoadsList.jsx
index fcc46c4..8f1d591 100644
--- a/frontend/src/components/LoadsList.jsx
+++ b/frontend/src/components/LoadsList.jsx
@@ -1,8 +1,8 @@
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { supabase } from '../supabaseClient';
+import BidSubmissionModal from './BidSubmissionModal';
-// 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');
@@ -26,41 +26,35 @@ const getStatusColor = (status) => {
'available vehicle': 'secondary',
};
return colors[status] || 'secondary';
-};
+}
function LoadsList() {
const [filterStatus, setFilterStatus] = useState('');
const [searchTerm, setSearchTerm] = useState('');
+ const [showBidModal, setShowBidModal] = useState(false);
+ const [selectedLoadId, setSelectedLoadId] = useState(null);
- const { data: loads = [], isLoading, isError, refetch } = useQuery({
+ const { data: loads = [], isLoading, isError } = 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)
- `)
+ .select('*, 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}%`);
}
+ if (filterStatus) {
+ query = query.eq('status', filterStatus);
+ }
const { data, error } = await query;
if (error) throw error;
return data;
},
- staleTime: 5 * 60 * 1000, // 5 minutes
+ staleTime: 5 * 60 * 1000,
});
if (isLoading) return Loading loads...
;
@@ -69,7 +63,7 @@ function LoadsList() {
return (
Loads Management
-
+
{/* Filters */}
@@ -106,6 +100,7 @@ function LoadsList() {
Freight |
Commission |
Status |
+ |
@@ -120,11 +115,31 @@ function LoadsList() {
{load.status}
+
- ))}
+
+
+ {/* Modal */}
+ {showBidModal && (
+ {
+ setShowBidModal(false);
+ setSelectedLoadId(null);
+ }}
+ />
+ )}
);
}