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:
parent
a160a88b08
commit
d45daacf28
5 changed files with 87 additions and 117 deletions
|
|
@ -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\\)\\)\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
65
ondeck/src/components/ui/user-select-content.tsx
Normal file
65
ondeck/src/components/ui/user-select-content.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue