feat: user dropdowns collapse 'All Staff' behind expandable toggle

- Add shared UserSelectContent component with Claims-first layout
- All Staff section is collapsed by default, click the label to expand
- Applied to: tasks 'Viewing as', tasks 'Transfer to', assign page
  (claims advocate + bulk assign), additional service modal 'Assign to'
This commit is contained in:
lorentz 2026-04-08 02:48:31 +00:00
parent a160a88b08
commit d45daacf28
5 changed files with 87 additions and 117 deletions

View file

@ -10,7 +10,10 @@
"Bash(git remote add:*)",
"Bash(git commit:*)",
"Bash(git config:*)",
"Bash(git push:*)"
"Bash(git push:*)",
"Bash(curl -s -X POST https://login.microsoftonline.com/480673c9-b57f-4366-b0ea-e317cbc6c160/oauth2/v2.0/token -H 'Content-Type: application/x-www-form-urlencoded' -d 'client_id=f6c778cf-c850-47d0-85e7-aff1d1c3288b&client_secret=0Wi8Q~6hvmszI1W4WeK3iI~NeSixFggyhcC_Qavk&scope=https://graph.microsoft.com/.default&grant_type=client_credentials')",
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('id',''\\), d.get\\('name',''\\), d.get\\('error',''\\)\\)\")",
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d, indent=2\\)\\)\")"
]
}
}

View file

@ -9,12 +9,11 @@ import { Checkbox } from '@/components/ui/checkbox'
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { UserSelectContent } from '@/components/ui/user-select-content'
import {
Table,
TableBody,
@ -167,35 +166,6 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli
}
}
const renderUserOptions = () => {
const claims = users.filter((u) => u.department?.toLowerCase().includes('claims'))
const others = users.filter((u) => !u.department?.toLowerCase().includes('claims'))
return (
<>
{claims.length > 0 && (
<SelectGroup>
<SelectLabel>Claims</SelectLabel>
{claims.map((u) => (
<SelectItem key={u.id} value={u.id}>
{u.displayName || u.email}
</SelectItem>
))}
</SelectGroup>
)}
{others.length > 0 && (
<SelectGroup>
<SelectLabel>All Staff</SelectLabel>
{others.map((u) => (
<SelectItem key={u.id} value={u.id}>
{u.displayName || u.email}
</SelectItem>
))}
</SelectGroup>
)}
</>
)
}
const statusColor: Record<string, string> = {
NOT_STARTED: 'secondary',
IN_PROGRESS: 'default',
@ -229,10 +199,10 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli
<SelectTrigger>
<SelectValue placeholder="Select advocate..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="_none">Select advocate</SelectItem>
{renderUserOptions()}
</SelectContent>
<UserSelectContent
users={users}
prefixItem={<SelectItem value="_none">Select advocate</SelectItem>}
/>
</Select>
</div>
<div className="flex-1 min-w-[200px]">
@ -321,10 +291,10 @@ export function BulkAssignClient({ users, clients, designations }: BulkAssignCli
<SelectTrigger className="w-[220px]">
<SelectValue placeholder="Assign to..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="_none">Select user</SelectItem>
{renderUserOptions()}
</SelectContent>
<UserSelectContent
users={users}
prefixItem={<SelectItem value="_none">Select user</SelectItem>}
/>
</Select>
<Button onClick={handleAssign} disabled={!assignTo || assigning}>
{assigning ? 'Assigning...' : 'Assign'}

View file

@ -16,12 +16,11 @@ import {
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { UserSelectContent } from '@/components/ui/user-select-content'
import { CheckSquare, Clock, AlertCircle, MessageSquare, CheckCircle2, RotateCcw, Eye, CalendarRange, ArrowRightLeft, Search, X, Building2, Plus, Ban } from 'lucide-react'
import { Input } from '@/components/ui/input'
import { formatDate } from '@/lib/utils'
@ -591,34 +590,11 @@ export function TasksClient({ initialTasks, currentUserId, isPrivileged, users }
<SelectTrigger className="w-[200px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={currentUserId}>Myself</SelectItem>
{(() => {
const others = users.filter((u) => u.id !== currentUserId)
const claims = others.filter((u) => u.department?.toLowerCase().includes('claims'))
const rest = others.filter((u) => !u.department?.toLowerCase().includes('claims'))
return (
<>
{claims.length > 0 && (
<SelectGroup>
<SelectLabel>Claims</SelectLabel>
{claims.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.displayName || u.email}</SelectItem>
))}
</SelectGroup>
)}
{rest.length > 0 && (
<SelectGroup>
<SelectLabel>All Staff</SelectLabel>
{rest.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.displayName || u.email}</SelectItem>
))}
</SelectGroup>
)}
</>
)
})()}
</SelectContent>
<UserSelectContent
users={users}
excludeIds={[currentUserId]}
prefixItem={<SelectItem value={currentUserId}>Myself</SelectItem>}
/>
</Select>
</div>
)}
@ -849,33 +825,7 @@ export function TasksClient({ initialTasks, currentUserId, isPrivileged, users }
<SelectTrigger>
<SelectValue placeholder="Select team member..." />
</SelectTrigger>
<SelectContent>
{(() => {
const eligible = users.filter((u) => u.id !== viewingUserId)
const claims = eligible.filter((u) => u.department?.toLowerCase().includes('claims'))
const rest = eligible.filter((u) => !u.department?.toLowerCase().includes('claims'))
return (
<>
{claims.length > 0 && (
<SelectGroup>
<SelectLabel>Claims</SelectLabel>
{claims.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.displayName || u.email}</SelectItem>
))}
</SelectGroup>
)}
{rest.length > 0 && (
<SelectGroup>
<SelectLabel>All Staff</SelectLabel>
{rest.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.displayName || u.email}</SelectItem>
))}
</SelectGroup>
)}
</>
)
})()}
</SelectContent>
<UserSelectContent users={users} excludeIds={[viewingUserId]} />
</Select>
</div>
</div>

