import { useState, useEffect } from 'react'; import { fetchUsers, deleteCompetitorBranch } from '@/utils/apiClient'; import { Search, Edit, Trash2, Phone, MapPin, FileText, ChevronDown, ChevronUp, Store, Truck, Map, Plus, X, CheckCircle2, AlertCircle } from 'lucide-react'; import { Button } from '@astryxdesign/core/Button'; import { Card } from '@astryxdesign/core/Card'; import { Dialog, DialogHeader } from '@astryxdesign/core/Dialog'; import { Layout, LayoutContent, LayoutFooter } from '@astryxdesign/core/Layout'; import { FormLayout } from '@astryxdesign/core/FormLayout'; import { TextInput } from '@astryxdesign/core/TextInput'; import { TextArea } from '@astryxdesign/core/TextArea'; import { Selector } from '@astryxdesign/core/Selector'; import { AlertDialog } from '@astryxdesign/core/AlertDialog'; import { Text, Heading } from '@astryxdesign/core/Text'; import { Badge } from '@astryxdesign/core/Badge'; import { IconButton } from '@astryxdesign/core/IconButton'; import { Banner } from '@astryxdesign/core/Banner'; import PageHeader from '@/components/PageHeader'; import StatCard from '@/components/StatCard'; import EmptyState from '@/components/EmptyState'; function isQuoted(row) { return !!(row.rate_per_kg && !String(row.rate_per_kg).toLowerCase().includes('not answered')); } const API_BASE = import.meta.env.VITE_API_BASE || 'https://api.doormile.com/api/v1'; const getHeaders = () => { const token = localStorage.getItem('auth_token'); return { 'Content-Type': 'application/json', ...(token ? { 'Authorization': `Bearer ${token}` } : {}) }; }; function Pill({ label, color = 'neutral', truncate = false }) { if (!label) return ; const badgeStyles = { success: { bg: '#E3F6EC', fg: '#00773B' }, warning: { bg: '#FFF7E0', fg: '#8A6500' }, info: { bg: '#E0F7F8', fg: '#00727B' }, error: { bg: '#FEEAE9', fg: '#A82216' }, primary: { bg: '#F8E0E3', fg: '#9E0E20' }, neutral: { bg: '#f1f5f9', fg: '#475569' } }; const style = badgeStyles[color] || badgeStyles.neutral; return ( {label} ); } function Field({ label, children }) { return (
{label}
{children}
); } function SectionCard({ icon: Icon, title, children }) { return (
{title}
{children}
); } async function apiFetchSurveys() { const res = await fetch(`${API_BASE}/admin/competitor-branches?limit=1000`, { headers: getHeaders() }); if (!res.ok) throw new Error('Failed to fetch surveys'); const json = await res.json(); const data = Array.isArray(json) ? json : (json.data || []); return { data, total: data.length }; } async function apiSaveSurvey(data) { const isUpdate = !!data.id; const url = isUpdate ? `${API_BASE}/admin/competitor-branches/${data.id}` : `${API_BASE}/admin/competitor-branches`; const method = isUpdate ? 'PUT' : 'POST'; const res = await fetch(url, { method, headers: getHeaders(), body: JSON.stringify(data) }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || err.message || 'Failed to save survey'); } return res.status === 204 ? {} : res.json().catch(() => ({})); } async function apiSavePricing(data) { const url = `${API_BASE}/admin/carrier-pricing`; const method = 'POST'; const res = await fetch(url, { method, headers: getHeaders(), body: JSON.stringify({ company: data.company, weight_slab: data.weight_slab, zone: data.zone || '', service_type: data.service_type || '', rate: String(data.rate) }) }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error || err.message || 'Failed to save pricing'); } return res.status === 204 ? {} : res.json().catch(() => ({})); } const ZONES = ['Local / Same', 'Within Tamilnadu', 'Interstate India']; const WEIGHT_SLABS = ['< 500g', '500g - 1kg', '1kg - 2kg', '2kg - 5kg', '> 5kg']; function SurveyFormDialog({ open, onClose, onSave, initialData }) { const [formData, setFormData] = useState({}); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (open) { setFormData(initialData ? { ...initialData, pincodes: initialData.pincodes ? initialData.pincodes.split(',').map(s => s.trim()).filter(Boolean) : [] } : { company: '', area: '', phone: '', rate_per_kg: '', offers_pickup: 'no', offers_drop: 'no', packing_charge: '', time_in_days: '', plus_code: '', address: '', frequency: '', pincodes: [], slabs: [] }); setError(null); } }, [open, initialData]); const set = (field) => (val) => { setFormData(prev => ({ ...prev, [field]: val })); }; const handleSlabChange = (index, field) => (val) => { const newSlabs = [...(formData.slabs || [])]; newSlabs[index] = { ...newSlabs[index], [field]: val }; setFormData(prev => ({ ...prev, slabs: newSlabs })); }; const addSlab = () => { setFormData(prev => ({ ...prev, slabs: [...(prev.slabs || []), { weight_slab: '', zone: '', service_type: '', rate: '' }] })); }; const removeSlab = (index) => { const newSlabs = [...(formData.slabs || [])]; newSlabs.splice(index, 1); setFormData(prev => ({ ...prev, slabs: newSlabs })); }; const handlePincodeChange = (index) => (val) => { const newPincodes = [...(formData.pincodes || [])]; newPincodes[index] = val; setFormData(prev => ({ ...prev, pincodes: newPincodes })); }; const addPincode = () => { setFormData(prev => ({ ...prev, pincodes: [...(prev.pincodes || []), ''] })); }; const removePincode = (index) => { const newPincodes = [...(formData.pincodes || [])]; newPincodes.splice(index, 1); setFormData(prev => ({ ...prev, pincodes: newPincodes })); }; const handleSave = async () => { setSaving(true); setError(null); try { if (formData.pincodes && formData.pincodes.length > 0) { const invalidPincodes = formData.pincodes.filter(p => p.trim() && !/^\d{6}$/.test(p.trim())); if (invalidPincodes.length > 0) { throw new Error(`Invalid pincodes: ${invalidPincodes.join(', ')}. Must be 6 digits.`); } } const payload = { ...(formData.id ? { id: formData.id } : {}), company: formData.company || '', area: formData.area || '', phone: formData.phone || '', rate_per_kg: formData.rate_per_kg || '', offers_pickup: formData.offers_pickup || 'no', offers_drop: formData.offers_drop || 'no', packing_charge: formData.packing_charge || '', time_in_days: formData.time_in_days || '', plus_code: formData.plus_code || '', address: formData.address || '', frequency: formData.frequency || '', pincodes: formData.pincodes ? formData.pincodes.join(',') : '', }; await apiSaveSurvey(payload); if (formData.slabs && formData.slabs.length > 0 && formData.company) { try { await Promise.all(formData.slabs.map(slab => { if (slab.weight_slab && slab.rate) { return apiSavePricing({ company: formData.company, weight_slab: slab.weight_slab, zone: slab.zone || '', service_type: slab.service_type || '', rate: slab.rate }); } return Promise.resolve(); })); } catch (err) { console.error("Failed to save pricing slabs", err); } } onSave(); onClose(); } catch (err) { console.error(err); setError(err.message || 'Failed to save. Please try again.'); } finally { setSaving(false); } }; const YES_NO = [{ value: 'yes', label: 'Yes' }, { value: 'no', label: 'No' }]; const FREQUENCY_OPTIONS = ['Daily', 'Weekly', 'Bi-weekly', 'Monthly', 'On-demand']; const WEIGHT_SLAB_OPTIONS = WEIGHT_SLABS.map((s) => ({ value: s, label: s })); const ZONE_OPTIONS = ZONES.map((z) => ({ value: z, label: z })); return ( { if (!o) onClose(); }} width={600} maxHeight="90dvh" purpose="form"> { if (!o) onClose(); }} />} content={ {error && } set('frequency')(v ?? '')} />
SERVICEABLE PINCODES
{(formData.pincodes || []).map((pincode, index) => (
))}