Files
daily_merchant_web/src/components/SettingsView.tsx

661 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Building2,
Store,
Truck,
CreditCard,
SlidersHorizontal,
Users,
MapPin,
Phone,
Mail,
Plus,
Bike
} from 'lucide-react';
import { useFiestaAllTenants, useFiestaTenantLocations, useFiestaUpdateLocation, useFiestaDeleteLocation } from '../services/fiestaQueries';
import { useAppRoles } from '../services/queries';
import { FIESTA_TENANT_ID, str as fstr, num as fnum, roleName } from '../services/fiestaApi';
import UsersPanel from './UsersPanel';
import AwaitingApi from './AwaitingApi';
import AdminConsole from './AdminConsole';
import { ConfirmModal } from './ConfirmModal';
import type { AuthUser } from '../services/auth';
type TabKey = 'profile' | 'outlets' | 'users';
/** Locally-persisted merchant preferences (survive reload via localStorage). */
interface MerchantSettings {
// Business profile (seeded from live tenant data, then locally editable)
contactEmail: string;
contactPhone: string;
minOrderValue: number;
// Delivery
deliveryCharge: number;
prepMins: number;
deliveryWindowMins: number;
cancelWindowSecs: number;
autoAssignRider: boolean;
// Payment & tax
defaultTaxPercent: number;
codEnabled: boolean;
onlinePaymentEnabled: boolean;
// Preferences
defaultRegion: string;
defaultNewUserRole: number;
orderNotifications: boolean;
lowStockAlerts: boolean;
dailySummaryEmail: boolean;
syncInterval: number;
sandboxMode: boolean;
}
const DEFAULTS: MerchantSettings = {
contactEmail: '',
contactPhone: '',
minOrderValue: 0,
deliveryCharge: 30,
prepMins: 15,
deliveryWindowMins: 45,
cancelWindowSecs: 60,
autoAssignRider: true,
defaultTaxPercent: 5,
codEnabled: true,
onlinePaymentEnabled: true,
defaultRegion: 'Coimbatore',
defaultNewUserRole: 4,
orderNotifications: true,
lowStockAlerts: true,
dailySummaryEmail: false,
syncInterval: 5,
sandboxMode: false,
};
const formatFriendlyTime = (timeStr: string) => {
try {
if (timeStr.includes('T')) {
const parts = timeStr.split('T')[1].split(':');
let hour = parseInt(parts[0], 10);
const min = parts[1];
const ampm = hour >= 12 ? 'PM' : 'AM';
hour = hour % 12;
hour = hour ? hour : 12;
return `${hour}:${min} ${ampm}`;
}
if (timeStr.includes(':')) {
const parts = timeStr.split(':');
let hour = parseInt(parts[0], 10);
const min = parts[1].slice(0, 2);
const ampm = hour >= 12 ? 'PM' : 'AM';
hour = hour % 12;
hour = hour ? hour : 12;
return `${hour}:${min} ${ampm}`;
}
} catch {
// fallback
}
return timeStr;
};
/// ── Small presentational helpers ────────────────────────────────────────────
function Row({
title,
desc,
children,
}: {
title: string;
desc?: string;
children: React.ReactNode;
}) {
return (
<div className="flex justify-between items-center gap-5 py-5 px-4 bg-white hover:bg-slate-50/20 border-b border-slate-100/70 last:border-none transition-all duration-200">
<div className="min-w-0">
<h4 className="font-sans font-bold text-sm text-slate-800 leading-tight">{title}</h4>
{desc && <p className="text-slate-500 text-xs mt-1.5 font-medium leading-relaxed">{desc}</p>}
</div>
<div className="shrink-0 flex items-center">{children}</div>
</div>
);
}
export interface SettingsViewProps {
tenantId?: number;
user?: AuthUser;
}
export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: SettingsViewProps) {
const [activeTab, setActiveTab] = useState<TabKey>('profile');
// Live tenant profile + outlets.
// Fetch a larger page size to ensure we find our specific tenant (ID 1087).
const tenantsQ = useFiestaAllTenants({ pagesize: 5000, status: '' });
const tenant = (tenantsQ.data ?? []).find((t) => Number(t.tenantid) === tenantId) || null;
const locationsQ = useFiestaTenantLocations(tenantId);
const outlets = locationsQ.data ?? [];
// Application roles (Hasura) — drives the role dropdowns.
const rolesQ = useAppRoles();
// In-session workspace preferences. These have NO merchant-settings backend
// (see [R6]) so they are not persisted; the operational controls that would
// need persistence show an AwaitingApi notice instead of saving silently.
const [form, setForm] = useState<MerchantSettings>({ ...DEFAULTS });
const updateLocationMut = useFiestaUpdateLocation();
const deleteLocationMut = useFiestaDeleteLocation();
const [editingStore, setEditingStore] = useState<any>(null);
const [showEditStoreModal, setShowEditStoreModal] = useState(false);
const [confirmModalState, setConfirmModalState] = useState<{isOpen: boolean, loc: any}>({ isOpen: false, loc: null });
const handleUpdateStoreSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!editingStore) return;
try {
await updateLocationMut.mutateAsync({
tenantid: Number(tenantId),
locationid: Number(editingStore.locationid),
locationname: editingStore.locationname,
suburb: editingStore.suburb,
city: editingStore.city,
contactno: editingStore.contactno,
status: editingStore.status,
});
setShowEditStoreModal(false);
setEditingStore(null);
alert('Outlet updated successfully!');
} catch (err: any) {
alert(err.message || 'Failed to update outlet.');
}
};
const handleDeleteStore = async (loc: any) => {
setConfirmModalState({ isOpen: true, loc });
};
const confirmDeleteOutlet = async () => {
const loc = confirmModalState.loc;
if (!loc) return;
try {
await deleteLocationMut.mutateAsync({ tenantid: tenantId, locationid: loc.locationid });
setConfirmModalState({ isOpen: false, loc: null });
} catch (err: any) {
alert(err.message || 'Failed to delete outlet.');
setConfirmModalState({ isOpen: false, loc: null });
}
};
const [showStoreOnboarding, setShowStoreOnboarding] = useState(false);
useEffect(() => {
if (activeTab !== 'outlets') {
setShowStoreOnboarding(false);
}
}, [activeTab]);
// First-run seeding: fill region/role defaults from the live tenant once it
// arrives (used at runtime by the Add User dialog / region label).
const seededRef = useRef(false);
useEffect(() => {
if (seededRef.current || !tenant) return;
seededRef.current = true;
setForm((prev) => ({
...prev,
contactEmail: prev.contactEmail || fstr(tenant.primaryemail),
contactPhone: prev.contactPhone || fstr(tenant.primarycontact),
minOrderValue: prev.minOrderValue || fnum(tenant.minorder),
defaultRegion: prev.defaultRegion || fstr(tenant.city) || 'Coimbatore',
}));
}, [tenant]);
// Live outlets only — no fabricated fallback. Render whatever the API returns.
const cleanOutlets = useMemo(() => {
return outlets.map((loc, idx) => ({
locationid: fstr(loc.locationid) || String(idx),
locationname: fstr(loc.locationname) || '—',
suburb: fstr(loc.suburb),
city: fstr(loc.city),
postcode: fstr(loc.postcode),
status: fstr(loc.status) || '—',
opentime: fstr(loc.opentime),
closetime: fstr(loc.closetime),
deliverymins: fnum(loc.deliverymins),
deliveryradius: fnum(loc.deliveryradius),
}));
}, [outlets]);
const set = <K extends keyof MerchantSettings>(key: K, value: MerchantSettings[K]) =>
setForm((f) => ({ ...f, [key]: value }));
const tabs: Array<{ key: TabKey; label: string; icon: typeof Building2 }> = [
{ key: 'profile', label: 'Business Profile', icon: Building2 },
{ key: 'outlets', label: 'Outlets', icon: Store },
{ key: 'users', label: 'Users & Access', icon: Users },
];
// Build role options from the live app-roles API; fall back to the known
// numeric roles + roleName() helper when the API has no rows/names.
const roleOptions = useMemo<Array<{ id: number; name: string }>>(() => {
const rows = rolesQ.data ?? [];
const mapped = rows
.map((r) => {
const id = fnum((r as Record<string, unknown>).roleid);
const name =
fstr((r as Record<string, unknown>).rolename) ||
fstr((r as Record<string, unknown>).name) ||
roleName(id);
return { id, name };
})
.filter((r) => r.id > 0);
if (mapped.length) return mapped;
return [1, 2, 3, 4, 6].map((id) => ({ id, name: roleName(id) }));
}, [rolesQ.data]);
return (
<div className="font-sans text-slate-700">
{/* Header Removed */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-gutter items-start">
{/* Tab rail & Merchant Card */}
<div className="lg:col-span-1 space-y-md bg-slate-50/50 border border-slate-200/60 p-4 shadow-sm">
{/* Merchant ID Card */}
<div className="bg-gradient-to-br from-slate-900 via-slate-950 to-purple-955 border border-purple-500/20 p-5 rounded-2xl text-white shadow-md relative overflow-hidden select-none">
{/* Background design accents */}
<div className="absolute top-0 right-0 w-28 h-28 bg-purple-500/10 rounded-full blur-xl -mr-8 -mt-8 pointer-events-none" />
<div className="relative z-10 flex items-center gap-3.5">
{/* Initials avatar badge with glowing ring */}
<div className="w-12 h-12 rounded-full bg-gradient-to-tr from-purple-500 to-indigo-650 border border-purple-400/30 flex items-center justify-center font-black text-sm shadow-[0_0_15px_rgba(168,85,247,0.35)] shrink-0">
{user?.name ? user.name.substring(0, 2).toUpperCase() : tenant ? fstr(tenant.tenantname).substring(0, 2).toUpperCase() : 'ND'}
</div>
<div className="min-w-0">
<h4 className="font-sans font-bold text-sm truncate text-white">{user?.name || (tenant ? fstr(tenant.tenantname) : 'Nearle Merchant')}</h4>
<p className="text-slate-400 text-[10px] font-mono mt-1 truncate uppercase tracking-wider">Store ID: #{tenantId}</p>
</div>
</div>
<div className="mt-5 pt-4 border-t border-slate-800/80 flex justify-between items-center text-xs">
<span className="text-slate-400 font-medium">Status</span>
<span className="inline-flex items-center gap-1.5 font-bold text-emerald-455">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
Online & Synced
</span>
</div>
</div>
{/* Navigation tab rail */}
<nav className="flex lg:flex-col gap-1.5 overflow-x-auto custom-scrollbar no-scrollbar touch-scrolling select-none">
{tabs.map((t) => {
const Icon = t.icon;
const active = activeTab === t.key;
return (
<button
key={t.key}
onClick={() => setActiveTab(t.key)}
className={`flex items-center gap-3 px-4 py-3 rounded-xl text-sm font-bold transition-all duration-200 whitespace-nowrap cursor-pointer border-none ${
active
? 'bg-purple-50 text-[#662582] shadow-sm border-l-2 border-purple-650'
: 'text-slate-600 hover:text-slate-900 hover:bg-white bg-transparent'
}`}
>
<Icon size={16} className={active ? 'text-[#662582]' : 'text-slate-450'} />
<span>{t.label}</span>
</button>
);
})}
</nav>
</div>
{/* Panel */}
<div className="lg:col-span-3 space-y-gutter text-sm pb-24">
{activeTab === 'profile' && (
<div className="bg-white border border-slate-200/60 p-6 shadow-sm space-y-lg animate-in fade-in duration-200">
<div>
<span className="text-xs font-bold text-slate-450 uppercase tracking-widest block">Store Profile</span>
<h2 className="text-xl font-bold text-slate-900 mt-1">Identity & Contacts</h2>
</div>
{/* Identity Row */}
<div className="flex flex-col pb-6 border-b border-slate-100">
<div className="min-w-0 flex-1 leading-relaxed">
<span className="text-base font-bold text-slate-900 mr-2">{user?.name || fstr(tenant?.tenantname) || 'Nearle Store'}</span>
<span className="text-slate-500 text-xs font-medium">
View your customer service email and contact number. Official registration details are synced with your primary credentials.
</span>
</div>
</div>
{/* Live identity (read-only) */}
<div className="space-y-sm bg-slate-50/50 p-5 rounded-2xl border border-slate-100/80">
<span className="text-xs font-bold text-slate-500 uppercase tracking-wider block">Official Registration Info</span>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-md mt-3">
<div className="p-4 bg-white rounded-xl border border-slate-200/60 shadow-sm">
<span className="text-[10px] text-slate-400 uppercase font-black tracking-wider block">Company Name</span>
<p className="font-bold text-slate-800 text-sm mt-1">{fstr(tenant?.companyname) || user?.name || 'Nearle Merchant'}</p>
</div>
<div className="p-4 bg-white rounded-xl border border-slate-200/60 shadow-sm">
<span className="text-[10px] text-slate-400 uppercase font-black tracking-wider block">Category</span>
<p className="font-bold text-slate-800 text-sm mt-1">{fstr(tenant?.subcategoryname) || (tenant?.categoryid ? `Category ${fnum(tenant.categoryid)}` : 'General Retail')}</p>
</div>
<div className="p-4 bg-white rounded-xl border border-slate-200/60 shadow-sm">
<span className="text-[10px] text-slate-400 uppercase font-black tracking-wider block">Registration Status</span>
<p className="font-bold text-slate-800 text-sm mt-1 flex items-center gap-1.5">
<span className={`w-2 h-2 rounded-full ${
(fstr(tenant?.status).toLowerCase() === 'active' || user) ? 'bg-emerald-500' : 'bg-slate-400'
}`} />
{fstr(tenant?.status) || (user ? 'Active' : '—')}
</p>
</div>
<div className="p-4 bg-white rounded-xl border border-slate-200/60 shadow-sm flex items-center justify-between">
<div>
<span className="text-[10px] text-slate-400 uppercase font-black tracking-wider block">Store Verification</span>
<p className="font-bold text-slate-800 text-sm mt-1">{fnum(tenant?.approved) === 1 || user ? 'Verified' : 'Pending'}</p>
</div>
<span className={`px-2.5 py-1 rounded-lg text-[9px] font-black uppercase tracking-wider ${
fnum(tenant?.approved) === 1 || user
? 'text-emerald-700 bg-emerald-50 border border-emerald-100/50'
: 'text-zinc-555 bg-zinc-100'
}`}>
{fnum(tenant?.approved) === 1 || user ? 'Verified' : 'Pending'}
</span>
</div>
</div>
<div className="p-4 bg-white rounded-xl border border-slate-200/60 shadow-sm flex items-start gap-3 mt-4">
<MapPin size={18} className="text-slate-400 shrink-0 mt-0.5" />
<div>
<span className="text-[10px] text-slate-400 uppercase font-black tracking-wider block">Registered Address</span>
<p className="text-slate-700 font-semibold text-xs mt-1 leading-relaxed">
{fstr(tenant?.address) || [user?.locationname, user?.applocation].filter(Boolean).join(', ') || 'Headquarters'}
{tenant?.city ? ` · ${fstr(tenant.city)}, ${fstr(tenant.state)} ${fstr(tenant.postcode)}` : ''}
</p>
</div>
</div>
</div>
{/* Customer support contacts — live (read-only) tenant values. */}
<div className="space-y-sm">
<span className="text-xs font-bold text-slate-500 uppercase tracking-wider block">Customer Support & Contacts</span>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-md mt-2">
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px] flex items-center gap-1.5">
<Mail size={12} className="text-slate-400" /> Support Email
</label>
<input
type="email"
value={user?.email || fstr(tenant?.primaryemail) || ''}
readOnly
className="w-full border border-slate-200 rounded-xl py-3 px-4 bg-slate-50/40 outline-none transition-all text-slate-800 font-semibold text-sm shadow-sm"
placeholder="—"
/>
</div>
<div className="space-y-1.5">
<label className="font-bold text-slate-500 uppercase tracking-widest text-[10px] flex items-center gap-1.5">
<Phone size={12} className="text-slate-400" /> Phone Number
</label>
<input
type="text"
value={user?.contactno || fstr(tenant?.primarycontact) || ''}
readOnly
className="w-full border border-slate-200 rounded-xl py-3 px-4 bg-slate-50/40 outline-none transition-all text-slate-800 font-semibold text-sm shadow-sm"
placeholder="—"
/>
</div>
</div>
</div>
</div>
)}
{activeTab === 'outlets' && (
<div className="bg-white border border-slate-200/60 p-6 shadow-sm space-y-md animate-in fade-in duration-200">
<div className="flex justify-between items-center pb-4 border-b border-slate-100">
<div>
<span className="text-xs font-bold text-slate-450 uppercase tracking-widest block">
{showStoreOnboarding ? 'Onboarding' : 'Our Stores'}
</span>
<h2 className="text-xl font-bold text-slate-900 mt-1">
{showStoreOnboarding ? 'Add Store Outlet Location' : 'Store Directory'}
</h2>
</div>
<button
type="button"
onClick={() => setShowStoreOnboarding(!showStoreOnboarding)}
className="bg-[#662582] hover:bg-purple-800 text-white px-4 py-2.5 rounded-xl text-xs font-bold uppercase tracking-wider flex items-center gap-1.5 cursor-pointer shadow-sm active:scale-95 transition-all border-none"
>
{showStoreOnboarding ? 'View Store Directory' : '+ Add Store Branch'}
</button>
</div>
{showStoreOnboarding ? (
<div className="pt-2">
<AdminConsole activeTab="store" showHeader={false} onBack={() => setShowStoreOnboarding(false)} tenantId={tenantId} />
</div>
) : (
<>
{locationsQ.isLoading ? (
<div className="text-center py-lg text-slate-400 font-medium text-sm">Loading live outlets</div>
) : cleanOutlets.length === 0 ? (
<div className="text-center py-lg text-slate-400 font-medium text-sm">No outlets configured yet.</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-md max-h-[38rem] overflow-y-auto pr-1 scrollbar-thin">
{cleanOutlets.map((loc, i) => (
<div key={loc.locationid || i} className="p-5 border border-slate-200/60 rounded-2xl bg-white hover:border-purple-300 hover:shadow-md transition-all duration-350 flex flex-col justify-between group">
<div className="space-y-4">
{/* Header: Outlet name & status */}
<div className="flex justify-between items-start gap-3">
<div className="flex gap-3 flex-1 min-w-0">
<div className="p-3 rounded-xl bg-purple-50 text-purple-650 shrink-0 self-start group-hover:bg-purple-100 transition-colors">
<Store size={20} />
</div>
<div className="min-w-0">
<p className="font-bold text-slate-800 text-sm truncate leading-tight group-hover:text-purple-950 transition-colors">{loc.locationname}</p>
<p className="text-xs text-slate-450 mt-1.5 flex items-center gap-1">
<MapPin size={12} className="shrink-0 text-slate-400" />
<span className="truncate">{[loc.suburb, loc.city].filter(Boolean).join(', ') || '—'}</span>
</p>
</div>
</div>
<div className="flex items-center justify-end gap-2 flex-wrap shrink-0">
<span className={`shrink-0 px-2.5 py-1 rounded-full text-[9px] font-black uppercase tracking-wider ${
loc.status.toLowerCase() === 'active'
? 'text-emerald-700 bg-emerald-50 border border-emerald-100/50'
: 'text-zinc-555 bg-zinc-100'
}`}>
{loc.status || '—'}
</span>
<button
type="button"
onClick={() => {
setEditingStore({ ...loc });
setShowEditStoreModal(true);
}}
className="text-[10px] uppercase font-bold text-purple-600 hover:text-purple-800 hover:bg-purple-50 px-2 py-1 rounded border border-purple-200 transition-colors"
>
Edit
</button>
<button
type="button"
onClick={() => handleDeleteStore(loc)}
className="text-[10px] uppercase font-bold text-red-600 hover:text-red-800 hover:bg-red-50 px-2 py-1 rounded border border-red-200 transition-colors"
>
Delete
</button>
</div>
</div>
{/* Outlet Details Grid */}
<div className="grid grid-cols-1 gap-3 bg-slate-50/50 p-3.5 rounded-xl border border-slate-100/80">
<div className="space-y-1">
<span className="text-[10px] text-slate-455 uppercase font-bold block">Delivery Range</span>
<p className="font-bold text-slate-700 text-xs">
{loc.deliveryradius ? `Up to ${loc.deliveryradius / 1000} km` : '—'}
</p>
</div>
<div className="space-y-1 border-t border-slate-100 pt-2 mt-1">
<span className="text-[10px] text-slate-455 uppercase font-bold block">Opening Hours</span>
<p className="font-bold text-slate-750 text-xs flex items-center gap-1.5 mt-0.5">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
{loc.opentime && loc.closetime
? `Open: ${formatFriendlyTime(loc.opentime)} ${formatFriendlyTime(loc.closetime)}`
: 'Hours not set'}
</p>
</div>
</div>
</div>
</div>
))}
</div>
)}
</>
)}
</div>
)}
{activeTab === 'users' && (
<UsersPanel tenantId={tenantId} defaultNewUserRole={form.defaultNewUserRole} />
)}
</div>
</div>
{/* EDIT STORE MODAL */}
{showEditStoreModal && editingStore && (
<div
className="fixed inset-0 bg-[#0f172a]/40 backdrop-blur-sm z-[200] flex items-center justify-center p-4"
onClick={(e) => { if (e.target === e.currentTarget) setShowEditStoreModal(false); }}
>
<div className="bg-white border border-[#e2e8f0] rounded-xl w-[90vw] sm:w-[450px] max-h-[90vh] flex flex-col shadow-2xl overflow-hidden animate-in zoom-in-95 duration-200" onClick={(e) => e.stopPropagation()}>
<div className="p-4 border-b border-[#e2e8f0] bg-[#f8fafc] flex justify-between items-center shrink-0">
<h4 className="font-bold text-[#0f172a] text-sm flex items-center gap-2">
<Store size={16} className="text-[#662582]" />
Edit Store Outlet Location
</h4>
<button
type="button"
onClick={() => setShowEditStoreModal(false)}
className="text-zinc-400 hover:text-zinc-700 bg-zinc-100 hover:bg-zinc-200 p-1.5 rounded-lg transition-colors cursor-pointer border-none"
>
</button>
</div>
<form onSubmit={handleUpdateStoreSubmit} className="flex flex-col flex-1 min-h-0">
<div className="p-4 overflow-y-auto space-y-4">
<div className="bg-purple-50/50 border border-purple-100 rounded-lg p-3 flex items-start gap-3">
<Store className="text-purple-600 mt-0.5 shrink-0" size={16} />
<div>
<h5 className="font-bold text-[#0f172a] text-xs">Update Outlet Profile</h5>
<p className="text-zinc-500 text-[11px] leading-relaxed mt-0.5">
Adjust the operational metadata for this node. The outlet will instantly be updated across the delivery fleet system.
</p>
</div>
</div>
<div className="grid grid-cols-1 gap-4">
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">OUTLET NAME (*)</label>
<input
type="text"
placeholder="e.g. Coimbatore Main"
value={editingStore.locationname}
onChange={(e) => setEditingStore({ ...editingStore, locationname: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582] text-xs"
required
/>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">LOCAL ZONE AREA (*)</label>
<input
type="text"
placeholder="e.g. RS Puram"
value={editingStore.suburb}
onChange={(e) => setEditingStore({ ...editingStore, suburb: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582] text-xs"
required
/>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">CITY</label>
<input
type="text"
placeholder="e.g. Coimbatore"
value={editingStore.city}
onChange={(e) => setEditingStore({ ...editingStore, city: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582] text-xs"
/>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">CONTACT PHONE (*)</label>
<input
type="text"
placeholder="e.g. 9876543210"
value={editingStore.contactno}
onChange={(e) => setEditingStore({ ...editingStore, contactno: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582] text-xs"
required
/>
</div>
<div className="space-y-1">
<label className="font-bold text-zinc-500 uppercase tracking-widest text-[9px]">STATUS</label>
<select
value={editingStore.status}
onChange={(e) => setEditingStore({ ...editingStore, status: e.target.value })}
className="w-full border border-[#e2e8f0] rounded-lg p-sm bg-[#f8fafc] focus:bg-white outline-none focus:ring-1 focus:ring-[#662582] text-xs"
>
<option value="Active">Active</option>
<option value="Inactive">Inactive</option>
<option value="Suspended">Suspended</option>
</select>
</div>
</div>
</div>
<div className="p-4 border-t border-[#f1f5f9] flex justify-end gap-3 bg-[#f8fafc] shrink-0">
<button
type="button"
onClick={() => setShowEditStoreModal(false)}
className="px-4 py-2 border border-[#e2e8f0] rounded-lg font-semibold text-zinc-500 hover:bg-zinc-50 cursor-pointer text-xs"
>
Cancel
</button>
<button
type="submit"
disabled={updateLocationMut.isPending}
className="px-4 py-2 bg-[#662582] hover:bg-purple-800 text-white rounded-lg font-bold shadow-sm cursor-pointer border-none flex items-center gap-2 text-xs"
>
{updateLocationMut.isPending ? 'Updating...' : 'Update Outlet Profile'}
</button>
</div>
</form>
</div>
</div>
)}
<ConfirmModal
isOpen={confirmModalState.isOpen}
title="Delete Outlet"
message={
<>
Are you sure you want to delete <strong>{confirmModalState.loc?.locationname}</strong>? This action cannot be undone.
</>
}
confirmText="Delete Outlet"
onConfirm={confirmDeleteOutlet}
onCancel={() => setConfirmModalState({ isOpen: false, loc: null })}
isDestructive={true}
isLoading={deleteLocationMut.isPending}
/>
</div>
);
}