stock request

This commit is contained in:
2026-07-04 17:46:30 +05:30
parent c6a3ea51da
commit 7cd0ae85ef
23 changed files with 1293 additions and 570 deletions

View File

@@ -17,12 +17,13 @@ import {
Plus,
Bike
} from 'lucide-react';
import { useFiestaAllTenants, useFiestaTenantLocations } from '../services/fiestaQueries';
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';
@@ -143,6 +144,50 @@ export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: Sett
// (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(() => {
@@ -210,11 +255,10 @@ export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: Sett
}, [rolesQ.data]);
return (
<div className="space-y-lg animate-in fade-in duration-300 relative font-sans text-slate-700">
<div className="space-y-3 font-sans text-slate-700">
{/* Header */}
<div className="pb-4 border-b border-slate-100">
<div className="mt-3">
<div>
<div>
{tenantsQ.isLoading ? (
<span className="inline-flex items-center gap-1.5 text-xs font-bold text-slate-400 uppercase tracking-wider">
<span className="w-2 h-2 rounded-full bg-slate-350 animate-pulse" /> Loading store profile
@@ -420,7 +464,7 @@ export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: Sett
<div className="space-y-4">
{/* Header: Outlet name & status */}
<div className="flex justify-between items-start gap-3">
<div className="flex 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>
@@ -432,30 +476,43 @@ export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: Sett
</p>
</div>
</div>
<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>
<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-2 gap-3 bg-slate-50/50 p-3.5 rounded-xl border border-slate-100/80">
<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">
<span className="text-[10px] text-slate-455 uppercase font-bold block">Delivery Speed</span>
<p className="font-bold text-slate-700 text-xs">
{loc.deliverymins ? `${loc.deliverymins} mins avg` : '—'}
</p>
</div>
<div className="space-y-1 col-span-2 border-t border-slate-100 pt-2 mt-1">
<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" />
@@ -483,6 +540,138 @@ export default function SettingsView({ tenantId = FIESTA_TENANT_ID, user }: Sett
</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>
);
}