rights system
This commit is contained in:
@@ -9,6 +9,7 @@ import NotificationBroadcastTab from '../components/admin/NotificationBroadcastT
|
||||
import BannerManagementTab from '../components/admin/BannerManagementTab';
|
||||
import ServiceModeTab from '../components/admin/ServiceModeTab';
|
||||
import FdiskSyncTab from '../components/admin/FdiskSyncTab';
|
||||
import PermissionMatrixTab from '../components/admin/PermissionMatrixTab';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
interface TabPanelProps {
|
||||
@@ -22,7 +23,7 @@ function TabPanel({ children, value, index }: TabPanelProps) {
|
||||
return <Box sx={{ pt: 3 }}>{children}</Box>;
|
||||
}
|
||||
|
||||
const ADMIN_TAB_COUNT = 7;
|
||||
const ADMIN_TAB_COUNT = 8;
|
||||
|
||||
function AdminDashboard() {
|
||||
const navigate = useNavigate();
|
||||
@@ -57,6 +58,7 @@ function AdminDashboard() {
|
||||
<Tab label="Banner" />
|
||||
<Tab label="Wartung" />
|
||||
<Tab label="FDISK Sync" />
|
||||
<Tab label="Berechtigungen" />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -81,6 +83,9 @@ function AdminDashboard() {
|
||||
<TabPanel value={tab} index={6}>
|
||||
<FdiskSyncTab />
|
||||
</TabPanel>
|
||||
<TabPanel value={tab} index={7}>
|
||||
<PermissionMatrixTab />
|
||||
</TabPanel>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||
import { atemschutzApi } from '../services/atemschutz';
|
||||
import { membersService } from '../services/members';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { toGermanDate, fromGermanDate, isValidGermanDate } from '../utils/dateInput';
|
||||
import type {
|
||||
AtemschutzUebersicht,
|
||||
@@ -142,10 +142,9 @@ const StatCard: React.FC<StatCardProps> = ({ label, value, color, bgcolor }) =>
|
||||
|
||||
function Atemschutz() {
|
||||
const notification = useNotification();
|
||||
const { user } = useAuth();
|
||||
const ATEMSCHUTZ_PRIVILEGED = ['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'];
|
||||
const canViewAll = user?.groups?.some(g => ATEMSCHUTZ_PRIVILEGED.includes(g)) ?? false;
|
||||
const canWrite = canViewAll;
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const canViewAll = hasPermission('atemschutz:view');
|
||||
const canWrite = hasPermission('atemschutz:create');
|
||||
|
||||
// Data state
|
||||
const [traeger, setTraeger] = useState<AtemschutzUebersicht[]>([]);
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import SkeletonCard from '../components/shared/SkeletonCard';
|
||||
import UserProfile from '../components/dashboard/UserProfile';
|
||||
@@ -33,13 +34,7 @@ import { WidgetKey } from '../constants/widgets';
|
||||
|
||||
function Dashboard() {
|
||||
const { user } = useAuth();
|
||||
const isAdmin = user?.groups?.includes('dashboard_admin') ?? false;
|
||||
const canViewAtemschutz = user?.groups?.some(g =>
|
||||
['dashboard_admin', 'dashboard_kommando', 'dashboard_atemschutz', 'dashboard_moderator'].includes(g)
|
||||
) ?? false;
|
||||
const canWrite = user?.groups?.some(g =>
|
||||
['dashboard_admin', 'dashboard_kommando', 'dashboard_moderator', 'dashboard_gruppenfuehrer'].includes(g)
|
||||
) ?? false;
|
||||
const { hasPermission, isAdmin } = usePermissionContext();
|
||||
const [dataLoading, setDataLoading] = useState(true);
|
||||
|
||||
const { data: preferences } = useQuery({
|
||||
@@ -120,7 +115,7 @@ function Dashboard() {
|
||||
</Fade>
|
||||
)}
|
||||
|
||||
{canViewAtemschutz && widgetVisible('atemschutz') && (
|
||||
{hasPermission('atemschutz:widget') && widgetVisible('atemschutz') && (
|
||||
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '400ms' }}>
|
||||
<Box>
|
||||
<AtemschutzDashboardCard />
|
||||
@@ -163,7 +158,7 @@ function Dashboard() {
|
||||
</Fade>
|
||||
)}
|
||||
|
||||
{canWrite && widgetVisible('eventQuickAdd') && (
|
||||
{hasPermission('kalender:widget_quick_add') && widgetVisible('eventQuickAdd') && (
|
||||
<Fade in={!dataLoading} timeout={600} style={{ transitionDelay: '600ms' }}>
|
||||
<Box>
|
||||
<EventQuickAddWidget />
|
||||
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
EINSATZ_STATUS_LABELS,
|
||||
} from '../services/incidents';
|
||||
import CreateEinsatzDialog from '../components/incidents/CreateEinsatzDialog';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// COLOUR MAP for Einsatzart chips
|
||||
@@ -176,10 +176,8 @@ function StatsSummaryBar({ stats, loading }: StatsSummaryProps) {
|
||||
// ---------------------------------------------------------------------------
|
||||
function Einsaetze() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const canWrite = user?.groups?.some((g: string) =>
|
||||
['dashboard_admin', 'dashboard_kommando', 'dashboard_gruppenfuehrer'].includes(g)
|
||||
) ?? false;
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const canWrite = hasPermission('einsaetze:create');
|
||||
|
||||
// List state
|
||||
const [items, setItems] = useState<EinsatzListItem[]>([]);
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
EinsatzArt,
|
||||
} from '../services/incidents';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// COLOUR MAPS
|
||||
@@ -165,10 +165,8 @@ function EinsatzDetail() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const notification = useNotification();
|
||||
const { user } = useAuth();
|
||||
const canWrite = user?.groups?.some((g: string) =>
|
||||
['dashboard_admin', 'dashboard_kommando', 'dashboard_gruppenfuehrer'].includes(g)
|
||||
) ?? false;
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const canWrite = hasPermission('einsaetze:create');
|
||||
|
||||
const [einsatz, setEinsatz] = useState<EinsatzDetailType | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { bookingApi, fetchVehicles } from '../services/bookings';
|
||||
import type {
|
||||
@@ -85,21 +86,17 @@ const EMPTY_FORM: CreateBuchungInput = {
|
||||
kontaktTelefon: '',
|
||||
};
|
||||
|
||||
const WRITE_GROUPS = ['dashboard_admin', 'dashboard_fahrmeister', 'dashboard_moderator'];
|
||||
const MANAGE_ART_GROUPS = ['dashboard_admin', 'dashboard_moderator'];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Main Page
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function FahrzeugBuchungen() {
|
||||
const { user } = useAuth();
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const notification = useNotification();
|
||||
const canCreate = !!user; // All authenticated users can create bookings
|
||||
const canWrite =
|
||||
user?.groups?.some((g) => WRITE_GROUPS.includes(g)) ?? false; // Can edit/cancel
|
||||
const canChangeBuchungsArt =
|
||||
user?.groups?.some((g) => MANAGE_ART_GROUPS.includes(g)) ?? false; // Can change booking type
|
||||
const canCreate = hasPermission('kalender:create_bookings');
|
||||
const canWrite = hasPermission('kalender:edit_bookings');
|
||||
const canChangeBuchungsArt = hasPermission('kalender:manage_categories');
|
||||
|
||||
// ── Week navigation ────────────────────────────────────────────────────────
|
||||
const [currentWeekStart, setCurrentWeekStart] = useState<Date>(() =>
|
||||
|
||||
@@ -72,6 +72,7 @@ import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||
import { toGermanDateTime, fromGermanDate, fromGermanDateTime, isValidGermanDateTime } from '../utils/dateInput';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { trainingApi } from '../services/training';
|
||||
import { eventsApi } from '../services/events';
|
||||
@@ -117,9 +118,6 @@ import { de } from 'date-fns/locale';
|
||||
// Constants
|
||||
// ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const WRITE_GROUPS_EVENTS = ['dashboard_admin', 'dashboard_moderator'];
|
||||
const WRITE_GROUPS_BOOKINGS = ['dashboard_admin', 'dashboard_fahrmeister', 'dashboard_moderator'];
|
||||
|
||||
const WEEKDAY_LABELS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
|
||||
const MONTH_LABELS = [
|
||||
'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
|
||||
@@ -1704,15 +1702,14 @@ export default function Kalender() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const { user } = useAuth();
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const notification = useNotification();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const canWriteEvents =
|
||||
user?.groups?.some((g) => WRITE_GROUPS_EVENTS.includes(g)) ?? false;
|
||||
const canWriteBookings =
|
||||
user?.groups?.some((g) => WRITE_GROUPS_BOOKINGS.includes(g)) ?? false;
|
||||
const canCreateBookings = !!user;
|
||||
const canWriteEvents = hasPermission('kalender:create_events');
|
||||
const canWriteBookings = hasPermission('kalender:edit_bookings');
|
||||
const canCreateBookings = hasPermission('kalender:create_bookings');
|
||||
|
||||
// ── Tab ─────────────────────────────────────────────────────────────────────
|
||||
const [activeTab, setActiveTab] = useState(() => {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { membersService } from '../services/members';
|
||||
import { atemschutzApi } from '../services/atemschutz';
|
||||
import { toGermanDate, fromGermanDate } from '../utils/dateInput';
|
||||
@@ -67,9 +68,8 @@ import { UntersuchungErgebnisLabel } from '../types/atemschutz.types';
|
||||
// Role helpers
|
||||
// ----------------------------------------------------------------
|
||||
function useCanWrite(): boolean {
|
||||
const { user } = useAuth();
|
||||
const groups: string[] = (user as any)?.groups ?? [];
|
||||
return groups.includes('dashboard_admin') || groups.includes('dashboard_kommando');
|
||||
const { hasPermission } = usePermissionContext();
|
||||
return hasPermission('mitglieder:edit');
|
||||
}
|
||||
|
||||
function useCurrentUserId(): string | undefined {
|
||||
|
||||
@@ -34,6 +34,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { membersService } from '../services/members';
|
||||
import {
|
||||
MemberListItem,
|
||||
@@ -51,9 +52,8 @@ import {
|
||||
// Helper: determine whether the current user can write member data
|
||||
// ----------------------------------------------------------------
|
||||
function useCanWrite(): boolean {
|
||||
const { user } = useAuth();
|
||||
const groups: string[] = (user as any)?.groups ?? [];
|
||||
return groups.includes('dashboard_admin') || groups.includes('dashboard_kommando');
|
||||
const { hasPermission } = usePermissionContext();
|
||||
return hasPermission('mitglieder:edit');
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
@@ -73,17 +73,17 @@ function useDebounce<T>(value: T, delay: number): T {
|
||||
// ----------------------------------------------------------------
|
||||
function Mitglieder() {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth(); const canWrite = useCanWrite();
|
||||
const { user } = useAuth();
|
||||
const canWrite = useCanWrite();
|
||||
const { hasPermission } = usePermissionContext();
|
||||
|
||||
// --- redirect non-admin/non-kommando users to their own profile ---
|
||||
// --- redirect non-privileged users to their own profile ---
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
const groups: string[] = (user as any)?.groups ?? [];
|
||||
const isAdmin = groups.includes('dashboard_admin') || groups.includes('dashboard_kommando');
|
||||
if (!isAdmin) {
|
||||
if (!hasPermission('mitglieder:edit')) {
|
||||
navigate(`/mitglieder/${(user as any).id}`, { replace: true });
|
||||
}
|
||||
}, [user, navigate]);
|
||||
}, [user, navigate, hasPermission]);
|
||||
|
||||
// --- data state ---
|
||||
const [members, setMembers] = useState<MemberListItem[]>([]);
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
Category as CategoryIcon,
|
||||
} from '@mui/icons-material';
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { eventsApi } from '../services/events';
|
||||
import type { VeranstaltungKategorie, GroupInfo } from '../types/events.types';
|
||||
@@ -298,10 +298,9 @@ function DeleteDialog({ open, kategorie, onClose, onDeleted }: DeleteDialogProps
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function VeranstaltungKategorien() {
|
||||
const { user } = useAuth();
|
||||
const { hasPermission } = usePermissionContext();
|
||||
|
||||
const canManage =
|
||||
user?.groups?.some((g) => ['dashboard_admin', 'dashboard_moderator'].includes(g)) ?? false;
|
||||
const canManage = hasPermission('kalender:manage_categories');
|
||||
|
||||
const [kategorien, setKategorien] = useState<VeranstaltungKategorie[]>([]);
|
||||
const [groups, setGroups] = useState<GroupInfo[]>([]);
|
||||
|
||||
@@ -52,7 +52,7 @@ import {
|
||||
import DashboardLayout from '../components/dashboard/DashboardLayout';
|
||||
import ChatAwareFab from '../components/shared/ChatAwareFab';
|
||||
import { toGermanDate, toGermanDateTime, fromGermanDate, fromGermanDateTime } from '../utils/dateInput';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { usePermissionContext } from '../contexts/PermissionContext';
|
||||
import { useNotification } from '../contexts/NotificationContext';
|
||||
import { eventsApi } from '../services/events';
|
||||
import type {
|
||||
@@ -1069,13 +1069,12 @@ function EventListView({ events, canWrite, onEdit, onCancel, onDelete }: ListVie
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export default function Veranstaltungen() {
|
||||
const { user } = useAuth();
|
||||
const { hasPermission } = usePermissionContext();
|
||||
const notification = useNotification();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const canWrite =
|
||||
user?.groups?.some((g) => ['dashboard_admin', 'dashboard_moderator'].includes(g)) ?? false;
|
||||
const canWrite = hasPermission('kalender:create_events');
|
||||
|
||||
const today = new Date();
|
||||
const [viewMonth, setViewMonth] = useState({ year: today.getFullYear(), month: today.getMonth() });
|
||||
|
||||
Reference in New Issue
Block a user