feature: phase1 - org-type navigation, Cases permissions, staff filtering, and route guards.

This commit is contained in:
2026-06-28 14:59:06 +03:30
parent 64c7e5a257
commit dc965b2528
22 changed files with 376 additions and 76 deletions

View File

@@ -0,0 +1,14 @@
'use client';
import { useTranslations } from 'next-intl';
export default function CasesPage() {
const t = useTranslations('cases');
return (
<div className="space-y-4">
<h1 className="text-2xl font-semibold text-text-primary">{t('title')}</h1>
<p className="text-sm text-text-muted max-w-xl">{t('stubDescription')}</p>
</div>
);
}

View File

@@ -8,10 +8,8 @@ import Sidebar from '@/components/ui/shared/Sidebar';
import { TopBarControls } from '@/components/ui/shared/TopBarControls';
import { DashboardAccountMenu } from '@/components/ui/dashboard/DashboardAccountMenu';
import {
canAccessAppointmentsSection,
canAccessDashboardRoute,
firstAccessibleDashboardPath,
getRequiredReadPermissionForPath,
hasPermission,
} from '@/components/shared/permissions';
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
@@ -33,15 +31,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
return;
}
const required = getRequiredReadPermissionForPath(pathname);
if (required) {
const allowed =
hasPermission(currentOrganization, required) ||
(required === 'TAB_APPOINTMENTS_READ' &&
canAccessAppointmentsSection(currentOrganization));
if (!allowed) {
router.replace(firstAccessibleDashboardPath(currentOrganization));
}
if (!canAccessDashboardRoute(currentOrganization, pathname)) {
router.replace(firstAccessibleDashboardPath(currentOrganization));
}
}, [isAuthReady, user, currentOrganization, router, pathname]);

View File

@@ -9,13 +9,13 @@ import {
canViewStaff,
} from '@/components/shared/permissions';
import {
STAFF_FEATURE_GROUPS,
permissionNamesFromFeatureState,
emptyFeaturePermissionState,
featureStateFromPermissionNames,
featureStateHasTreatmentEdit,
resolveStaffFeatureLabel,
formatAccessSummary,
staffFeatureGroupsForOrgType,
type FeaturePermState,
} from '@/components/staff/staff-permission-form';
import {
@@ -112,7 +112,7 @@ function PermissionGrid({
return (
<div className="grid gap-3 sm:grid-cols-2">
{STAFF_FEATURE_GROUPS.map((g) => {
{staffFeatureGroupsForOrgType(organizationType).map((g) => {
const cell = state[g.edit] ?? { read: false, edit: false };
return (
<div
@@ -199,10 +199,14 @@ export default function StaffPage() {
const canEdit = useMemo(() => canEditStaff(currentOrganization), [currentOrganization]);
const inviteHasTreatmentEdit = useMemo(
() => featureStateHasTreatmentEdit(invitePerms),
[invitePerms],
() =>
currentOrganization?.type === 'CLINIC' && featureStateHasTreatmentEdit(invitePerms),
[currentOrganization?.type, invitePerms],
);
const editHasTreatmentEdit = useMemo(
() => currentOrganization?.type === 'CLINIC' && featureStateHasTreatmentEdit(editPerms),
[currentOrganization?.type, editPerms],
);
const editHasTreatmentEdit = useMemo(() => featureStateHasTreatmentEdit(editPerms), [editPerms]);
const hasActivePlan = Boolean(currentOrganization?.plan);
const atSeatLimit = useMemo(() => {
if (!seats || seats.unlimited) return false;
@@ -309,7 +313,7 @@ export default function StaffPage() {
setInviteStep(1);
setInviteEmail('');
setInviteName('');
setInvitePerms(emptyFeaturePermissionState());
setInvitePerms(emptyFeaturePermissionState(currentOrganization?.type));
const defaults = createDefaultWorkingHoursState();
setInviteWorkingHoursDays(defaults.days);
setInviteAutoRepeatWeekly(defaults.autoRepeatWeekly);
@@ -393,7 +397,7 @@ export default function StaffPage() {
setEditing(m);
setEditStep(1);
setEditName(m.name);
setEditPerms(featureStateFromPermissionNames(m.permissions ?? []));
setEditPerms(featureStateFromPermissionNames(m.permissions ?? [], currentOrganization?.type));
setEditHoursValidationError(null);
const defaults = createDefaultWorkingHoursState();
setEditWorkingHoursDays(defaults.days);