feature: a minimal implementation of staff management is done.
This commit is contained in:
70
frontend/src/components/ui/Checkbox.tsx
Normal file
70
frontend/src/components/ui/Checkbox.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import { useId } from 'react';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
type CheckboxProps = {
|
||||
checked: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
disabled?: boolean;
|
||||
label: string;
|
||||
id?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* App design-system checkbox: primary fill when checked, rounded, focus-visible ring.
|
||||
*/
|
||||
export function Checkbox({
|
||||
checked,
|
||||
onChange,
|
||||
disabled = false,
|
||||
label,
|
||||
id,
|
||||
className = '',
|
||||
}: CheckboxProps) {
|
||||
const genId = useId();
|
||||
const inputId = id ?? genId;
|
||||
|
||||
return (
|
||||
<label
|
||||
htmlFor={inputId}
|
||||
className={`
|
||||
inline-flex items-center gap-2.5 cursor-pointer select-none rounded-[var(--radius-sm)] -m-0.5 p-0.5
|
||||
has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-primary/45 has-[:focus-visible]:ring-offset-2
|
||||
has-[:focus-visible]:ring-offset-background-secondary
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
|
||||
${className}
|
||||
`}
|
||||
>
|
||||
<input
|
||||
id={inputId}
|
||||
type="checkbox"
|
||||
className="sr-only"
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.checked)}
|
||||
/>
|
||||
<span
|
||||
className={`
|
||||
flex h-5 w-5 shrink-0 items-center justify-center rounded-[var(--radius-sm)] border-2 transition-all duration-200
|
||||
shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]
|
||||
${
|
||||
checked
|
||||
? 'border-primary bg-primary shadow-[0_0_0_1px_rgba(9,169,188,0.25)]'
|
||||
: 'border-border-strong bg-background-card/90 hover:border-border'
|
||||
}
|
||||
`}
|
||||
aria-hidden
|
||||
>
|
||||
<Check
|
||||
strokeWidth={3}
|
||||
className={`h-3.5 w-3.5 text-primary-contrast transition-all duration-200 ${
|
||||
checked ? 'scale-100 opacity-100' : 'scale-75 opacity-0'
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
<span className="text-sm text-text-secondary">{label}</span>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { memo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import {
|
||||
LayoutDashboard,
|
||||
@@ -12,19 +12,27 @@ import {
|
||||
FileText,
|
||||
CreditCard,
|
||||
} from 'lucide-react';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { canViewTab } from '@/access';
|
||||
|
||||
const menu = [
|
||||
{ name: 'Today', path: '/today', icon: LayoutDashboard },
|
||||
{ name: 'Patients', path: '/patients', icon: Users },
|
||||
{ name: 'Appointments', path: '/appointments', icon: Calendar },
|
||||
{ name: 'Staff Management', path: '/staff', icon: UserCog },
|
||||
{ name: 'Lab Management', path: '/lab', icon: FlaskConical },
|
||||
{ name: 'Billing', path: '/billing', icon: CreditCard },
|
||||
{ name: 'Reports', path: '/reports', icon: FileText },
|
||||
{ name: 'Today', path: '/today', icon: LayoutDashboard, read: 'TAB_TODAY_READ' as const },
|
||||
{ name: 'Patients', path: '/patients', icon: Users, read: 'TAB_PATIENTS_READ' as const },
|
||||
{ name: 'Appointments', path: '/appointments', icon: Calendar, read: 'TAB_APPOINTMENTS_READ' as const },
|
||||
{ name: 'Staff Management', path: '/staff', icon: UserCog, read: 'TAB_STAFF_READ' as const },
|
||||
{ name: 'Lab Management', path: '/lab', icon: FlaskConical, read: 'TAB_LAB_READ' as const },
|
||||
{ name: 'Billing', path: '/billing', icon: CreditCard, read: 'TAB_BILLING_READ' as const },
|
||||
{ name: 'Reports', path: '/reports', icon: FileText, read: 'TAB_REPORTS_READ' as const },
|
||||
];
|
||||
|
||||
function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const { currentOrganization } = useAuth();
|
||||
|
||||
const visibleMenu = useMemo(
|
||||
() => menu.filter((item) => canViewTab(currentOrganization, item.read)),
|
||||
[currentOrganization],
|
||||
);
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-background-secondary/90 border-r border-border text-text-primary flex flex-col">
|
||||
@@ -34,7 +42,7 @@ function Sidebar() {
|
||||
<div className="mx-4 border-b border-border/70" />
|
||||
|
||||
<nav className="flex flex-col gap-2 p-4">
|
||||
{menu.map((item) => {
|
||||
{visibleMenu.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive = pathname === item.path;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user