Design principles: Claims First (collapsed others) + Renewal Date (+1 day) everywhere
This commit is contained in:
parent
4315f704c7
commit
02d1fcb73d
5 changed files with 65 additions and 38 deletions
|
|
@ -5,7 +5,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Building2, Calendar, FileText, CheckSquare, GitBranch, CornerLeftUp } from 'lucide-react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { formatDate, daysUntil } from '@/lib/utils'
|
||||
import { formatRenewalDate, daysUntil } from '@/lib/utils'
|
||||
|
||||
interface ClientCardProps {
|
||||
client: {
|
||||
|
|
@ -117,7 +117,7 @@ export function ClientCard({ client }: ClientCardProps) {
|
|||
<Calendar className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-muted-foreground">Renewal date:</span>
|
||||
<span className="font-medium" suppressHydrationWarning>
|
||||
{formatDate(nextPolicy.expirationDate)}
|
||||
{formatRenewalDate(nextPolicy.expirationDate)}
|
||||
</span>
|
||||
{daysToExpiration !== null && daysToExpiration <= 90 && (
|
||||
<Badge
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
import { Badge } from '@/components/ui/badge'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { GitBranch, CornerLeftUp } from 'lucide-react'
|
||||
import { formatDate, daysUntil } from '@/lib/utils'
|
||||
import { formatRenewalDate, daysUntil } from '@/lib/utils'
|
||||
|
||||
interface ClientTableProps {
|
||||
clients: Array<{
|
||||
|
|
@ -134,7 +134,7 @@ export function ClientTable({ clients }: ClientTableProps) {
|
|||
<TableCell>
|
||||
{nextPolicy ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<span suppressHydrationWarning>{formatDate(nextPolicy.expirationDate)}</span>
|
||||
<span suppressHydrationWarning>{formatRenewalDate(nextPolicy.expirationDate)}</span>
|
||||
{daysToExpiration !== null && daysToExpiration <= 90 && (
|
||||
<Badge
|
||||
variant={daysToExpiration <= 30 ? 'destructive' : 'secondary'}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
CreditCard,
|
||||
Shield
|
||||
} from 'lucide-react'
|
||||
import { formatDate } from '@/lib/utils'
|
||||
import { formatDate, formatRenewalDate } from '@/lib/utils'
|
||||
|
||||
interface PolicyDetailProps {
|
||||
policy: any
|
||||
|
|
@ -52,10 +52,10 @@ export function PolicyDetail({ policy }: PolicyDetailProps) {
|
|||
variant={isExpired ? 'destructive' : daysUntilExpiration <= 30 ? 'destructive' : daysUntilExpiration <= 90 ? 'secondary' : 'default'}
|
||||
className="text-sm"
|
||||
>
|
||||
{isExpired ? 'Expired' : `Expires in ${daysUntilExpiration} days`}
|
||||
{isExpired ? 'Expired' : `Renews in ${daysUntilExpiration} days`}
|
||||
</Badge>
|
||||
<p className="text-sm text-muted-foreground mt-2">
|
||||
<span suppressHydrationWarning>{formatDate(policy.expirationDate)}</span>
|
||||
<span suppressHydrationWarning>{formatRenewalDate(policy.expirationDate)}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -117,10 +117,10 @@ export function PolicyDetail({ policy }: PolicyDetailProps) {
|
|||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Expiration Date</p>
|
||||
<p className="text-sm text-muted-foreground">Renewal Date</p>
|
||||
<p className="font-medium">
|
||||
<span suppressHydrationWarning>
|
||||
{formatDate(policy.expirationDate)}
|
||||
{formatRenewalDate(policy.expirationDate)}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export function PolicyChip({ policy, disabled }: PolicyChipProps) {
|
|||
</div>
|
||||
<div className="text-xs text-muted-foreground truncate">
|
||||
{policy.carrierName || 'Unknown carrier'}
|
||||
{expDisplay && <span suppressHydrationWarning> · Exp {expDisplay}</span>}
|
||||
{expDisplay && <span suppressHydrationWarning> · Renews {expDisplay}</span>}
|
||||
</div>
|
||||
</div>
|
||||
{!policy.expirationDate && (
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
'use client'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { SelectContent, SelectGroup, SelectItem, SelectLabel } from '@/components/ui/select'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
|
||||
export interface SelectableUser {
|
||||
id: string
|
||||
|
|
@ -24,53 +25,79 @@ function formatDepartment(dept: string): string {
|
|||
.join(' ')
|
||||
}
|
||||
|
||||
/** Departments shown first in this order; everything else follows alphabetically */
|
||||
const PRIORITY_DEPARTMENTS = ['claims']
|
||||
|
||||
export function UserSelectContent({ users, excludeIds = [], prefixItem }: UserSelectContentProps) {
|
||||
const grouped = useMemo(() => {
|
||||
const eligible = users.filter((u) => !excludeIds.includes(u.id))
|
||||
const [showAll, setShowAll] = useState(false)
|
||||
|
||||
const { claimsUsers, otherGroups } = useMemo(() => {
|
||||
const eligible = users.filter((u) => !excludeIds.includes(u.id))
|
||||
const sorter = (a: SelectableUser, b: SelectableUser) =>
|
||||
(a.displayName || a.email).localeCompare(b.displayName || b.email)
|
||||
|
||||
const claims = eligible
|
||||
.filter((u) => u.department?.toLowerCase() === 'claims')
|
||||
.sort(sorter)
|
||||
|
||||
// Group remaining users by department, sorted alphabetically
|
||||
const map = new Map<string, SelectableUser[]>()
|
||||
for (const u of eligible) {
|
||||
if (u.department?.toLowerCase() === 'claims') continue
|
||||
const key = (u.department || 'other').toLowerCase()
|
||||
if (!map.has(key)) map.set(key, [])
|
||||
map.get(key)!.push(u)
|
||||
}
|
||||
for (const list of map.values()) list.sort(sorter)
|
||||
|
||||
// Sort users within each group
|
||||
for (const list of map.values()) {
|
||||
list.sort((a, b) =>
|
||||
(a.displayName || a.email).localeCompare(b.displayName || b.email)
|
||||
)
|
||||
}
|
||||
const sortedKeys = [...map.keys()].sort((a, b) => a.localeCompare(b))
|
||||
const groups = sortedKeys.map((key) => ({
|
||||
key,
|
||||
label: formatDepartment(key),
|
||||
users: map.get(key)!,
|
||||
}))
|
||||
|
||||
// Sort department keys: priority first, then alphabetical
|
||||
const keys = [...map.keys()].sort((a, b) => {
|
||||
const ai = PRIORITY_DEPARTMENTS.indexOf(a)
|
||||
const bi = PRIORITY_DEPARTMENTS.indexOf(b)
|
||||
if (ai !== -1 && bi !== -1) return ai - bi
|
||||
if (ai !== -1) return -1
|
||||
if (bi !== -1) return 1
|
||||
return a.localeCompare(b)
|
||||
})
|
||||
|
||||
return keys.map((key) => ({ key, label: formatDepartment(key), users: map.get(key)! }))
|
||||
return { claimsUsers: claims, otherGroups: groups }
|
||||
}, [users, excludeIds])
|
||||
|
||||
return (
|
||||
<SelectContent>
|
||||
{prefixItem}
|
||||
{grouped.map((group) => (
|
||||
<SelectGroup key={group.key}>
|
||||
<SelectLabel>{group.label}</SelectLabel>
|
||||
{group.users.map((u) => (
|
||||
{claimsUsers.length > 0 && (
|
||||
<SelectGroup>
|
||||
<SelectLabel>Claims</SelectLabel>
|
||||
{claimsUsers.map((u) => (
|
||||
<SelectItem key={u.id} value={u.id}>
|
||||
{u.displayName || u.email}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))}
|
||||
)}
|
||||
{otherGroups.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 &&
|
||||
otherGroups.map((group) => (
|
||||
<SelectGroup key={group.key} className="pl-2">
|
||||
<SelectLabel className="text-xs text-muted-foreground">{group.label}</SelectLabel>
|
||||
{group.users.map((u) => (
|
||||
<SelectItem key={u.id} value={u.id}>
|
||||
{u.displayName || u.email}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectGroup>
|
||||
))}
|
||||
</SelectGroup>
|
||||
)}
|
||||
</SelectContent>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue