diff --git a/distribution_fe/app/(dashboard)/network/regions/_components/region-manage-sheet.tsx b/distribution_fe/app/(dashboard)/network/regions/_components/region-manage-sheet.tsx new file mode 100644 index 0000000..8d19d60 --- /dev/null +++ b/distribution_fe/app/(dashboard)/network/regions/_components/region-manage-sheet.tsx @@ -0,0 +1,276 @@ +'use client' + +import { useEffect, useState } from 'react' + +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, +} from '@/components/ui/dialog' +import { Separator } from '@/components/ui/separator' +import { toast } from '@/components/ui/toast' +import { regions as regionsApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index' +import type { + RegionResponse, + UserResponse, + RegionDashboardResponse, + RegionHierarchyResponse, +} from '@/types/api' + +interface RegionManageSheetProps { + open: boolean + onClose: () => void + region: RegionResponse | null +} + +function currentPeriod() { + const now = new Date() + return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}` +} + +export function RegionManageSheet({ open, onClose, region }: RegionManageSheetProps) { + const [period, setPeriod] = useState(currentPeriod()) + const [targetValue, setTargetValue] = useState('') + const [budgetAmount, setBudgetAmount] = useState('') + const [ruleConfig, setRuleConfig] = useState('') + const [agencyUserId, setAgencyUserId] = useState('') + const [agencyUsers, setAgencyUsers] = useState([]) + const [dashboard, setDashboard] = useState(null) + const [hierarchy, setHierarchy] = useState(null) + const [saving, setSaving] = useState(null) + + useEffect(() => { + if (!open || !region) return + + setPeriod(currentPeriod()) + setTargetValue('') + setBudgetAmount('') + setRuleConfig('') + setAgencyUserId('') + + usersApi.list({ page: 1, pageSize: 200 }) + .then((res) => setAgencyUsers(getPaginatedItems(res).filter((u) => u.roleName === 'RegionalAgency'))) + .catch(() => toast.add({ title: 'Failed to load agency users', type: 'error' })) + + regionsApi.hierarchy(region.id) + .then(setHierarchy) + .catch(() => toast.add({ title: 'Failed to load hierarchy', type: 'error' })) + }, [open, region]) + + useEffect(() => { + if (!open || !region) return + regionsApi.dashboard(region.id, period) + .then(setDashboard) + .catch(() => toast.add({ title: 'Failed to load dashboard', type: 'error' })) + }, [open, region, period]) + + if (!region) return null + + async function handleSetTarget() { + const value = parseFloat(targetValue) + if (Number.isNaN(value)) return + setSaving('target') + try { + await regionsApi.setTarget(region!.id, period, value) + toast.add({ title: 'Target updated', type: 'success' }) + regionsApi.dashboard(region!.id, period).then(setDashboard) + } catch (err) { + toast.add({ title: 'Failed to set target', description: getApiError(err), type: 'error' }) + } finally { + setSaving(null) + } + } + + async function handleSetBudget() { + const value = parseFloat(budgetAmount) + if (Number.isNaN(value)) return + setSaving('budget') + try { + await regionsApi.setBudget(region!.id, period, value) + toast.add({ title: 'Budget updated', type: 'success' }) + regionsApi.dashboard(region!.id, period).then(setDashboard) + } catch (err) { + toast.add({ title: 'Failed to set budget', description: getApiError(err), type: 'error' }) + } finally { + setSaving(null) + } + } + + async function handleSetTaxRule() { + if (!ruleConfig.trim()) return + setSaving('tax-rule') + try { + await regionsApi.setTaxRule(region!.id, ruleConfig) + toast.add({ title: 'Tax rule updated', type: 'success' }) + } catch (err) { + toast.add({ title: 'Failed to set tax rule', description: getApiError(err), type: 'error' }) + } finally { + setSaving(null) + } + } + + async function handleMapAgency() { + if (!agencyUserId) return + setSaving('agency') + try { + await regionsApi.mapAgency(region!.id, agencyUserId) + toast.add({ title: 'Agency mapped to region', type: 'success' }) + setAgencyUserId('') + } catch (err) { + toast.add({ title: 'Failed to map agency', description: getApiError(err), type: 'error' }) + } finally { + setSaving(null) + } + } + + return ( + !v && onClose()}> + + + Manage {region.name} + + Targets, budgets, tax rules, agency mapping, and dashboard for this region. + + + +
+
+ + setPeriod(e.target.value)} + className="w-40" + /> +
+ + + +
+ +
+ setTargetValue(e.target.value)} + /> + +
+ {dashboard?.targetValue != null && ( +

Current: {dashboard.targetValue}

+ )} +
+ +
+ +
+ setBudgetAmount(e.target.value)} + /> + +
+ {dashboard?.budgetAmount != null && ( +

Current: {dashboard.budgetAmount}

+ )} +
+ + + +
+ +
+ setRuleConfig(e.target.value)} + /> + +
+
+ + + +
+ +
+ + +
+ {agencyUsers.length === 0 && ( +

No users with the RegionalAgency role found.

+ )} +
+ + + +
+ + {hierarchy && hierarchy.routes.length > 0 ? ( +
    + {hierarchy.routes.map((r) => ( +
  • + {r.routeName} — {r.storeIds.length} store{r.storeIds.length === 1 ? '' : 's'} +
  • + ))} +
+ ) : ( +

No routes under this region yet.

+ )} +
+ + + +
+ + {dashboard ? ( +
+
Orders: {dashboard.orderCount}
+
Actual Sales: {dashboard.actualSales}
+
Target: {dashboard.targetValue ?? '—'}
+
Budget: {dashboard.budgetAmount ?? '—'}
+
+ ) : ( +

Loading dashboard…

+ )} +
+
+
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/network/regions/_components/regions-client.tsx b/distribution_fe/app/(dashboard)/network/regions/_components/regions-client.tsx index 3017e36..2f23e80 100644 --- a/distribution_fe/app/(dashboard)/network/regions/_components/regions-client.tsx +++ b/distribution_fe/app/(dashboard)/network/regions/_components/regions-client.tsx @@ -13,6 +13,7 @@ import { regions as regionsApi, getApiError, getPaginatedItems } from '@/lib/api import type { RegionResponse } from '@/types/api' import { RegionSheet } from './region-sheet' +import { RegionManageSheet } from './region-manage-sheet' export function RegionsClient() { const [data, setData] = useState([]) @@ -24,6 +25,9 @@ export function RegionsClient() { const [sheetOpen, setSheetOpen] = useState(false) const [editing, setEditing] = useState(null) + const [manageOpen, setManageOpen] = useState(false) + const [managing, setManaging] = useState(null) + const fetchData = useCallback(async (p: number, ps: number) => { setLoading(true) try { @@ -51,6 +55,11 @@ export function RegionsClient() { setSheetOpen(true) } + function openManage(region: RegionResponse) { + setManaging(region) + setManageOpen(true) + } + async function toggleActive(region: RegionResponse) { try { await regionsApi.update(region.id, { isActive: !region.isActive }) @@ -103,6 +112,11 @@ export function RegionsClient() { ], rowActions: { actions: () => [ + { + action: 'confirm', + label: 'Manage targets, budgets & agencies', + onClick: openManage, + }, { action: 'edit', label: 'Edit region', @@ -122,7 +136,7 @@ export function RegionsClient() { }, ], }, - }), [pageSize, openEdit, toggleActive]) + }), [pageSize, openEdit, openManage, toggleActive]) return ( <> @@ -156,6 +170,12 @@ export function RegionsClient() { region={editing} onSuccess={() => fetchData(page, pageSize)} /> + + setManageOpen(false)} + region={managing} + /> ) }