feature: Tasks tab added for lab organizations. tasks now can be assigned and their status can be updated by the assignee.

This commit is contained in:
2026-06-28 22:56:56 +03:30
parent feb0b26ad0
commit 2a48946a51
29 changed files with 851 additions and 43 deletions

View File

@@ -16,6 +16,7 @@ export const DASHBOARD_ROUTES: DashboardRouteConfig[] = [
{ prefix: '/appointments', permission: 'TAB_APPOINTMENTS_READ', orgTypes: ['CLINIC'] },
{ prefix: '/treatment', permission: 'TAB_TREATMENT_READ', orgTypes: ['CLINIC'] },
{ prefix: '/cases', permission: 'TAB_CASES_READ', orgTypes: ['LAB'] },
{ prefix: '/tasks', permission: 'TAB_TASKS_READ', orgTypes: ['LAB'] },
{ prefix: '/billing', permission: 'TAB_BILLING_READ', orgTypes: ['CLINIC', 'LAB'] },
{ prefix: '/reports', permission: 'TAB_REPORTS_READ', orgTypes: ['CLINIC', 'LAB'] },
];
@@ -71,6 +72,14 @@ export function canAccessDashboardRoute(org: Organization | null, pathname: stri
return canAccessAppointmentsSection(org);
}
if (route.prefix === '/cases') {
return canViewCases(org);
}
if (route.prefix === '/tasks') {
return canViewTasks(org);
}
return hasPermission(org, route.permission);
}
@@ -84,6 +93,14 @@ export function firstAccessibleDashboardPath(org: Organization | null): string {
if (canAccessAppointmentsSection(org)) return route.prefix;
continue;
}
if (route.prefix === '/cases') {
if (canViewCases(org)) return route.prefix;
continue;
}
if (route.prefix === '/tasks') {
if (canViewTasks(org)) return route.prefix;
continue;
}
if (hasPermission(org, route.permission)) return route.prefix;
}
@@ -175,3 +192,21 @@ export function canEditCases(org: Organization | null): boolean {
if (org.isOwner) return true;
return hasPermission(org, 'TAB_CASES_EDIT');
}
/** Lab task inbox */
export function canViewTasks(org: Organization | null): boolean {
if (!org) return false;
if (org.type !== 'LAB') return false;
if (org.isOwner) return true;
return (
hasPermission(org, 'TAB_TASKS_READ') ||
hasPermission(org, 'TAB_TASKS_EDIT')
);
}
export function canEditTasks(org: Organization | null): boolean {
if (!org) return false;
if (org.type !== 'LAB') return false;
if (org.isOwner) return true;
return hasPermission(org, 'TAB_TASKS_EDIT');
}

View File

@@ -13,6 +13,7 @@ export const STAFF_FEATURE_GROUPS = [
{ labelKey: 'featureAppointment', read: 'TAB_APPOINTMENTS_READ', edit: 'TAB_APPOINTMENTS_EDIT', orgTypes: ['CLINIC'] as const },
{ labelKey: 'featureTreatment', read: 'TAB_TREATMENT_READ', edit: 'TAB_TREATMENT_EDIT', orgTypes: ['CLINIC'] as const },
{ labelKey: 'featureCases', read: 'TAB_CASES_READ', edit: 'TAB_CASES_EDIT', orgTypes: ['LAB'] as const },
{ labelKey: 'featureTasks', read: 'TAB_TASKS_READ', edit: 'TAB_TASKS_EDIT', orgTypes: ['LAB'] as const },
{ labelKey: 'featureBilling', read: 'TAB_BILLING_READ', edit: 'TAB_BILLING_EDIT', orgTypes: ['CLINIC', 'LAB'] as const },
{ labelKey: 'featureReports', read: 'TAB_REPORTS_READ', edit: 'TAB_REPORTS_EDIT', orgTypes: ['CLINIC', 'LAB'] as const },
] as const;

View File

@@ -28,18 +28,13 @@ export const Dropdown = forwardRef<HTMLSelectElement, DropdownProps>(
ref={ref}
id={selectId}
className={`
w-full appearance-none rounded-[var(--radius-md)] border
form-select w-full appearance-none rounded-[var(--radius-md)] border
${error ? 'border-red-500' : 'border-border'}
bg-background-secondary/90 text-text-primary
bg-background-card text-text-primary
pl-4 pr-14 py-2 text-sm
focus:outline-none focus:ring-2 focus:ring-primary/35 focus:border-border-strong
disabled:opacity-50 disabled:cursor-not-allowed
transition-all duration-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.02)]
${className}
`}
{...props}

View File

@@ -12,6 +12,7 @@ import {
FileText,
CreditCard,
Package,
ListTodo,
} from 'lucide-react';
import type { OrgTypeName } from '@/components/shared/permissions';
import { useAuth } from '@/lib/hooks/useAuth';
@@ -19,6 +20,7 @@ import { usePendingConnectionsCount } from '@/lib/hooks/usePendingConnectionsCou
import {
canAccessAppointmentsSection,
canViewCases,
canViewTasks,
canViewTab,
} from '@/components/shared/permissions';
import {
@@ -55,6 +57,7 @@ function Sidebar() {
},
{ name: t('patients'), path: '/patients', icon: Users, read: 'TAB_PATIENTS_READ', orgTypes: ['CLINIC'] },
{ name: t('cases'), path: '/cases', icon: Package, read: 'TAB_CASES_READ', orgTypes: ['LAB'] },
{ name: t('tasks'), path: '/tasks', icon: ListTodo, read: 'TAB_TASKS_READ', orgTypes: ['LAB'] },
{ name: t('appointment'), path: '/appointments', icon: Calendar, read: 'TAB_APPOINTMENTS_READ', orgTypes: ['CLINIC'] },
{ name: t('treatment'), path: '/treatment', icon: FlaskConical, read: 'TAB_TREATMENT_READ', orgTypes: ['CLINIC'] },
{ name: t('billing'), path: '/billing', icon: CreditCard, read: 'TAB_BILLING_READ', orgTypes: ['CLINIC', 'LAB'] },
@@ -75,6 +78,9 @@ function Sidebar() {
if (item.path === '/cases') {
return canViewCases(currentOrganization);
}
if (item.path === '/tasks') {
return canViewTasks(currentOrganization);
}
return canViewTab(currentOrganization, item.read);
}),
[currentOrganization, menu, orgType],

View File

@@ -0,0 +1,3 @@
/** Shared native select styling — readable in light and dark themes */
export const FORM_SELECT_CLASS =
'form-select rounded border border-border bg-background-card text-text-primary px-2 py-1 text-sm disabled:opacity-60 focus:outline-none focus:ring-2 focus:ring-primary/35';