Assign a region manager for the route

This commit is contained in:
2026-10-06 15:40:43 +05:30
parent b75cd01e90
commit 3538bb5b0e
5 changed files with 257 additions and 0 deletions
@@ -0,0 +1,138 @@
'use client'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from '@/components/ui/dialog'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { toast } from '@/components/ui/toast'
import { regions as regionsApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index'
import type { RegionResponse, RegionManagerResponse, UserResponse } from '@/types/api'
const MANAGER_ROLE = 'RegionalManager'
interface RegionManagerDialogProps {
open: boolean
onClose: () => void
region: RegionResponse | null
}
export function RegionManagerDialog({ open, onClose, region }: RegionManagerDialogProps) {
const [current, setCurrent] = useState<RegionManagerResponse | null>(null)
const [candidates, setCandidates] = useState<UserResponse[]>([])
const [selected, setSelected] = useState('')
const [loading, setLoading] = useState(false)
const [saving, setSaving] = useState(false)
useEffect(() => {
if (!open || !region) return
let cancelled = false
setLoading(true)
setSelected('')
setCurrent(null)
Promise.all([
regionsApi.getManager(region.id).catch(() => null), // 404 = no manager yet
usersApi.list({ pageSize: 500 }),
])
.then(([manager, userRes]) => {
if (cancelled) return
setCurrent(manager)
setSelected(manager?.userId ?? '')
setCandidates(getPaginatedItems(userRes).filter((u) => u.isActive && u.roleName === MANAGER_ROLE))
})
.catch((err) => toast.add({ title: 'Failed to load users', description: getApiError(err), type: 'error' }))
.finally(() => !cancelled && setLoading(false))
return () => { cancelled = true }
}, [open, region])
async function save() {
if (!region || !selected) return
setSaving(true)
try {
await regionsApi.setManager(region.id, selected)
toast.add({ title: 'Region manager assigned', type: 'success' })
onClose()
} catch (err) {
toast.add({ title: 'Failed to assign manager', description: getApiError(err), type: 'error' })
} finally {
setSaving(false)
}
}
async function remove() {
if (!region) return
setSaving(true)
try {
await regionsApi.removeManager(region.id)
toast.add({ title: 'Region manager removed', type: 'success' })
onClose()
} catch (err) {
toast.add({ title: 'Failed to remove manager', description: getApiError(err), type: 'error' })
} finally {
setSaving(false)
}
}
return (
<Dialog open={open} onOpenChange={(v) => !v && !saving && onClose()}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Region Manager</DialogTitle>
<DialogDescription>
{region ? `${region.code} – ${region.name}. ` : ''}
Each region has one manager, chosen from users with the RegionalManager role.
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-3">
<p className="text-sm text-muted-foreground">
Current manager:{' '}
<span className="font-medium text-foreground">
{loading ? 'Loading…' : current ? `${current.fullName} (${current.username})` : 'None'}
</span>
</p>
<Select value={selected} onValueChange={(v) => setSelected(v ?? '')} disabled={loading || saving}>
<SelectTrigger className="w-full">
<SelectValue placeholder={candidates.length ? 'Select a manager' : 'No RegionalManager users available'} />
</SelectTrigger>
<SelectContent>
{candidates.map((u) => (
<SelectItem key={u.id} value={u.id}>
{u.fullName} ({u.username})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<DialogFooter>
{current && (
<Button type="button" variant="destructive" onClick={remove} disabled={saving}>
Remove manager
</Button>
)}
<Button type="button" variant="outline" onClick={onClose} disabled={saving}>
Cancel
</Button>
<Button type="button" onClick={save} disabled={saving || loading || !selected || selected === current?.userId}>
{saving ? 'Saving…' : 'Assign manager'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
@@ -14,6 +14,7 @@ import { regions as regionsApi, getApiError, getPaginatedItems } from '@/lib/api
import type { RegionResponse } from '@/types/api'
import { RegionSheet } from './region-sheet'
import { RegionManagerDialog } from './region-manager-dialog'
export function RegionsClient() {
const [data, setData] = useState<RegionResponse[]>([])
@@ -27,6 +28,7 @@ export function RegionsClient() {
const [viewOnly, setViewOnly] = useState(false)
const [deleting, setDeleting] = useState<RegionResponse | null>(null)
const [deleteBusy, setDeleteBusy] = useState(false)
const [managerRegion, setManagerRegion] = useState<RegionResponse | null>(null)
const fetchData = useCallback(async (p: number, ps: number) => {
setLoading(true)
@@ -124,6 +126,11 @@ export function RegionsClient() {
],
rowActions: {
actions: () => [
{
action: 'confirm',
label: 'Manage region manager',
onClick: (region: RegionResponse) => setManagerRegion(region),
},
{
action: 'view',
label: 'View region',
@@ -176,6 +183,12 @@ export function RegionsClient() {
readOnly={viewOnly}
onSuccess={() => fetchData(page, pageSize)}
/>
<RegionManagerDialog
open={!!managerRegion}
onClose={() => setManagerRegion(null)}
region={managerRegion}
/>
<Dialog open={!!deleting} onOpenChange={(v) => !v && !deleteBusy && setDeleting(null)}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
+10
View File
@@ -2,6 +2,7 @@ import { api } from '@/lib/api'
import type {
PaginatedResponse,
RegionResponse,
RegionManagerResponse,
ManageRegionResponse,
RegionHierarchyResponse,
RegionDashboardResponse,
@@ -92,6 +93,15 @@ export const regions = {
list: (params?: PageParams) =>
api.get<PaginatedResponse<RegionResponse>>('/regions', { params }).then(r => r.data),
getManager: (id: string) =>
api.get<RegionManagerResponse>(`/regions/${id}/manager`).then(r => r.data),
setManager: (id: string, userId: string) =>
api.put<RegionManagerResponse>(`/regions/${id}/manager`, { userId }).then(r => r.data),
removeManager: (id: string) =>
api.delete(`/regions/${id}/manager`),
nextCode: () =>
api.get<{ code: string }>('/regions/next-code').then(r => r.data),
+7
View File
@@ -729,3 +729,10 @@ export interface SysConfigResponse {
export interface ApiError {
error: string
}
export interface RegionManagerResponse {
regionId: string
userId: string
username: string
fullName: string
}