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:
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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],
|
||||
|
||||
3
frontend/src/components/ui/shared/formSelectStyles.ts
Normal file
3
frontend/src/components/ui/shared/formSelectStyles.ts
Normal 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';
|
||||
Reference in New Issue
Block a user