@arch-cadre/panel 1.0.6 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/actions/activity-log/index.cjs +1 -1
  2. package/dist/actions/activity-log/index.mjs +1 -1
  3. package/dist/actions/index.cjs +2 -2
  4. package/dist/actions/index.d.ts +2 -2
  5. package/dist/actions/index.mjs +2 -2
  6. package/dist/actions/profile.d.ts +1 -1
  7. package/dist/index.cjs +7 -7
  8. package/dist/index.mjs +7 -7
  9. package/dist/routes.cjs +10 -10
  10. package/dist/routes.mjs +10 -10
  11. package/dist/schema.cjs +1 -1
  12. package/dist/schema.d.ts +1 -1
  13. package/dist/schema.mjs +1 -1
  14. package/dist/ui/activity-log/components/ActivityStatsWidget.cjs +1 -1
  15. package/dist/ui/activity-log/components/ActivityStatsWidget.mjs +1 -1
  16. package/dist/ui/activity-log/components/RecentLogsWidget.cjs +1 -1
  17. package/dist/ui/activity-log/components/RecentLogsWidget.mjs +1 -1
  18. package/dist/ui/activity-log/pages/log-list.cjs +2 -2
  19. package/dist/ui/activity-log/pages/log-list.mjs +1 -1
  20. package/dist/ui/components/app-content.cjs +1 -1
  21. package/dist/ui/components/app-content.mjs +1 -1
  22. package/dist/ui/components/app-sidebar.cjs +3 -3
  23. package/dist/ui/components/app-sidebar.mjs +2 -2
  24. package/dist/ui/components/app-user.cjs +5 -5
  25. package/dist/ui/components/app-user.mjs +4 -4
  26. package/dist/ui/components/breadcrumb-slot.cjs +2 -2
  27. package/dist/ui/components/breadcrumb-slot.mjs +1 -1
  28. package/dist/ui/components/manager/module-card.cjs +3 -3
  29. package/dist/ui/components/manager/module-card.mjs +2 -2
  30. package/dist/ui/components/manager/module-list.cjs +3 -3
  31. package/dist/ui/components/manager/module-list.mjs +2 -2
  32. package/dist/ui/components/manager/module-upload.cjs +3 -3
  33. package/dist/ui/components/manager/module-upload.mjs +2 -2
  34. package/dist/ui/components/profile/components.cjs +7 -7
  35. package/dist/ui/components/profile/components.d.ts +1 -1
  36. package/dist/ui/components/profile/components.mjs +3 -3
  37. package/dist/ui/components/profile/link.cjs +2 -2
  38. package/dist/ui/components/profile/link.mjs +1 -1
  39. package/dist/ui/components/profile/page.cjs +2 -6
  40. package/dist/ui/components/profile/page.mjs +3 -10
  41. package/dist/ui/components/sidebar-slot.cjs +1 -1
  42. package/dist/ui/components/sidebar-slot.mjs +1 -1
  43. package/dist/ui/dashboard/page.cjs +3 -3
  44. package/dist/ui/dashboard/page.mjs +1 -1
  45. package/dist/ui/dashboard/widgets/WelcomeBackUserWidget.cjs +1 -0
  46. package/dist/ui/dashboard/widgets/WelcomeBackUserWidget.mjs +5 -4
  47. package/dist/ui/error.cjs +2 -2
  48. package/dist/ui/error.mjs +1 -1
  49. package/dist/ui/layout.cjs +3 -3
  50. package/dist/ui/layout.mjs +3 -3
  51. package/dist/ui/modules/docs/page.cjs +1 -1
  52. package/dist/ui/modules/docs/page.mjs +1 -1
  53. package/dist/ui/modules/page.cjs +3 -3
  54. package/dist/ui/modules/page.mjs +3 -3
  55. package/dist/ui/rbac/pages/rbac-admin.cjs +16 -16
  56. package/dist/ui/rbac/pages/rbac-admin.mjs +2 -2
  57. package/dist/ui/router.cjs +1 -1
  58. package/dist/ui/router.mjs +1 -1
  59. package/dist/ui/session-manager/components/sessions-list.cjs +7 -7
  60. package/dist/ui/session-manager/components/sessions-list.mjs +3 -3
  61. package/dist/ui/session-manager/pages/sessions-page.cjs +4 -4
  62. package/dist/ui/session-manager/pages/sessions-page.mjs +3 -3
  63. package/dist/ui/settings-page.cjs +3 -3
  64. package/dist/ui/settings-page.mjs +2 -2
  65. package/package.json +7 -6
  66. package/src/actions/actions.ts +17 -0
  67. package/src/actions/activity-log/index.ts +17 -0
  68. package/src/actions/index.ts +2 -0
  69. package/src/actions/manager.ts +168 -0
  70. package/src/actions/profile.ts +173 -0
  71. package/src/actions/rbac/index.ts +131 -0
  72. package/src/actions/session-manager/index.ts +87 -0
  73. package/src/actions/settings.ts +34 -0
  74. package/src/index.ts +135 -0
  75. package/src/intl.d.ts +9 -0
  76. package/src/navigation.ts +57 -0
  77. package/src/routes.ts +107 -0
  78. package/src/schema/activity-log.ts +16 -0
  79. package/src/schema.ts +1 -0
  80. package/src/types.ts +18 -0
  81. package/src/ui/activity-log/components/ActivityStatsWidget.tsx +37 -0
  82. package/src/ui/activity-log/components/RecentLogsWidget.tsx +74 -0
  83. package/src/ui/activity-log/pages/log-list.tsx +91 -0
  84. package/src/ui/components/app-content.tsx +51 -0
  85. package/src/ui/components/app-header.tsx +65 -0
  86. package/src/ui/components/app-sidebar.tsx +249 -0
  87. package/src/ui/components/app-user.tsx +126 -0
  88. package/src/ui/components/breadcrumb-slot.tsx +52 -0
  89. package/src/ui/components/manager/module-card.tsx +327 -0
  90. package/src/ui/components/manager/module-list.tsx +59 -0
  91. package/src/ui/components/manager/module-upload.tsx +84 -0
  92. package/src/ui/components/profile/components.tsx +311 -0
  93. package/src/ui/components/profile/link.tsx +36 -0
  94. package/src/ui/components/profile/page.tsx +45 -0
  95. package/src/ui/components/sidebar-slot.tsx +47 -0
  96. package/src/ui/dashboard/page.tsx +17 -0
  97. package/src/ui/dashboard/widgets/WelcomeBackUserWidget.tsx +47 -0
  98. package/src/ui/error.tsx +82 -0
  99. package/src/ui/layout.tsx +54 -0
  100. package/src/ui/modules/docs/page.tsx +105 -0
  101. package/src/ui/modules/page.tsx +30 -0
  102. package/src/ui/page.tsx +15 -0
  103. package/src/ui/rbac/pages/rbac-admin.tsx +551 -0
  104. package/src/ui/router.tsx +69 -0
  105. package/src/ui/session-manager/components/sessions-list.tsx +303 -0
  106. package/src/ui/session-manager/pages/sessions-page.tsx +22 -0
  107. package/src/ui/settings/page.tsx +73 -0
  108. package/src/ui/settings-page.tsx +97 -0
