mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 21:08:12 +00:00
fix ui
This commit is contained in:
@@ -1,44 +1,44 @@
|
||||
import { Activity } from "@/super-admin/hooks/useDashboardData";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface ActivityTimelineProps {
|
||||
activities: Activity[];
|
||||
}
|
||||
|
||||
export const ActivityTimeline = ({ activities }: ActivityTimelineProps) => {
|
||||
if (!activities || activities.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground text-center py-4">
|
||||
{t("organization.noRecentActivities")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative pl-6">
|
||||
<div className="absolute left-2 top-2 bottom-2 w-0.5 bg-purple-500" />
|
||||
<ul className="space-y-6">
|
||||
{activities.map((activity) => (
|
||||
<li key={activity.id} className="relative pl-4">
|
||||
<div className="absolute left-0 top-1 w-3 h-3 bg-purple-500 rounded-full" />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatDistanceToNow(new Date(activity.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{activity.description}
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
By{" "}
|
||||
<span className="font-medium text-purple-600">
|
||||
{activity.user}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { Activity } from "@/super-admin/hooks/useDashboardData";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface ActivityTimelineProps {
|
||||
activities: Activity[];
|
||||
}
|
||||
|
||||
export const ActivityTimeline = ({ activities }: ActivityTimelineProps) => {
|
||||
if (!activities || activities.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground text-center py-4">
|
||||
{t("organization.noRecentActivities")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative pl-6">
|
||||
<div className="absolute left-2 top-2 bottom-2 w-0.5 bg-purple-500" />
|
||||
<ul className="space-y-6">
|
||||
{activities.map((activity) => (
|
||||
<li key={activity.id} className="relative pl-4">
|
||||
<div className="absolute left-0 top-1 w-3 h-3 bg-purple-500 rounded-full" />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{formatDistanceToNow(new Date(activity.timestamp), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</div>
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
||||
{activity.description}
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
By{" "}
|
||||
<span className="font-medium text-purple-600">
|
||||
{activity.user}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useTenantConfig } from "@/layout/components/TenantConfig";
|
||||
|
||||
export const HeaderBar = () => {
|
||||
const { config: tenantConfig } = useTenantConfig();
|
||||
return (
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-gray-100">
|
||||
{tenantConfig.appName} Dashboard
|
||||
</h1>
|
||||
<button className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-md text-sm">
|
||||
+ Add Organization
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { useTenantConfig } from "@/layout/components/TenantConfig";
|
||||
|
||||
export const HeaderBar = () => {
|
||||
const { config: tenantConfig } = useTenantConfig();
|
||||
return (
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-gray-100">
|
||||
{tenantConfig.appName} Dashboard
|
||||
</h1>
|
||||
<button className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-md text-sm">
|
||||
+ Add Organization
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,72 +1,72 @@
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/shared/common/ui/table";
|
||||
import { Badge } from "@/shared/common/ui/badge";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { OrganizationDto } from "@/shared/dto/organization/organizationDto";
|
||||
import { t } from "i18next";
|
||||
interface OrgTableProps {
|
||||
organizations: OrganizationDto[];
|
||||
}
|
||||
|
||||
export const OrgTable = ({ organizations }: OrgTableProps) => {
|
||||
if (!organizations || organizations.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground text-center py-4 border dark:border-gray-700 rounded-xl p-6 dark:bg-gray-800">
|
||||
No organizations found
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl overflow-hidden shadow-sm border bg-white dark:bg-gray-800 dark:border-gray-700">
|
||||
<Table>
|
||||
<TableHeader className="bg-white dark:bg-gray-800 text-muted-foreground">
|
||||
<TableRow>
|
||||
<TableHead className="px-6 py-3">{t("organization.organizationName")}</TableHead>
|
||||
{/* <TableHead className="px-6 py-3">{t("organization.key")}</TableHead> */}
|
||||
<TableHead className="px-6 py-3">{t("organization.createdOn")}</TableHead>
|
||||
<TableHead className="px-6 py-3">{t("dashboard.Status")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{organizations.map((org, index) => (
|
||||
<TableRow
|
||||
key={org.id}
|
||||
className={index % 2 ? "bg-purple-50/20 dark:bg-gray-700/40" : "bg-white dark:bg-gray-800"}
|
||||
>
|
||||
<TableCell className="px-6 py-3 font-medium">
|
||||
{org.name?.en || "N/A"}
|
||||
</TableCell>
|
||||
{/* <TableCell className="px-6 py-3">{org.key || "N/A"}</TableCell> */}
|
||||
<TableCell className="px-6 py-3">
|
||||
{org.createdAt
|
||||
? formatDistanceToNow(new Date(org.createdAt), {
|
||||
addSuffix: true,
|
||||
})
|
||||
: "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="px-6 py-3">
|
||||
<Badge
|
||||
variant={org.status === "Active" ? "default" : "outline"}
|
||||
className={
|
||||
org.status === "Active"
|
||||
? "bg-primary-100 text-primary-800 hover:bg-primary-100 dark:bg-primary-900/50 dark:text-primary-300"
|
||||
: "bg-gray-100 text-gray-800 hover:bg-gray-100 dark:bg-gray-700 dark:text-gray-300"
|
||||
}
|
||||
>
|
||||
{org.status === "Active" ? t("statusBar.activate") : t("statusBar.deactivate")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/shared/common/ui/table";
|
||||
import { Badge } from "@/shared/common/ui/badge";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { OrganizationDto } from "@/shared/dto/organization/organizationDto";
|
||||
import { t } from "i18next";
|
||||
interface OrgTableProps {
|
||||
organizations: OrganizationDto[];
|
||||
}
|
||||
|
||||
export const OrgTable = ({ organizations }: OrgTableProps) => {
|
||||
if (!organizations || organizations.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground text-center py-4 border dark:border-gray-700 rounded-xl p-6 dark:bg-gray-800">
|
||||
No organizations found
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-xl overflow-hidden shadow-sm border bg-white dark:bg-gray-800 dark:border-gray-700">
|
||||
<Table>
|
||||
<TableHeader className="bg-white dark:bg-gray-800 text-muted-foreground">
|
||||
<TableRow>
|
||||
<TableHead className="px-6 py-3">{t("organization.organizationName")}</TableHead>
|
||||
{/* <TableHead className="px-6 py-3">{t("organization.key")}</TableHead> */}
|
||||
<TableHead className="px-6 py-3">{t("organization.createdOn")}</TableHead>
|
||||
<TableHead className="px-6 py-3">{t("dashboard.Status")}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{organizations.map((org, index) => (
|
||||
<TableRow
|
||||
key={org.id}
|
||||
className={index % 2 ? "bg-purple-50/20 dark:bg-gray-700/40" : "bg-white dark:bg-gray-800"}
|
||||
>
|
||||
<TableCell className="px-6 py-3 font-medium">
|
||||
{org.name?.en || "N/A"}
|
||||
</TableCell>
|
||||
{/* <TableCell className="px-6 py-3">{org.key || "N/A"}</TableCell> */}
|
||||
<TableCell className="px-6 py-3">
|
||||
{org.createdAt
|
||||
? formatDistanceToNow(new Date(org.createdAt), {
|
||||
addSuffix: true,
|
||||
})
|
||||
: "N/A"}
|
||||
</TableCell>
|
||||
<TableCell className="px-6 py-3">
|
||||
<Badge
|
||||
variant={org.status === "Active" ? "default" : "outline"}
|
||||
className={
|
||||
org.status === "Active"
|
||||
? "bg-primary-100 text-primary-800 hover:bg-primary-100 dark:bg-primary-900/50 dark:text-primary-300"
|
||||
: "bg-gray-100 text-gray-800 hover:bg-gray-100 dark:bg-gray-700 dark:text-gray-300"
|
||||
}
|
||||
>
|
||||
{org.status === "Active" ? t("statusBar.activate") : t("statusBar.deactivate")}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import { Card, CardContent } from "@/shared/common/ui/card";
|
||||
import { cn } from "@/super-admin/lib/utils";
|
||||
import { LucideIcon, Building2, Clock, User } from "lucide-react";
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: number;
|
||||
indicator?: string;
|
||||
color: string;
|
||||
icon: "building" | "clock" | "user";
|
||||
variant?: "primary" | "default";
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const iconMap: Record<string, LucideIcon> = {
|
||||
building: Building2,
|
||||
clock: Clock,
|
||||
user: User,
|
||||
};
|
||||
|
||||
export const StatCard: React.FC<StatCardProps> = ({
|
||||
title,
|
||||
value,
|
||||
indicator,
|
||||
color,
|
||||
icon,
|
||||
variant = "default",
|
||||
onClick,
|
||||
}) => {
|
||||
const Icon = iconMap[icon];
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"p-4 rounded-xl shadow-sm transition-colors",
|
||||
variant === "primary" ? "bg-purple-600 text-white" : "bg-white dark:bg-gray-800 dark:border-gray-700",
|
||||
onClick && "cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="text-sm font-medium opacity-80">{title}</div>
|
||||
<Icon className="w-5 h-5 opacity-60" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold leading-snug">{value}</div>
|
||||
{indicator && (
|
||||
<div className={cn("text-xs mt-1", color)}>{indicator}</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
import { Card, CardContent } from "@/shared/common/ui/card";
|
||||
import { cn } from "@/super-admin/lib/utils";
|
||||
import { LucideIcon, Building2, Clock, User } from "lucide-react";
|
||||
|
||||
interface StatCardProps {
|
||||
title: string;
|
||||
value: number;
|
||||
indicator?: string;
|
||||
color: string;
|
||||
icon: "building" | "clock" | "user";
|
||||
variant?: "primary" | "default";
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const iconMap: Record<string, LucideIcon> = {
|
||||
building: Building2,
|
||||
clock: Clock,
|
||||
user: User,
|
||||
};
|
||||
|
||||
export const StatCard: React.FC<StatCardProps> = ({
|
||||
title,
|
||||
value,
|
||||
indicator,
|
||||
color,
|
||||
icon,
|
||||
variant = "default",
|
||||
onClick,
|
||||
}) => {
|
||||
const Icon = iconMap[icon];
|
||||
|
||||
return (
|
||||
<Card
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"p-4 rounded-xl shadow-sm transition-colors",
|
||||
variant === "primary" ? "bg-purple-600 text-white" : "bg-white dark:bg-gray-800 dark:border-gray-700",
|
||||
onClick && "cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
)}
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="text-sm font-medium opacity-80">{title}</div>
|
||||
<Icon className="w-5 h-5 opacity-60" />
|
||||
</div>
|
||||
<div className="text-3xl font-bold leading-snug">{value}</div>
|
||||
{indicator && (
|
||||
<div className={cn("text-xs mt-1", color)}>{indicator}</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface DashboardState {
|
||||
totalOrgs: number;
|
||||
pendingRequests: number;
|
||||
orgAdmins: number;
|
||||
}
|
||||
|
||||
const initialState: DashboardState = {
|
||||
totalOrgs: 0,
|
||||
pendingRequests: 0,
|
||||
orgAdmins: 0,
|
||||
};
|
||||
|
||||
const dashboardSlice = createSlice({
|
||||
name: "dashboard",
|
||||
initialState,
|
||||
reducers: {
|
||||
setStats(state, action: PayloadAction<DashboardState>) {
|
||||
return { ...state, ...action.payload };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setStats } = dashboardSlice.actions;
|
||||
export default dashboardSlice.reducer;
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
interface DashboardState {
|
||||
totalOrgs: number;
|
||||
pendingRequests: number;
|
||||
orgAdmins: number;
|
||||
}
|
||||
|
||||
const initialState: DashboardState = {
|
||||
totalOrgs: 0,
|
||||
pendingRequests: 0,
|
||||
orgAdmins: 0,
|
||||
};
|
||||
|
||||
const dashboardSlice = createSlice({
|
||||
name: "dashboard",
|
||||
initialState,
|
||||
reducers: {
|
||||
setStats(state, action: PayloadAction<DashboardState>) {
|
||||
return { ...state, ...action.payload };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setStats } = dashboardSlice.actions;
|
||||
export default dashboardSlice.reducer;
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { getDashboardStats } from "@/super-admin/services/api/dashboardApi";
|
||||
import { DashboardStatsDto } from "../types/dashboardStatsDto";
|
||||
|
||||
export const useDashboardStats = () => {
|
||||
const {
|
||||
data: stats,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["dashboardStats"],
|
||||
queryFn: async (): Promise<DashboardStatsDto> => {
|
||||
try {
|
||||
return await getDashboardStats();
|
||||
} catch (err) {
|
||||
toast("Failed to fetch dashboard stats. Using mock data.");
|
||||
return err instanceof Error
|
||||
? Promise.reject(new Error(err.message))
|
||||
: Promise.reject(new Error("Failed to fetch dashboard stats."));
|
||||
// Fallback to mock data if API call fails
|
||||
}
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
return {
|
||||
stats,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import { getDashboardStats } from "@/super-admin/services/api/dashboardApi";
|
||||
import { DashboardStatsDto } from "../types/dashboardStatsDto";
|
||||
|
||||
export const useDashboardStats = () => {
|
||||
const {
|
||||
data: stats,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
} = useQuery({
|
||||
queryKey: ["dashboardStats"],
|
||||
queryFn: async (): Promise<DashboardStatsDto> => {
|
||||
try {
|
||||
return await getDashboardStats();
|
||||
} catch (err) {
|
||||
toast("Failed to fetch dashboard stats. Using mock data.");
|
||||
return err instanceof Error
|
||||
? Promise.reject(new Error(err.message))
|
||||
: Promise.reject(new Error("Failed to fetch dashboard stats."));
|
||||
// Fallback to mock data if API call fails
|
||||
}
|
||||
},
|
||||
staleTime: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
return {
|
||||
stats,
|
||||
isLoading,
|
||||
isError,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export interface DashboardStatsDto {
|
||||
totalOrgs: number;
|
||||
pendingRequests: number;
|
||||
orgAdmins: number;
|
||||
percentChanges: {
|
||||
orgs: number;
|
||||
pending: number;
|
||||
admins: number;
|
||||
};
|
||||
}
|
||||
export interface DashboardStatsDto {
|
||||
totalOrgs: number;
|
||||
pendingRequests: number;
|
||||
orgAdmins: number;
|
||||
percentChanges: {
|
||||
orgs: number;
|
||||
pending: number;
|
||||
admins: number;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user