TS issues #2

Merged
Brook-Tewabe-Yaltopia merged 1 commits from el-ui into prod 2026-06-11 10:50:01 +03:00
14 changed files with 50 additions and 32 deletions
Showing only changes of commit a65191024c - Show all commits

View File

@ -1,5 +1,3 @@
import { Navigate, useLocation } from "react-router-dom";
interface ProtectedRouteProps { interface ProtectedRouteProps {
children: React.ReactNode; children: React.ReactNode;
} }

View File

@ -16,5 +16,7 @@ export function useAdminRole() {
roleLabel: roleLabel(role), roleLabel: roleLabel(role),
hasPanelAccess: hasPanelAccess(role), hasPanelAccess: hasPanelAccess(role),
...permissions, ...permissions,
canAccessSystemMembers: permissions.canManageSystem,
canSendBroadcast: permissions.canSendNotifications,
}; };
} }

View File

@ -1,4 +1,4 @@
import React, { useState, type ComponentType, useEffect } from "react"; import { useState, type ComponentType, useEffect } from "react";
import { Outlet, Link, useLocation, useNavigate } from "react-router-dom"; import { Outlet, Link, useLocation, useNavigate } from "react-router-dom";
import { import {
LayoutDashboard, LayoutDashboard,

View File

@ -150,7 +150,7 @@ export default function AnalyticsStoragePage() {
fontWeight: "700", fontWeight: "700",
textTransform: "uppercase", textTransform: "uppercase",
}} }}
formatter={(value: number) => formatBytes(value)} formatter={(value) => formatBytes(Number(value ?? 0))}
/> />
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@ -150,7 +150,7 @@ export default function AnalyticsStoragePage() {
fontWeight: "700", fontWeight: "700",
textTransform: "uppercase", textTransform: "uppercase",
}} }}
formatter={(value: number) => formatBytes(value)} formatter={(value) => formatBytes(Number(value ?? 0))}
/> />
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@ -38,19 +38,19 @@ export default function AnalyticsUsersPage() {
{[ {[
{ {
label: "Total Population", label: "Total Population",
value: lastMetrics?.total || 0, value: lastMetrics?.users || 0,
icon: Users, icon: Users,
color: "text-blue-600 bg-blue-50 border-blue-100", color: "text-blue-600 bg-blue-50 border-blue-100",
}, },
{ {
label: "Privileged Access", label: "Active Users",
value: lastMetrics?.admins || 0, value: lastMetrics?.activeUsers || 0,
icon: ShieldCheck, icon: ShieldCheck,
color: "text-emerald-600 bg-emerald-50 border-emerald-100", color: "text-emerald-600 bg-emerald-50 border-emerald-100",
}, },
{ {
label: "Standard Users", label: "Inactive Users",
value: lastMetrics?.regular || 0, value: (lastMetrics?.users ?? 0) - (lastMetrics?.activeUsers ?? 0),
icon: UserCheck, icon: UserCheck,
color: "text-purple-600 bg-purple-50 border-purple-100", color: "text-purple-600 bg-purple-50 border-purple-100",
}, },
@ -126,21 +126,21 @@ export default function AnalyticsUsersPage() {
/> />
<Line <Line
type="monotone" type="monotone"
dataKey="total" dataKey="users"
stroke="#111827" stroke="#111827"
strokeWidth={2} strokeWidth={2}
dot={false} dot={false}
activeDot={{ r: 4, fill: "#111827", strokeWidth: 0 }} activeDot={{ r: 4, fill: "#111827", strokeWidth: 0 }}
name="Aggregate Population" name="Total Users"
/> />
<Line <Line
type="monotone" type="monotone"
dataKey="admins" dataKey="activeUsers"
stroke="#10B981" stroke="#10B981"
strokeWidth={1.5} strokeWidth={1.5}
strokeDasharray="4 4" strokeDasharray="4 4"
dot={false} dot={false}
name="Privileged" name="Active Users"
/> />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@ -1,6 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
@ -10,7 +10,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Megaphone, Plus, Edit, Trash2, Filter } from "lucide-react"; import { Plus, Edit, Trash2, Filter } from "lucide-react";
import { import {
announcementService, announcementService,
type Announcement, type Announcement,

View File

@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { import {
Search, Search,
Eye,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
Filter, Filter,

View File

@ -1,7 +1,7 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Shield, Ban, Mail, Globe, AlertTriangle } from "lucide-react"; import { Ban, Mail, Globe, AlertTriangle } from "lucide-react";
import { securityService } from "@/services"; import { securityService } from "@/services";
import type { SuspiciousIP, SuspiciousEmail } from "@/types/security.types"; import type { SuspiciousIP, SuspiciousEmail } from "@/types/security.types";

View File

@ -1,7 +1,6 @@
import { useState } from "react"; import { useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

View File

@ -1,7 +1,7 @@
import { useState } from "react"; import { useState } from "react";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Tabs, TabsContent } from "@/components/ui/tabs";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
@ -31,7 +31,6 @@ import {
Globe, Globe,
ArrowRight, ArrowRight,
Library, Library,
Settings2,
ChevronRight, ChevronRight,
} from "lucide-react"; } from "lucide-react";
import { faqService } from "@/services"; import { faqService } from "@/services";

View File

@ -23,16 +23,22 @@ import {
import { Search, UserPlus } from "lucide-react" import { Search, UserPlus } from "lucide-react"
import { systemMemberService } from "@/services" import { systemMemberService } from "@/services"
import { useAdminRole } from "@/hooks/use-admin-role" import { useAdminRole } from "@/hooks/use-admin-role"
import { AdminRole } from "@/lib/admin-roles" import { AdminRole, type AdminRoleValue } from "@/lib/admin-roles"
import { toast } from "sonner" import { toast } from "sonner"
export default function SystemMembersPage() { export default function SystemMembersPage() {
const { canAccessSystemMembers, canEdit } = useAdminRole() const { canAccessSystemMembers, canManageSystem } = useAdminRole()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [search, setSearch] = useState("") const [search, setSearch] = useState("")
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [form, setForm] = useState({ const [form, setForm] = useState<{
email: string
firstName: string
lastName: string
password: string
role: AdminRoleValue
}>({
email: "", email: "",
firstName: "", firstName: "",
lastName: "", lastName: "",
@ -85,7 +91,7 @@ export default function SystemMembersPage() {
areas; cannot manage this list). areas; cannot manage this list).
</p> </p>
</div> </div>
{canEdit && ( {canManageSystem && (
<Button <Button
className="rounded-none gap-2" className="rounded-none gap-2"
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
@ -243,7 +249,7 @@ export default function SystemMembersPage() {
<Select <Select
value={form.role} value={form.role}
onValueChange={(role) => onValueChange={(role) =>
setForm((f) => ({ ...f, role })) setForm((f) => ({ ...f, role: role as AdminRoleValue }))
} }
> >
<SelectTrigger className="rounded-none"> <SelectTrigger className="rounded-none">

View File

@ -1,6 +1,6 @@
import { useState, useMemo } from "react"; import { useState, useMemo } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
@ -12,21 +12,18 @@ import {
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { import {
Search, Search,
CheckCheck, CheckCheck,
Send, Send,
Plus,
BellRing, BellRing,
Mail, Mail,
MessageSquare, MessageSquare,
History, History,
Target, Target,
ArrowRight, ArrowRight,
ChevronRight,
Loader2, Loader2,
Calendar, Calendar,
} from "lucide-react"; } from "lucide-react";
@ -36,7 +33,6 @@ import type {
SendSmsNotificationRequest, SendSmsNotificationRequest,
SendEmailNotificationRequest, SendEmailNotificationRequest,
} from "@/services/notification.service"; } from "@/services/notification.service";
import { useAdminRole } from "@/hooks/use-admin-role";
import { toast } from "sonner"; import { toast } from "sonner";
import { format } from "date-fns"; import { format } from "date-fns";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@ -44,7 +40,6 @@ import { cn } from "@/lib/utils";
type Channel = "PUSH" | "SMS" | "EMAIL"; type Channel = "PUSH" | "SMS" | "EMAIL";
export default function NotificationsPage() { export default function NotificationsPage() {
const { canSendNotifications } = useAdminRole();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [searchQuery, setSearchQuery] = useState(""); const [searchQuery, setSearchQuery] = useState("");
const [activeChannel, setActiveChannel] = useState<Channel>("PUSH"); const [activeChannel, setActiveChannel] = useState<Channel>("PUSH");

View File

@ -46,6 +46,13 @@ export interface SendEmailNotificationRequest {
scheduledFor?: string; scheduledFor?: string;
} }
export interface SendBroadcastRequest {
title: string;
message: string;
audience: "all_end_users" | "system_users_only" | "everyone_with_access";
channels: ("push" | "sms" | "email")[];
}
class NotificationService { class NotificationService {
/** /**
* Get all notifications for current user (Paginated) * Get all notifications for current user (Paginated)
@ -87,6 +94,19 @@ class NotificationService {
await apiClient.post("/notifications/read-all"); await apiClient.post("/notifications/read-all");
} }
/**
* Send multi-channel broadcast (ADMIN only)
*/
async sendBroadcast(
data: SendBroadcastRequest,
): Promise<{ success: boolean }> {
const response = await apiClient.post<{ success: boolean }>(
"/admin/notifications/broadcast",
data,
);
return response.data;
}
/** /**
* Send push notification (ADMIN only) * Send push notification (ADMIN only)
*/ */