import React, { useState } from 'react'; import { Building2, Pencil, Plus, Radar, Trash2 } from 'lucide-react'; import { Button, ConfirmModal, DataTable, EmptyState, Field, IconButton, Input, Modal, Pagination, Stack, Surface, Tabs, } from '@/components/ds'; import { useCarrierPricing, useCompetitorBranches, useCreateCarrierPricing, useCreateCompetitorBranch, useDeleteCarrierPricing, useDeleteCompetitorBranch, useUpdateCarrierPricing, useUpdateCompetitorBranch, } from '@/lib/doormileHooks'; import { orDash } from '@/lib/doormileFormat'; /** * Competitive intelligence — what the other carriers in each area charge. * * Two resources behind one page: competitor branches, and the carrier rate * cards collected against them. Both paginate on `page`/`limit` rather than the * `pageno`/`pagesize` convention the rest of the API uses, and both return far * more rows than one page holds — so the pager here is server-side, not a slice * of an already-complete list. * * Rate fields are free text on purpose. Real values include "600", "25-35", * "Not Answered" and "." — casting them to numbers would discard most of the * data that was actually collected. */ const PAGE_SIZE = 50; const TABS = [ { value: 'branches', label: 'Competitor branches' }, { value: 'pricing', label: 'Carrier pricing' }, ]; const EMPTY_BRANCH = { id: null, company: '', area: '', phone: '', address: '', pincodes: '', rate_per_kg: '', offers_pickup: '', offers_drop: '', time_in_days: '', packing_charge: '', }; const EMPTY_CARRIER = { id: null, company: '', weight_slab: '', service_type: '', zone: '', rate: '', delivery_time: '', }; export default function CompetitiveIntel() { const [tab, setTab] = useState('branches'); const [page, setPage] = useState(1); const [dialogOpen, setDialogOpen] = useState(false); const [branchForm, setBranchForm] = useState(EMPTY_BRANCH); const [carrierForm, setCarrierForm] = useState(EMPTY_CARRIER); const [errors, setErrors] = useState({}); const [pendingDelete, setPendingDelete] = useState(null); const branchesQuery = useCompetitorBranches(page, PAGE_SIZE, { enabled: tab === 'branches' }); const carrierQuery = useCarrierPricing(page, PAGE_SIZE, { enabled: tab === 'pricing' }); const createBranch = useCreateCompetitorBranch(); const updateBranch = useUpdateCompetitorBranch(); const deleteBranch = useDeleteCompetitorBranch(); const createCarrier = useCreateCarrierPricing(); const updateCarrier = useUpdateCarrierPricing(); const deleteCarrier = useDeleteCarrierPricing(); const onBranches = tab === 'branches'; const response = onBranches ? branchesQuery.data : carrierQuery.data; const rows = response?.data || []; const total = Number(response?.total) || 0; const isLoading = onBranches ? branchesQuery.isLoading : carrierQuery.isLoading; const isFetching = onBranches ? branchesQuery.isFetching : carrierQuery.isFetching; const changeTab = (next) => { setTab(next); setPage(1); }; const setBranch = (key) => (value) => setBranchForm((current) => ({ ...current, [key]: value })); const setCarrier = (key) => (value) => setCarrierForm((current) => ({ ...current, [key]: value })); const openCreate = () => { setBranchForm(EMPTY_BRANCH); setCarrierForm(EMPTY_CARRIER); setErrors({}); setDialogOpen(true); }; const openEdit = (row) => { if (onBranches) setBranchForm({ ...EMPTY_BRANCH, ...row }); else setCarrierForm({ ...EMPTY_CARRIER, ...row }); setErrors({}); setDialogOpen(true); }; const save = () => { const close = { onSuccess: (res) => res?.success !== false && setDialogOpen(false) }; if (onBranches) { if (!branchForm.company.trim()) { setErrors({ company: 'Enter the company name' }); return; } setErrors({}); const { id, ...data } = branchForm; if (id) updateBranch.mutate({ id, data }, close); else createBranch.mutate(data, close); return; } const nextErrors = {}; if (!carrierForm.company.trim()) nextErrors.company = 'Enter the company name'; if (!carrierForm.weight_slab.trim()) nextErrors.weight_slab = 'Enter the weight slab'; setErrors(nextErrors); if (Object.keys(nextErrors).length) return; const { id, ...data } = carrierForm; if (id) updateCarrier.mutate({ id, data }, close); else createCarrier.mutate(data, close); }; const rowActions = (row) => (
openEdit(row)} /> setPendingDelete(row)} />
); const branchColumns = [ { key: 'company', header: 'Company', sortable: true, cell: (row) => (

{orDash(row.company)}

{orDash(row.area)}

), }, { key: 'phone', header: 'Phone', accessor: (row) => orDash(row.phone), hideBelow: 'md' }, { key: 'address', header: 'Address', cell: (row) => (

{orDash(row.address)}

), hideBelow: 'lg', }, { key: 'pincodes', header: 'Pincodes', accessor: (row) => orDash(row.pincodes), hideBelow: 'lg' }, { key: 'rate_per_kg', header: 'Rate / kg', sortable: true, accessor: (row) => orDash(row.rate_per_kg), align: 'right' }, { key: 'actions', header: 'Actions', align: 'right', cell: rowActions }, ]; const carrierColumns = [ { key: 'company', header: 'Company', sortable: true, accessor: (row) => orDash(row.company) }, { key: 'weight_slab', header: 'Weight slab', sortable: true, accessor: (row) => orDash(row.weight_slab) }, { key: 'service_type', header: 'Service', accessor: (row) => orDash(row.service_type), hideBelow: 'md' }, { key: 'zone', header: 'Zone', sortable: true, accessor: (row) => orDash(row.zone) }, { key: 'rate', header: 'Rate', accessor: (row) => orDash(row.rate), align: 'right' }, { key: 'delivery_time', header: 'Delivery time', accessor: (row) => orDash(row.delivery_time), hideBelow: 'lg' }, { key: 'actions', header: 'Actions', align: 'right', cell: rowActions }, ]; const busy = createBranch.isPending || updateBranch.isPending || createCarrier.isPending || updateCarrier.isPending; return (

{total ? `${total} records · page ${page} of ${Math.max(1, Math.ceil(total / PAGE_SIZE))}` : ''}

row.id ?? index} loading={isLoading} refreshing={isFetching && !isLoading} emptyState={ } /> {/* Server-side: each page is its own request, so this is not a slice of an already-complete list. */} {total > PAGE_SIZE && ( )}
} > {onBranches ? (
setBranch('company')(e.target.value)} /> setBranch('area')(e.target.value)} /> setBranch('phone')(e.target.value)} /> setBranch('pincodes')(e.target.value)} /> setBranch('address')(e.target.value)} /> setBranch('rate_per_kg')(e.target.value)} /> setBranch('packing_charge')(e.target.value)} /> setBranch('offers_pickup')(e.target.value)} /> setBranch('offers_drop')(e.target.value)} /> setBranch('time_in_days')(e.target.value)} />
) : (
setCarrier('company')(e.target.value)} /> setCarrier('weight_slab')(e.target.value)} /> setCarrier('service_type')(e.target.value)} /> setCarrier('zone')(e.target.value)} /> setCarrier('rate')(e.target.value)} /> setCarrier('delivery_time')(e.target.value)} />
)}
!open && setPendingDelete(null)} title="Delete this record?" description={`${pendingDelete?.company || 'This record'} will be removed from the competitive register.`} confirmLabel="Delete" busy={deleteBranch.isPending || deleteCarrier.isPending} onConfirm={() => { const mutation = onBranches ? deleteBranch : deleteCarrier; mutation.mutate(pendingDelete.id, { onSettled: () => setPendingDelete(null) }); }} />
); }