Compare commits
2 Commits
02b5258000
...
8885c55817
| Author | SHA1 | Date | |
|---|---|---|---|
| 8885c55817 | |||
| 6a6221f9f3 |
@@ -32,6 +32,7 @@ import {
|
||||
} from '../services/fiestaQueries';
|
||||
import { FIESTA_TENANT_ID, str as fstr, num as fnum } from '../services/fiestaApi';
|
||||
import StoreQRView from './StoreQRView';
|
||||
import AddressAutocomplete, { type AddressResult } from './AddressAutocomplete';
|
||||
|
||||
export default function AdminConsole({ activeTab: propActiveTab, showHeader = true, onBack, tenantId }: { activeTab?: 'tenant' | 'store' | 'rider', showHeader?: boolean, onBack?: () => void, tenantId?: number }) {
|
||||
const [activeTab, setActiveTab] = useState<'tenant' | 'store' | 'rider'>(propActiveTab || 'tenant');
|
||||
@@ -62,6 +63,10 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
||||
city: 'Coimbatore',
|
||||
state: 'Tamil Nadu',
|
||||
postcode: '',
|
||||
// Populated from AddressAutocomplete's geocoding lookup, not typed —
|
||||
// keeps HQ address and coordinates from drifting apart.
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
// Primary outlet — created in the same call as the tenant, so a fresh
|
||||
// tenant is never left without a location to actually operate from.
|
||||
locationname: '',
|
||||
@@ -84,6 +89,10 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
||||
city: 'Coimbatore',
|
||||
state: 'Tamil Nadu',
|
||||
postcode: '',
|
||||
// Populated from AddressAutocomplete's geocoding lookup, not typed —
|
||||
// keeps the branch address and coordinates from drifting apart.
|
||||
latitude: '',
|
||||
longitude: '',
|
||||
contactno: '',
|
||||
email: '',
|
||||
opentime: '06:00:00',
|
||||
@@ -128,6 +137,34 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
||||
setTimeout(() => setCopiedSql(false), 2000);
|
||||
};
|
||||
|
||||
// Address autocomplete → discrete fields + geocoded lat/long (or clear them
|
||||
// when the field is emptied), same pattern UsersPanel uses for team members.
|
||||
const handleTenantAddressSelect = (r: AddressResult | null) => {
|
||||
setTenantForm((f) => ({
|
||||
...f,
|
||||
address: r?.address ?? '',
|
||||
suburb: r?.suburb ?? '',
|
||||
city: r?.city ?? '',
|
||||
state: r?.state ?? '',
|
||||
postcode: r?.postcode ?? '',
|
||||
latitude: r?.latitude ?? '',
|
||||
longitude: r?.longitude ?? '',
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStoreAddressSelect = (r: AddressResult | null) => {
|
||||
setStoreForm((f) => ({
|
||||
...f,
|
||||
address: r?.address ?? '',
|
||||
suburb: r?.suburb ?? '',
|
||||
city: r?.city ?? '',
|
||||
state: r?.state ?? '',
|
||||
postcode: r?.postcode ?? '',
|
||||
latitude: r?.latitude ?? '',
|
||||
longitude: r?.longitude ?? '',
|
||||
}));
|
||||
};
|
||||
|
||||
// ----------------------------------------------------
|
||||
// Submissions
|
||||
// ----------------------------------------------------
|
||||
@@ -154,6 +191,8 @@ export default function AdminConsole({ activeTab: propActiveTab, showHeader = tr
|
||||
city: tenantForm.city,
|
||||
state: tenantForm.state,
|
||||
postcode: tenantForm.postcode,
|
||||
latitude: tenantForm.latitude,
|
||||
longitude: tenantForm.longitude,
|
||||
applocationid: tenantForm.applocationid,
|
||||
},
|
||||
});
|
||||
@@ -353,13 +392,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. 12, Avinashi Road"
|
||||
value={tenantForm.address}
|
||||
onChange={(e) => setTenantForm({ ...tenantForm, address: e.target.value })}
|
||||
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
|
||||
/>
|
||||
<AddressAutocomplete value={tenantForm.address} onSelect={handleTenantAddressSelect} placeholder="e.g. 12, Avinashi Road" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
@@ -471,7 +504,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
|
||||
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', latitude: '', longitude: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
|
||||
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer active:scale-95 transition-all shadow-sm"
|
||||
>
|
||||
Add Another Branch
|
||||
@@ -561,18 +594,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
</h4>
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-500 uppercase tracking-widest block mb-1">Store Branch Address</label>
|
||||
<div className="relative rounded-xl">
|
||||
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-slate-400">
|
||||
<MapPin size={14} />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. 240, DB Road"
|
||||
value={storeForm.address}
|
||||
onChange={(e) => setStoreForm({ ...storeForm, address: e.target.value })}
|
||||
className="pl-10 pr-4 py-2.5 w-full border border-slate-200 rounded-xl bg-slate-50/40 hover:bg-slate-100/60 focus:bg-white outline-none focus:ring-4 focus:ring-purple-100 focus:border-purple-600 transition-all font-semibold text-xs text-slate-800 shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
<AddressAutocomplete value={storeForm.address} onSelect={handleStoreAddressSelect} placeholder="e.g. 240, DB Road" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-4">
|
||||
@@ -1107,13 +1129,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">HQ Street Address</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. 12, Avinashi Road"
|
||||
value={tenantForm.address}
|
||||
onChange={(e) => setTenantForm({ ...tenantForm, address: e.target.value })}
|
||||
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
|
||||
/>
|
||||
<AddressAutocomplete value={tenantForm.address} onSelect={handleTenantAddressSelect} placeholder="e.g. 12, Avinashi Road" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
@@ -1218,7 +1234,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
|
||||
onClick={() => { setStoreSuccess(null); setStoreForm({ tenantid: FIESTA_TENANT_ID, locationname: '', address: '', suburb: '', city: 'Coimbatore', state: 'Tamil Nadu', postcode: '', latitude: '', longitude: '', contactno: '', email: '', opentime: '06:00:00', closetime: '22:00:00', deliverymins: 45, deliveryradius: 5000 }); }}
|
||||
className="bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs uppercase tracking-wider px-5 py-2.5 rounded-lg border-none cursor-pointer"
|
||||
>
|
||||
Add Another Branch
|
||||
@@ -1281,13 +1297,7 @@ VALUES (${newUserId}, 1, 'Active', NOW());
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-[10px] font-bold text-slate-450 uppercase tracking-widest">Store Branch Address</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="e.g. 240, DB Road"
|
||||
value={storeForm.address}
|
||||
onChange={(e) => setStoreForm({ ...storeForm, address: e.target.value })}
|
||||
className="w-full border border-slate-250 rounded-xl p-3 bg-slate-50/40 hover:bg-slate-100 focus:bg-white outline-none focus:border-purple-500 transition-all font-semibold text-xs text-slate-800"
|
||||
/>
|
||||
<AddressAutocomplete value={storeForm.address} onSelect={handleStoreAddressSelect} placeholder="e.g. 240, DB Road" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
|
||||
@@ -132,60 +132,7 @@ export default function DashboardView({ searchQuery, tenantId = FIESTA_TENANT_ID
|
||||
|
||||
return (
|
||||
<div className="space-y-lg animate-in fade-in duration-500 relative">
|
||||
{/* ── Immersive Executive Banner (cover image + slate→purple gradient overlay) ── */}
|
||||
<div className="relative p-6 md:p-8 text-white shadow-xl border border-purple-500/20 overflow-hidden animate-in fade-in duration-300">
|
||||
{/* Cover image background & decorative glow */}
|
||||
<div className="absolute inset-0 z-0 overflow-hidden">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1551288049-bebda4e38f71?auto=format&fit=crop&w=1400&q=80"
|
||||
alt="Executive operations dashboard"
|
||||
className="w-full h-full object-cover object-center opacity-40"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-slate-950 via-slate-900/90 to-purple-950/80" />
|
||||
<div className="absolute top-0 right-0 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl -mr-20 -mt-20 pointer-events-none" />
|
||||
<div className="absolute bottom-0 left-0 w-56 h-56 bg-indigo-500/10 rounded-full blur-2xl -ml-20 -mb-20 pointer-events-none" />
|
||||
</div>
|
||||
|
||||
{/* Content row */}
|
||||
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-lg">
|
||||
<div>
|
||||
<h1 className="font-sans font-bold text-2xl md:text-3xl tracking-tight text-white flex items-center gap-2.5">
|
||||
Executive Command Center
|
||||
<span className="text-[10px] text-purple-200 font-bold bg-purple-900/60 border border-purple-500/30 px-2 py-0.5 rounded-full uppercase tracking-wider animate-pulse">
|
||||
Live Core
|
||||
</span>
|
||||
</h1>
|
||||
<p className="text-slate-300 font-sans text-sm mt-2 leading-relaxed">
|
||||
Month-to-date order operations for <strong className="text-white font-semibold">{tenantName}</strong>, pulled live from the API.
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{loading ? (
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-slate-300 uppercase tracking-wide">
|
||||
<span className="w-2 h-2 rounded-full bg-slate-400 animate-pulse" /> Syncing live data…
|
||||
</span>
|
||||
) : errored ? (
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-rose-300 uppercase tracking-wide" title="Restart the dev server so the /hasura proxy is active.">
|
||||
<span className="w-2 h-2 rounded-full bg-rose-400" /> Live data unavailable
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center gap-1.5 text-[11px] font-bold text-emerald-300 uppercase tracking-wide">
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" /> Live · {tenantName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Reporting scope panel */}
|
||||
<div className="flex flex-col items-start md:items-end gap-2 shrink-0">
|
||||
<div className="inline-flex items-center gap-2 bg-white/10 backdrop-blur-md border border-white/15 rounded-xl px-3.5 py-2.5 shadow-sm">
|
||||
<Clock size={14} className="text-purple-300" />
|
||||
<span className="text-xs font-bold font-mono text-white tracking-tight">{fromdate} → {todate}</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Month-to-date reporting scope</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Error hint */}
|
||||
{errored && (
|
||||
@@ -202,32 +149,36 @@ export default function DashboardView({ searchQuery, tenantId = FIESTA_TENANT_ID
|
||||
)}
|
||||
|
||||
{/* KPI cards — all live from getordersummary / getinvoiceinsight */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-gutter">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
|
||||
{kpis.map((kpi) => {
|
||||
const Icon = kpi.icon;
|
||||
return (
|
||||
<div
|
||||
key={kpi.title}
|
||||
className="group relative flex flex-col overflow-hidden bg-white border border-slate-200/70 rounded-2xl p-5 shadow-[0_1px_2px_rgba(16,24,40,0.04)] transition-all duration-300 hover:-translate-y-1 hover:border-purple-200 hover:shadow-[0_16px_36px_rgba(16,24,40,0.10)]"
|
||||
className="group relative flex items-center gap-3 overflow-hidden bg-white border border-slate-200/70 rounded-xl p-3 shadow-[0_1px_2px_rgba(16,24,40,0.04)] transition-all duration-300 hover:-translate-y-1 hover:border-purple-200 hover:shadow-[0_16px_36px_rgba(16,24,40,0.10)]"
|
||||
>
|
||||
{/* Gradient accent bar */}
|
||||
<span className={`absolute inset-x-0 top-0 h-1 bg-gradient-to-r ${kpi.bar}`} />
|
||||
<div className="flex items-start justify-between">
|
||||
<div className={`h-11 w-11 rounded-xl flex items-center justify-center ring-1 group-hover:scale-110 transition-transform duration-300 ${kpi.chip}`}>
|
||||
<Icon size={19} />
|
||||
<span className={`absolute inset-y-0 left-0 w-1 bg-gradient-to-b ${kpi.bar}`} />
|
||||
|
||||
<div className={`h-10 w-10 shrink-0 rounded-lg flex items-center justify-center ring-1 group-hover:scale-110 transition-transform duration-300 ml-1 ${kpi.chip}`}>
|
||||
<Icon size={18} />
|
||||
</div>
|
||||
<ArrowUpRight size={16} className="text-slate-300 group-hover:text-purple-400 transition-colors" />
|
||||
</div>
|
||||
<p className="text-[10px] font-bold text-slate-400 tracking-widest uppercase font-sans mt-4">
|
||||
|
||||
<div className="flex-1 min-w-0 flex flex-col justify-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-[9px] font-bold text-slate-400 tracking-widest uppercase font-sans truncate pr-2">
|
||||
{kpi.title}
|
||||
</p>
|
||||
<p className="font-sans font-extrabold text-[28px] leading-tight text-slate-900 tracking-tight mt-1">
|
||||
<ArrowUpRight size={12} className="text-slate-300 group-hover:text-purple-400 transition-colors shrink-0" />
|
||||
</div>
|
||||
<p className="font-sans font-extrabold text-lg leading-tight text-slate-900 tracking-tight mt-0.5 truncate">
|
||||
{kpi.loading ? <span className="text-slate-300">…</span> : kpi.display}
|
||||
</p>
|
||||
<p className="text-[11px] text-slate-400 font-medium mt-1.5 leading-snug">
|
||||
<p className="text-[9px] text-slate-400 font-medium mt-0.5 leading-snug truncate">
|
||||
{kpi.sub}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -1037,6 +1037,8 @@ export interface CreateTenantLocationPayload {
|
||||
city?: string;
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
applocationid?: number;
|
||||
}
|
||||
|
||||
@@ -1050,6 +1052,8 @@ export interface CreateTenantInput {
|
||||
city?: string;
|
||||
state?: string;
|
||||
postcode?: string;
|
||||
latitude?: string;
|
||||
longitude?: string;
|
||||
approved?: number;
|
||||
status?: string;
|
||||
applocationid?: number;
|
||||
|
||||
Reference in New Issue
Block a user