feat[agent]: integrate bidding system into Loads page with modal submission

This commit is contained in:
Hermes Agent 2026-06-08 01:30:46 +00:00
parent 6a8e7490d2
commit c1c680d92b
3 changed files with 195 additions and 18 deletions

View file

@ -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 <div className="text-center py-4">Loading bids...</div>;
if (isError) return <div className="text-center py-4 text-danger">Error loading bids</div>;
return (
<div className="container mt-4">
<h2>Active Bids</h2>
{bids.length === 0 ? (
<p className="text-muted">No bids yet. Be the first to offer!</p>
) : (
<table className="table table-hover">
<thead>
<tr>
<th>Driver</th>
<th>Bid Amount</th>
<th>Status</th>
<th>Time</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{bids.map((bid) => (
<tr key={bid.id}>
<td>{bid.driver?.username || 'Unknown'}</td>
<td>{parseFloat(bid.bid_amount).toLocaleString('en-IN')}</td>
<td>
<span className={`badge ${
bid.status === 'accepted'
? 'bg-success'
: bid.status === 'rejected'
? 'bg-danger'
: bid.status === 'counter_offer'
? 'bg-warning'
: 'bg-secondary'
}`}>
{bid.status}
</span>
</td>
<td>{new Date(bid.created_at).toLocaleString()}</td>
<td>
{/* Action buttons will be added for shipper to accept/reject */}
<button className="btn btn-sm btn-outline-primary">View</button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
);
}
export default BidFeed;

View file

@ -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 (
<div className="modal d-block" style={{ backgroundColor: 'rgba(0,0,0,0.5)' }}>
<div className="modal-dialog">
<form onSubmit={handleSubmit}>
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">Submit Bid for Load</h5>
<button type="button" className="btn-close" onClick={onClose}></button>
</div>
<div className="modal-body">
<div className="mb-3">
<label className="form-label">Bid Amount ()</label>
<input
type="number"
step="0.01"
className="form-control"
value={bidAmount}
onChange={(e) => setBidAmount(e.target.value)}
required
/>
</div>
<div className="mb-3">
<label className="form-label">Notes (Optional)</label>
<textarea
className="form-control"
rows="3"
value={notes}
onChange={(e) => setNotes(e.target.value)}
/>
</div>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={onClose}>
Cancel
</button>
<button type="submit" className="btn btn-primary" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit Bid'}
</button>
</div>
</div>
</form>
</div>
</div>
);
}
export default BidSubmissionModal;

View file

@ -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 <div className="text-center py-5">Loading loads...</div>;
@ -106,6 +100,7 @@ function LoadsList() {
<th>Freight</th>
<th>Commission</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
@ -120,11 +115,31 @@ function LoadsList() {
<span className={`badge bg-${getStatusColor(load.status)}`}>
{load.status}
</span>
<button
className="btn btn-sm btn-outline-primary ms-2"
onClick={() => {
setSelectedLoadId(load.id);
setShowBidModal(true);
}}
>
Bid
</button>
</td>
</tr>
))}
</tr>
</tbody>
</table>
{/* Modal */}
{showBidModal && (
<BidSubmissionModal
loadId={selectedLoadId}
onClose={() => {
setShowBidModal(false);
setSelectedLoadId(null);
}}
/>
)}
</div>
);
}