View file

@ -21,6 +21,7 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { UserSelectContent } from '@/components/ui/user-select-content'
import { Plus, Wand2 } from 'lucide-react'
interface SimpleUser {
@ -188,8 +189,6 @@ export function AdditionalServiceModal({
}
}
const claimsUsers = users.filter((u) => u.department?.toLowerCase().includes('claims'))
const otherUsers = users.filter((u) => !u.department?.toLowerCase().includes('claims'))
return (
<Dialog open={open} onOpenChange={onOpenChange}>
@ -359,24 +358,7 @@ export function AdditionalServiceModal({
<label className="text-sm font-medium">Assign to</label>
<Select value={assignTo} onValueChange={setAssignTo}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{claimsUsers.length > 0 && (
<SelectGroup>
<SelectLabel>Claims</SelectLabel>
{claimsUsers.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.displayName || u.email}</SelectItem>
))}
</SelectGroup>
)}
{otherUsers.length > 0 && (
<SelectGroup>
<SelectLabel>All Staff</SelectLabel>
{otherUsers.map((u) => (
<SelectItem key={u.id} value={u.id}>{u.displayName || u.email}</SelectItem>
))}
</SelectGroup>
)}
</SelectContent>
<UserSelectContent users={users} />
</Select>
</div>
</div>

View file

@ -0,0 +1,65 @@
'use client'
import { useState } from 'react'
import { SelectContent, SelectGroup, SelectItem, SelectLabel } from '@/components/ui/select'
import { ChevronDown } from 'lucide-react'
export interface SelectableUser {
id: string
displayName: string | null
email: string
department?: string | null
}
interface UserSelectContentProps {
users: SelectableUser[]
excludeIds?: string[]
prefixItem?: React.ReactNode
}
export function UserSelectContent({ users, excludeIds = [], prefixItem }: UserSelectContentProps) {
const [showAll, setShowAll] = useState(false)
const eligible = users.filter((u) => !excludeIds.includes(u.id))
const claims = eligible.filter((u) => u.department?.toLowerCase().includes('claims'))
const rest = eligible.filter((u) => !u.department?.toLowerCase().includes('claims'))
return (
<SelectContent>
{prefixItem}
{claims.length > 0 && (
<SelectGroup>
<SelectLabel>Claims</SelectLabel>
{claims.map((u) => (
<SelectItem key={u.id} value={u.id}>
{u.displayName || u.email}
</SelectItem>
))}
</SelectGroup>
)}
{rest.length > 0 && (
<SelectGroup>
<SelectLabel
className="flex items-center justify-between cursor-pointer select-none hover:text-foreground"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setShowAll((v) => !v)
}}
>
<span>All Staff</span>
<ChevronDown
className={`h-3.5 w-3.5 transition-transform ${showAll ? 'rotate-180' : ''}`}
/>
</SelectLabel>
{showAll &&
rest.map((u) => (
<SelectItem key={u.id} value={u.id}>
{u.displayName || u.email}
</SelectItem>
))}
</SelectGroup>
)}
</SelectContent>
)
}