package/src/index.ts ADDED
@@ -0,0 +1,135 @@
1
+ import type { SystemEvent } from "@arch-cadre/core/server";
2
+ import { db, eventBus } from "@arch-cadre/core/server";
3
+ import type { IModule } from "@arch-cadre/modules";
4
+ import manifest from "../manifest.json";
5
+ import { navigation } from "./navigation.js";
6
+ import { apiRoutes, privateRoutes, publicRoutes } from "./routes.js";
7
+ import { activityLogsTable } from "./schema/activity-log.js";
8
+ import ActivityStatsWidget from "./ui/activity-log/components/ActivityStatsWidget.js";
9
+ import RecentLogsWidget from "./ui/activity-log/components/RecentLogsWidget.js";
10
+ import { UserDropdownLink } from "./ui/components/profile/link.js";
11
+ import WelcomeBackUserWidget from "./ui/dashboard/widgets/WelcomeBackUserWidget.js";
12
+
13
+ export interface ActivityCreatePayload {
14
+ action: string;
15
+ description: string;
16
+ userId?: string;
17
+ metadata?: any;
18
+ }
19
+
20
+ const kryoPanelModule: IModule = {
21
+ manifest,
22
+
23
+ init: async () => {
24
+ console.log("[ActivityLog] Registering core system listeners...");
25
+
26
+ // Helper to log any event
27
+ const logEvent = async (
28
+ event: SystemEvent<any>,
29
+ descriptionOverride?: string,
30
+ ) => {
31
+ try {
32
+ const userId = event.payload?.userId || undefined;
33
+ await db.insert(activityLogsTable).values({
34
+ userId,
35
+ action: event.type,
36
+ description: descriptionOverride || `System event: ${event.type}`,
37
+ metadata: event.payload,
38
+ });
39
+ } catch (error) {
40
+ console.error(
41
+ `[ActivityLog] Failed to save log for ${event.type}:`,
42
+ error,
43
+ );
44
+ }
45
+ };
46
+
47
+ // 1. Existing manual activity creation
48
+ eventBus.subscribe(
49
+ "activity.create",
50
+ "activity-log-manual",
51
+ async (event) => {
52
+ const { action, description, userId, metadata } = event.payload as any;
53
+ await db
54
+ .insert(activityLogsTable)
55
+ .values({ userId, action, description, metadata });
56
+ },
57
+ );
58
+
59
+ // 2. Auth Events
60
+ eventBus.subscribe("auth:login", "activity-log-auth", (e) =>
61
+ logEvent(e, `User logged in: ${(e.payload as any).email}`),
62
+ );
63
+ eventBus.subscribe("auth:signup", "activity-log-auth", (e) =>
64
+ logEvent(e, `New user registered: ${(e.payload as any).email}`),
65
+ );
66
+ eventBus.subscribe("auth:logout", "activity-log-auth", (e) =>
67
+ logEvent(e, `User logged out: ${(e.payload as any).email}`),
68
+ );
69
+
70
+ // 3. System Module Events
71
+ eventBus.subscribe("system:module:toggle", "activity-log-system", (e) =>
72
+ logEvent(
73
+ e,
74
+ `Module "${(e.payload as any).moduleId}" ${(e.payload as any).isEnabled ? "enabled" : "disabled"}`,
75
+ ),
76
+ );
77
+ eventBus.subscribe("system:module:upload", "activity-log-system", (e) =>
78
+ logEvent(e, `New module uploaded: "${(e.payload as any).moduleId}"`),
79
+ );
80
+ },
81
+
82
+ onDisable: async () => {
83
+ console.log("[ActivityLog] Unsubscribing listeners...");
84
+ eventBus.unsubscribe("activity.create", "activity-log-manual");
85
+ eventBus.unsubscribe("auth:login", "activity-log-auth");
86
+ eventBus.unsubscribe("auth:signup", "activity-log-auth");
87
+ eventBus.unsubscribe("auth:logout", "activity-log-auth");
88
+ eventBus.unsubscribe("system:module:toggle", "activity-log-system");
89
+ eventBus.unsubscribe("system:module:upload", "activity-log-system");
90
+ },
91
+
92
+ widgets: [
93
+ {
94
+ id: "welcome-back-user",
95
+ name: "Welcome Back User",
96
+ area: "dashboard-stats",
97
+ component: WelcomeBackUserWidget,
98
+ priority: 0,
99
+ },
100
+ {
101
+ id: "recent-activity",
102
+ name: "Recent Activity",
103
+ area: "dashboard-main",
104
+ component: RecentLogsWidget,
105
+ priority: 10,
106
+ },
107
+ {
108
+ id: "activity-stats",
109
+ name: "Activity Stats",
110
+ area: "dashboard-stats",
111
+ component: ActivityStatsWidget,
112
+ priority: 10,
113
+ },
114
+ ],
115
+
116
+ routes: {
117
+ public: publicRoutes,
118
+ private: privateRoutes,
119
+ api: apiRoutes,
120
+ },
121
+
122
+ navigation,
123
+
124
+ extensions: [
125
+ {
126
+ id: "user-settings-link",
127
+ targetModule: "panel",
128
+ point: "app-user:extra-link",
129
+ component: UserDropdownLink,
130
+ priority: 50,
131
+ },
132
+ ],
133
+ };
134
+
135
+ export default kryoPanelModule;
package/src/intl.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type messages from "../locales/en/global.json";
2
+
3
+ type JsonDataType = typeof messages;
4
+
5
+ declare module "@arch-cadre/intl" {
6
+ export interface IntlMessages extends JsonDataType {}
7
+ }
8
+
9
+ export {};
@@ -0,0 +1,57 @@
1
+ import { i18n } from "@arch-cadre/intl";
2
+ import type { ModuleNavigation } from "@arch-cadre/modules";
3
+
4
+ export const navigation: ModuleNavigation = {
5
+ admin: {
6
+ [i18n("General")]: [
7
+ {
8
+ id: "dashboard",
9
+ title: i18n("Dashboard"),
10
+ url: "/",
11
+ icon: "solar:widget-bold-duotone",
12
+ },
13
+ {
14
+ id: "settings",
15
+ title: i18n("Settings"),
16
+ url: "/settings",
17
+ icon: "solar:settings-bold-duotone",
18
+ },
19
+ ],
20
+ [i18n("System")]: [
21
+ {
22
+ id: "module-manager-nav",
23
+ title: i18n("Module Manager"),
24
+ url: "/modules",
25
+ icon: "solar:settings-bold-duotone",
26
+ // roles: ["admin"],
27
+ permissions: ["system:modules"],
28
+ },
29
+ {
30
+ id: "activity-logs-nav",
31
+ title: i18n("Activity Logs"),
32
+ url: "/activity-logs",
33
+ icon: "solar:condicioner-2-bold-duotone",
34
+ // roles: ["admin"],
35
+ permissions: ["system:activity-logs"],
36
+ },
37
+ {
38
+ id: "rbac-admin-nav",
39
+ title: i18n("Roles & Permissions"),
40
+ url: "/rbac",
41
+ icon: "solar:shield-keyhole-bold-duotone",
42
+ // roles: ["admin"],
43
+ permissions: ["system:rbac"],
44
+ },
45
+ ],
46
+ },
47
+ settings: {
48
+ [i18n("Panel Core")]: [
49
+ {
50
+ id: "kryo-config",
51
+ title: i18n("Panel Core Settings"),
52
+ url: "/settings/panel/config",
53
+ icon: "solar:tuning-bold-duotone",
54
+ },
55
+ ],
56
+ },
57
+ };
package/src/routes.ts ADDED
@@ -0,0 +1,107 @@
1
+ import type {
2
+ ApiRouteDefinition,
3
+ PrivateRouteDefinition,
4
+ PublicRouteDefinition,
5
+ } from "@arch-cadre/modules";
6
+ import { getKryoPathPrefix, getModuleStatus } from "@arch-cadre/modules/server";
7
+ import dynamic from "next/dynamic";
8
+ import { NextResponse } from "next/server";
9
+ import ActivityLogPage from "./ui/activity-log/pages/log-list.js";
10
+ import ProfileSettingsPage from "./ui/components/profile/page.js";
11
+ import ModuleDocsPage from "./ui/modules/docs/page.js";
12
+ import ModuleAdminPage from "./ui/modules/page.js";
13
+ import RbacAdminPage from "./ui/rbac/pages/rbac-admin.js";
14
+ import SessionsSettingsPage from "./ui/session-manager/pages/sessions-page.js";
15
+
16
+ const KryoLayout = dynamic(() => import("./ui/layout.js"));
17
+ const DashboardPage = dynamic(() => import("./ui/dashboard/page.js"));
18
+ const KryoPanelSettingsPage = dynamic(() => import("./ui/settings-page.js"));
19
+ const GlobalSettingsPage = dynamic(() => import("./ui/settings/page.js"));
20
+
21
+ export const publicRoutes: PublicRouteDefinition[] = [];
22
+
23
+ export const privateRoutes: PrivateRouteDefinition[] = [
24
+ {
25
+ path: "/", // Mapuje na prefiks (np. /kryo)
26
+ component: DashboardPage,
27
+ layout: KryoLayout,
28
+ auth: true,
29
+ roles: ["admin", "user"],
30
+ },
31
+ {
32
+ path: "/profile",
33
+ component: ProfileSettingsPage,
34
+ auth: true,
35
+ roles: ["admin", "user"],
36
+ },
37
+ {
38
+ id: "user-sessions",
39
+ path: "/sessions",
40
+ component: SessionsSettingsPage,
41
+ layout: KryoLayout,
42
+ auth: true,
43
+ roles: ["admin", "user"],
44
+ },
45
+ {
46
+ id: "activity-logs",
47
+ path: "/activity-logs",
48
+ component: ActivityLogPage,
49
+ layout: KryoLayout,
50
+ auth: true,
51
+ permissions: ["system:activity-logs"],
52
+ },
53
+ {
54
+ id: "rbac-admin",
55
+ path: "/rbac",
56
+ component: RbacAdminPage,
57
+ layout: KryoLayout,
58
+ auth: true,
59
+ roles: ["admin", "user"],
60
+ permissions: ["system:rbac"],
61
+ },
62
+ {
63
+ path: "/settings",
64
+ component: GlobalSettingsPage,
65
+ layout: KryoLayout,
66
+ auth: true,
67
+ roles: ["admin", "user"],
68
+ },
69
+ {
70
+ path: "/settings/panel/config",
71
+ component: KryoPanelSettingsPage,
72
+ layout: KryoLayout,
73
+ auth: true,
74
+ roles: ["admin", "user"],
75
+ },
76
+ {
77
+ id: "module-admin",
78
+ path: "/modules",
79
+ component: ModuleAdminPage,
80
+ layout: KryoLayout,
81
+ auth: true,
82
+ roles: ["admin", "user"],
83
+ permissions: ["system:modules"],
84
+ },
85
+ {
86
+ id: "module-docs",
87
+ path: "/modules/:id/docs",
88
+ component: ModuleDocsPage,
89
+ layout: KryoLayout,
90
+ auth: true,
91
+ roles: ["admin", "user"],
92
+ },
93
+ ];
94
+
95
+ export const apiRoutes: ApiRouteDefinition[] = [
96
+ {
97
+ id: "module-status-api",
98
+ path: "/api/system/modules/:id/status",
99
+ auth: true,
100
+ roles: ["admin", "user"],
101
+ permissions: ["system:modules"],
102
+ handler: async (_req, { params }) => {
103
+ const status = await getModuleStatus(params.id);
104
+ return NextResponse.json(status);
105
+ },
106
+ },
107
+ ];
@@ -0,0 +1,16 @@
1
+ import { userTable } from "@arch-cadre/core/server";
2
+ import { jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
+
4
+ export const activityLogsTable = pgTable("activity_logs", {
5
+ id: text("id")
6
+ .$defaultFn(() => crypto.randomUUID())
7
+ .notNull()
8
+ .primaryKey(),
9
+ userId: text("user_id").references(() => userTable.id, {
10
+ onDelete: "set null",
11
+ }),
12
+ action: text("action").notNull(), // np. "blog.post.created"
13
+ description: text("description").notNull(), // np. "Użytkownik dodał komentarz"
14
+ metadata: jsonb("metadata"), // dodatkowe dane (np. ID posta)
15
+ createdAt: timestamp("created_at", { precision: 3 }).notNull().defaultNow(),
16
+ });
package/src/schema.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./schema/activity-log.js";
package/src/types.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type {
2
+ SidebarGroupType,
3
+ SidebarMenuItemType,
4
+ SidebarMenuType,
5
+ } from "@arch-cadre/modules";
6
+
7
+ /**
8
+ *Action result.
9
+ */
10
+ export interface ActionResult<T = any> {
11
+ success?: boolean;
12
+ error?: boolean;
13
+ message?: string;
14
+ errors?: {
15
+ [K in keyof T]?: string[];
16
+ };
17
+ inputs?: T;
18
+ }
@@ -0,0 +1,37 @@
1
+ import { db } from "@arch-cadre/core/server";
2
+ import { getTranslation } from "@arch-cadre/intl/server";
3
+ import { Card, CardContent, CardHeader, CardTitle } from "@arch-cadre/ui";
4
+ import { sql } from "drizzle-orm";
5
+ import { Activity } from "lucide-react";
6
+ import * as React from "react";
7
+ import { activityLogsTable } from "../../../schema/activity-log.js";
8
+
9
+ export default async function ActivityStatsWidget() {
10
+ const { t } = await getTranslation();
11
+
12
+ const [result] = await db
13
+ .select({
14
+ count: sql<number>`count(*)`,
15
+ })
16
+ .from(activityLogsTable)
17
+ .where(sql`DATE(${activityLogsTable.createdAt}) = CURRENT_DATE`);
18
+
19
+ const count = result?.count || 0;
20
+
21
+ return (
22
+ <Card>
23
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
24
+ <CardTitle className="text-sm font-medium">
25
+ {t("Events Today")}
26
+ </CardTitle>
27
+ <Activity className="h-4 w-4 text-muted-foreground" />
28
+ </CardHeader>
29
+ <CardContent>
30
+ <div className="text-2xl font-bold">{count}</div>
31
+ <p className="text-xs text-muted-foreground">
32
+ {t("System activities recorded today")}
33
+ </p>
34
+ </CardContent>
35
+ </Card>
36
+ );
37
+ }
@@ -0,0 +1,74 @@
1
+ import { db, userTable } from "@arch-cadre/core/server";
2
+ import { getTranslation } from "@arch-cadre/intl/server";
3
+ import {
4
+ Avatar,
5
+ AvatarFallback,
6
+ AvatarImage,
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "@arch-cadre/ui";
13
+ import { desc, eq } from "drizzle-orm";
14
+ import * as React from "react";
15
+ import { activityLogsTable } from "../../../schema/activity-log.js";
16
+
17
+ export default async function RecentLogsWidget() {
18
+ const { t } = await getTranslation();
19
+
20
+ const logs = await db
21
+ .select({
22
+ id: activityLogsTable.id,
23
+ action: activityLogsTable.action,
24
+ description: activityLogsTable.description,
25
+ createdAt: activityLogsTable.createdAt,
26
+ user: {
27
+ name: userTable.name,
28
+ image: userTable.image,
29
+ },
30
+ })
31
+ .from(activityLogsTable)
32
+ .leftJoin(userTable, eq(activityLogsTable.userId, userTable.id))
33
+ .orderBy(desc(activityLogsTable.createdAt))
34
+ .limit(5);
35
+
36
+ return (
37
+ <Card>
38
+ <CardHeader>
39
+ <CardTitle>{t("Recent Activity")}</CardTitle>
40
+ <CardDescription>
41
+ {t("The latest events from your system.")}
42
+ </CardDescription>
43
+ </CardHeader>
44
+ <CardContent className="grid gap-8">
45
+ {logs.map((log) => (
46
+ <div key={log.id} className="flex items-center gap-4">
47
+ <Avatar className="h-9 w-9">
48
+ <AvatarImage src={log.user?.image || ""} alt="Avatar" />
49
+ <AvatarFallback>
50
+ {log.user?.name?.substring(0, 2).toUpperCase() || "SY"}
51
+ </AvatarFallback>
52
+ </Avatar>
53
+ <div className="grid gap-1">
54
+ <p className="text-sm font-medium leading-none">
55
+ {log.description}
56
+ </p>
57
+ <p className="text-sm text-muted-foreground">
58
+ {new Date(log.createdAt).toLocaleString()}
59
+ </p>
60
+ </div>
61
+ <div className="ml-auto font-medium text-xs text-muted-foreground">
62
+ {log.action}
63
+ </div>
64
+ </div>
65
+ ))}
66
+ {logs.length === 0 && (
67
+ <div className="text-center py-4 text-muted-foreground">
68
+ {t("No recent activity found.")}
69
+ </div>
70
+ )}
71
+ </CardContent>
72
+ </Card>
73
+ );
74
+ }
@@ -0,0 +1,91 @@
1
+ import { getTranslation } from "@arch-cadre/intl/server";
2
+ import { Icon } from "@arch-cadre/ui";
3
+ import {
4
+ Table,
5
+ TableBody,
6
+ TableCell,
7
+ TableHead,
8
+ TableHeader,
9
+ TableRow,
10
+ } from "@arch-cadre/ui/components/table";
11
+ import * as React from "react";
12
+ import { getActivityLogs } from "../../../actions/activity-log/index.js";
13
+
14
+ export default async function ActivityLogPage() {
15
+ const logs = await getActivityLogs();
16
+
17
+ const { t } = await getTranslation();
18
+
19
+ return (
20
+ <div className="space-y-6">
21
+ <div className="space-y-1">
22
+ <h2 className="text-3xl font-bold tracking-tight">{t("Activity")}</h2>
23
+ <p className="text-muted-foreground text-sm">
24
+ {t("Real-time audit log of all system events.")}
25
+ </p>
26
+ </div>
27
+
28
+ <div className="border rounded-xl bg-card overflow-hidden">
29
+ <Table>
30
+ <TableHeader className="bg-muted/50">
31
+ <TableRow>
32
+ <TableHead>{t("Event")}</TableHead>
33
+ <TableHead>{t("User")}</TableHead>
34
+ <TableHead>{t("Details")}</TableHead>
35
+ <TableHead className="text-right">{t("Time")}</TableHead>
36
+ </TableRow>
37
+ </TableHeader>
38
+ <TableBody>
39
+ {logs.map(({ log, user }) => (
40
+ <TableRow key={log.id}>
41
+ <TableCell>
42
+ <div className="flex items-center gap-2">
43
+ <div
44
+ className={`p-1.5 rounded-md ${log.action.includes("created") ? "bg-green-500/10 text-green-600" : "bg-blue-500/10 text-blue-600"}`}
45
+ >
46
+ <Icon
47
+ icon={
48
+ log.action.includes("comment")
49
+ ? "solar:chat-line-broken"
50
+ : "solar:file-text-broken"
51
+ }
52
+ className="size-4"
53
+ />
54
+ </div>
55
+ <span className="font-mono text-xs font-bold">
56
+ {log.action}
57
+ </span>
58
+ </div>
59
+ </TableCell>
60
+ <TableCell>
61
+ <div className="flex flex-col">
62
+ <span className="text-sm font-medium">
63
+ {user?.name || t("System")}
64
+ </span>
65
+ <span className="text-[10px] text-muted-foreground">
66
+ {user?.email || t("automated-task")}
67
+ </span>
68
+ </div>
69
+ </TableCell>
70
+ <TableCell className="text-sm">{log.description}</TableCell>
71
+ <TableCell className="text-right text-xs text-muted-foreground">
72
+ {new Date(log.createdAt).toLocaleString()}
73
+ </TableCell>
74
+ </TableRow>
75
+ ))}
76
+ {logs.length === 0 && (
77
+ <TableRow>
78
+ <TableCell
79
+ colSpan={4}
80
+ className="text-center py-20 text-muted-foreground italic"
81
+ >
82
+ {t("No activity recorded yet.")}
83
+ </TableCell>
84
+ </TableRow>
85
+ )}
86
+ </TableBody>
87
+ </Table>
88
+ </div>
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import { ScrollArea } from "@arch-cadre/ui/components/scroll-area";
4
+ import * as React from "react";
5
+ import { type ReactNode, useEffect, useState } from "react";
6
+ import { AppHeader } from "./app-header.js";
7
+
8
+ export function AppContent({
9
+ children,
10
+ breadcrumbs,
11
+ }: {
12
+ children?: ReactNode;
13
+ breadcrumbs?: ReactNode;
14
+ }) {
15
+ const [isScrolled, setIsScrolled] = useState(false);
16
+
17
+ useEffect(() => {
18
+ const onScroll = () => setIsScrolled(window.scrollY > 64);
19
+ onScroll();
20
+ window.addEventListener("scroll", onScroll, { passive: true });
21
+ return () => window.removeEventListener("scroll", onScroll);
22
+ }, []);
23
+
24
+ // handle scroll to add shadow to header/footer
25
+ const setScrolled = (e: React.UIEvent<HTMLDivElement>) => {
26
+ const scrollTop = e.currentTarget.scrollTop;
27
+
28
+ if (scrollTop > 20) {
29
+ setIsScrolled(true);
30
+ } else {
31
+ setIsScrolled(false);
32
+ }
33
+ };
34
+
35
+ return (
36
+ <main className="bg-sidebar h-svh overflow-hidden lg:p-2 w-full">
37
+ <div className="lg:border squircle overflow-hidden flex flex-col items-center justify-start h-full w-full bg-background">
38
+ <AppHeader isScrolled={isScrolled} breadcrumbs={breadcrumbs} />
39
+ <ScrollArea
40
+ onScroll={(e) => setScrolled(e)}
41
+ className="h-[calc(100svh-56px-var(--header-height))] w-full flex-1"
42
+ orientation="vertical"
43
+ >
44
+ <div className="min-h-[calc(100svh-56px-var(--header-height))] p-4">
45
+ {children}
46
+ </div>
47
+ </ScrollArea>
48
+ </div>
49
+ </main>
50
+ );
51
+ }
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import { LanguageSwitcher } from "@arch-cadre/ui";
4
+ import { Logo } from "@arch-cadre/ui/brand/logo";
5
+ import { Button } from "@arch-cadre/ui/components/button";
6
+ import { Separator } from "@arch-cadre/ui/components/separator";
7
+ import { useSidebar } from "@arch-cadre/ui/components/sidebar";
8
+ import { cn } from "@arch-cadre/ui/lib/utils";
9
+ import { Icon } from "@iconify/react";
10
+ import Link from "next/link";
11
+ import type { ReactNode } from "react";
12
+ import * as React from "react";
13
+
14
+ export function AppHeader({
15
+ breadcrumbs,
16
+ isScrolled,
17
+ }: {
18
+ breadcrumbs?: ReactNode;
19
+ isScrolled: boolean;
20
+ }) {
21
+ const { toggleSidebar } = useSidebar();
22
+ return (
23
+ <header
24
+ className={cn(
25
+ "sticky top-0 z-50",
26
+ "flex w-full items-center h-(--header-height)",
27
+ "transition-all",
28
+ isScrolled ? "bg-background shadow-xs " : "",
29
+ )}
30
+ >
31
+ <div className="flex h-[var(--header-height)] w-full items-center">
32
+ <div className="-mr-px flex shrink-0 items-center gap-2 px-4">
33
+ <Button
34
+ className="h-8 w-8 cursor-pointer"
35
+ variant="ghost"
36
+ size="icon"
37
+ onClick={toggleSidebar}
38
+ type="button"
39
+ >
40
+ <Icon icon="solar:sidebar-minimalistic-broken" />
41
+ </Button>
42
+ </div>
43
+
44
+ <Separator orientation="vertical" className="mr-2 h-4" />
45
+
46
+ <div className="flex flex-1 items-center gap-2 px-4">
47
+ <Link
48
+ href="/app"
49
+ className="md:hidden items-center gap-x-2 mr-4 md:mr-0 flex"
50
+ >
51
+ <Logo className="h-auto w-[134px]" aria-hidden={true} />
52
+ </Link>
53
+
54
+ {/* BreadCrumbs Slot */}
55
+ {breadcrumbs}
56
+
57
+ <div className="ml-auto w-auto flex gap-x-3">
58
+ {/* <NotificationMenu /> */}
59
+ <LanguageSwitcher />
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </header>
64
+ );
65
+ }