This commit is contained in:
natib21
2026-07-10 11:25:59 +00:00
parent 2696ca7498
commit e6e44e773b
1146 changed files with 200266 additions and 90527 deletions

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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;

View File

@@ -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,
};
};

View File

@@ -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;
};
}