AdminDepartments: light theme, group by location, location_name support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-07 12:42:50 +00:00
parent f3d46b8c80
commit 1dcf8bfbcb

View file

@ -6,6 +6,8 @@ interface WfDept {
id: string id: string
name: string name: string
staff_count: number staff_count: number
location_id: string | null
location_name: string | null
mapped_role_id: number | null mapped_role_id: number | null
} }
@ -80,6 +82,16 @@ export function AdminDepartments({ user }: { user: User }) {
} }
} }
// Group departments by location
const locations = Array.from(new Set(depts.map(d => d.location_name ?? ''))).filter(Boolean)
const hasMultipleLocations = locations.length > 1
const grouped: Record<string, WfDept[]> = {}
for (const d of depts) {
const key = d.location_name ?? ''
if (!grouped[key]) grouped[key] = []
grouped[key].push(d)
}
return ( return (
<div style={{ display: 'flex', height: '100dvh' }}> <div style={{ display: 'flex', height: '100dvh' }}>
<Sidebar user={user} /> <Sidebar user={user} />
@ -96,53 +108,67 @@ export function AdminDepartments({ user }: { user: User }) {
</div> </div>
</div> </div>
{syncResult && ( <p style={{ fontSize: '0.82rem', color: 'var(--text-mid)', marginBottom: syncResult ? '0.5rem' : '1.25rem' }}>
<div style={{ fontSize: '0.82rem', color: 'var(--text-muted)', marginBottom: '1rem',
background: 'var(--surface)', borderRadius: '6px', padding: '0.6rem 0.875rem',
border: '1px solid var(--surface-2)' }}>
{syncResult}
</div>
)}
<p style={{ fontSize: '0.82rem', color: 'var(--text-muted)', marginBottom: '1.25rem' }}>
Map Workforce departments to roles. Self-registering employees will automatically Map Workforce departments to roles. Self-registering employees will automatically
receive the role mapped to their department. receive the role mapped to their department.
</p> </p>
{error && <p style={{ color: 'var(--danger)', fontSize: '0.85rem', marginBottom: '1rem' }}>{error}</p>} {syncResult && (
<div style={{ fontSize: '0.82rem', color: 'var(--text-mid)', marginBottom: '1rem',
background: 'var(--card-bg)', borderRadius: '6px', padding: '0.6rem 0.875rem',
border: '1px solid var(--card-border)' }}>
{syncResult}
</div>
)}
{error && <p style={{ color: '#dc2626', fontSize: '0.85rem', marginBottom: '1rem' }}>{error}</p>}
{loading ? ( {loading ? (
<p style={{ color: 'var(--text-muted)', fontSize: '0.875rem' }}>Loading departments</p> <p style={{ color: 'var(--text-mid)', fontSize: '0.875rem' }}>Loading departments</p>
) : ( ) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}> <div style={{ display: 'flex', flexDirection: 'column', gap: hasMultipleLocations ? '1.5rem' : '0.5rem' }}>
{depts.map(dept => ( {Object.entries(grouped).map(([location, locationDepts]) => (
<div key={dept.id} style={{ <div key={location || 'default'}>
background: 'var(--surface)', borderRadius: '8px', {hasMultipleLocations && location && (
padding: '0.75rem 1rem', border: '1px solid var(--surface-2)', <div style={{ fontSize: '0.72rem', fontWeight: 700, textTransform: 'uppercase',
display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap', letterSpacing: '0.06em', color: 'var(--text-mid)', marginBottom: '0.5rem',
}}> paddingBottom: '0.25rem', borderBottom: '1px solid var(--card-border)' }}>
<div style={{ flex: 1, minWidth: '140px' }}> {location}
<div style={{ fontWeight: 600, fontSize: '0.875rem' }}>{dept.name}</div>
<div style={{ fontSize: '0.75rem', color: 'var(--text-muted)' }}>
{dept.staff_count} staff
</div> </div>
</div> )}
<select <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
value={mappings[dept.id] ?? ''} {locationDepts.map(dept => (
onChange={e => setMappings(m => ({ <div key={dept.id} style={{
...m, [dept.id]: e.target.value ? Number(e.target.value) : null background: 'var(--card-bg)', borderRadius: '8px',
}))} padding: '0.75rem 1rem', border: '1px solid var(--card-border)',
style={{ boxShadow: 'var(--shadow-sm)',
background: 'var(--navy-dark)', border: '1px solid var(--surface-2)', display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap',
borderRadius: '6px', color: 'var(--text)', padding: '0.4rem 0.6rem', }}>
fontSize: '0.82rem', minWidth: '160px', <div style={{ flex: 1, minWidth: '140px' }}>
}} <div style={{ fontWeight: 600, fontSize: '0.875rem' }}>{dept.name}</div>
> <div style={{ fontSize: '0.75rem', color: 'var(--text-mid)' }}>
<option value=""> No role </option> {dept.staff_count} staff
{roles.map(r => ( </div>
<option key={r.id} value={r.id}>{r.name}</option> </div>
<select
value={mappings[dept.id] ?? ''}
onChange={e => setMappings(m => ({
...m, [dept.id]: e.target.value ? Number(e.target.value) : null
}))}
style={{
background: 'var(--body-bg)', border: '1px solid var(--card-border)',
borderRadius: '6px', color: 'var(--text-dark)', padding: '0.4rem 0.6rem',
fontSize: '0.82rem', minWidth: '160px',
}}
>
<option value=""> No role </option>
{roles.map(r => (
<option key={r.id} value={r.id}>{r.name}</option>
))}
</select>
</div>
))} ))}
</select> </div>
</div> </div>
))} ))}
</div> </div>
@ -153,10 +179,10 @@ export function AdminDepartments({ user }: { user: User }) {
} }
const goldBtn: React.CSSProperties = { const goldBtn: React.CSSProperties = {
background: 'var(--gold)', color: 'var(--navy-dark)', border: 'none', background: 'var(--navy)', color: '#fff', border: 'none',
borderRadius: '6px', padding: '0.5rem 1rem', fontSize: '0.875rem', fontWeight: 600, borderRadius: '6px', padding: '0.5rem 1rem', fontSize: '0.875rem', fontWeight: 600, cursor: 'pointer',
} }
const mutedBtn: React.CSSProperties = { const mutedBtn: React.CSSProperties = {
background: 'var(--surface-2)', color: 'var(--text-muted)', border: '1px solid var(--surface-2)', background: 'var(--body-bg)', color: 'var(--text-mid)', border: '1px solid var(--card-border)',
borderRadius: '6px', padding: '0.5rem 1rem', fontSize: '0.875rem', borderRadius: '6px', padding: '0.5rem 1rem', fontSize: '0.875rem', cursor: 'pointer',
} }