@goplusvn/core 0.1.59 → 0.1.60
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.
- package/PLATFORM.md +18 -0
- package/bin/goerp-init.mjs +141 -0
- package/package.json +4 -4
- package/src/cron/db-cron-manager.ts +3 -3
- package/src/cron/index.ts +2 -2
- package/src/{infrastructure/cron/cron-manager.ts → cron/simple-cron-job.ts} +1 -1
- package/src/infrastructure/__tests__/architecture-verification.spec.ts +13 -97
- package/src/infrastructure/index.ts +4 -7
- package/src/ui/management/index.ts +3 -2
- package/templates/starter-app/.dockerignore +41 -0
- package/templates/starter-app/.env.example +25 -0
- package/templates/starter-app/AGENTS.md +52 -0
- package/templates/starter-app/Dockerfile +74 -0
- package/templates/starter-app/README.md +141 -0
- package/templates/starter-app/gitignore +9 -0
- package/templates/starter-app/next.config.mjs +50 -0
- package/templates/starter-app/package.json +55 -0
- package/templates/starter-app/postcss.config.mjs +5 -0
- package/templates/starter-app/prisma/migrations/20260801000000_init/migration.sql +504 -0
- package/templates/starter-app/prisma/migrations/20260801091814_goerp_audit_logs_0001_init/migration.sql +33 -0
- package/templates/starter-app/prisma/migrations/20260801091815_goerp_background_tasks_0001_init/migration.sql +23 -0
- package/templates/starter-app/prisma/migrations/20260801091816_goerp_error_logs_0001_init/migration.sql +32 -0
- package/templates/starter-app/prisma/migrations/20260801091817_goerp_notifications_0001_init/migration.sql +59 -0
- package/templates/starter-app/prisma/migrations/20260801091818_goerp_system_jobs_0001_init/migration.sql +47 -0
- package/templates/starter-app/prisma/schema/auth.prisma +87 -0
- package/templates/starter-app/prisma/schema/domain.prisma +24 -0
- package/templates/starter-app/prisma/schema/goerp-audit-logs.prisma +23 -0
- package/templates/starter-app/prisma/schema/goerp-background-tasks.prisma +25 -0
- package/templates/starter-app/prisma/schema/goerp-error-logs.prisma +31 -0
- package/templates/starter-app/prisma/schema/goerp-notifications.prisma +49 -0
- package/templates/starter-app/prisma/schema/goerp-system-jobs.prisma +45 -0
- package/templates/starter-app/prisma/schema/organization.prisma +31 -0
- package/templates/starter-app/prisma/schema/rbac.prisma +100 -0
- package/templates/starter-app/prisma/schema/schema.prisma +8 -0
- package/templates/starter-app/prisma/schema/system.prisma +22 -0
- package/templates/starter-app/prisma/seed.ts +127 -0
- package/templates/starter-app/prisma.config.ts +20 -0
- package/templates/starter-app/public/.gitkeep +2 -0
- package/templates/starter-app/scripts/rbac-sync.ts +235 -0
- package/templates/starter-app/src/__tests__/architecture.test.ts +151 -0
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/audit/page.tsx +24 -0
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/error-logs/page.tsx +21 -0
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/jobs/page.tsx +18 -0
- package/templates/starter-app/src/app/[lang]/(main)/admin/system/settings/page.tsx +22 -0
- package/templates/starter-app/src/app/[lang]/(main)/crud/[entity]/page.tsx +41 -0
- package/templates/starter-app/src/app/[lang]/(main)/layout.tsx +13 -0
- package/templates/starter-app/src/app/[lang]/(main)/notifications/page.tsx +13 -0
- package/templates/starter-app/src/app/[lang]/(main)/page.tsx +130 -0
- package/templates/starter-app/src/app/[lang]/(main)/roles/[id]/edit/page.tsx +60 -0
- package/templates/starter-app/src/app/[lang]/(main)/roles/new/page.tsx +41 -0
- package/templates/starter-app/src/app/[lang]/(main)/roles/page.tsx +48 -0
- package/templates/starter-app/src/app/[lang]/(main)/tasks/page.tsx +13 -0
- package/templates/starter-app/src/app/[lang]/(plain)/layout.tsx +10 -0
- package/templates/starter-app/src/app/[lang]/(plain)/sign-in/page.tsx +39 -0
- package/templates/starter-app/src/app/[lang]/layout.tsx +21 -0
- package/templates/starter-app/src/app/api/admin/system/audit/route.ts +60 -0
- package/templates/starter-app/src/app/api/admin/system/jobs/[name]/history/route.ts +39 -0
- package/templates/starter-app/src/app/api/admin/system/jobs/route.ts +96 -0
- package/templates/starter-app/src/app/api/admin/system/settings/all/route.ts +17 -0
- package/templates/starter-app/src/app/api/admin/system/settings/create/route.ts +19 -0
- package/templates/starter-app/src/app/api/admin/system/settings/delete/route.ts +20 -0
- package/templates/starter-app/src/app/api/admin/system/settings/route.ts +49 -0
- package/templates/starter-app/src/app/api/admin/system/settings/toggle-status/route.ts +28 -0
- package/templates/starter-app/src/app/api/admin/system/settings/update/route.ts +24 -0
- package/templates/starter-app/src/app/api/admin/system/settings/update-full/route.ts +23 -0
- package/templates/starter-app/src/app/api/better-auth/[...all]/route.ts +13 -0
- package/templates/starter-app/src/app/api/crud/[entity]/[id]/route.ts +13 -0
- package/templates/starter-app/src/app/api/crud/[entity]/route.ts +14 -0
- package/templates/starter-app/src/app/api/error-logs/[id]/route.ts +23 -0
- package/templates/starter-app/src/app/api/error-logs/route.ts +124 -0
- package/templates/starter-app/src/app/api/files/[...key]/route.ts +25 -0
- package/templates/starter-app/src/app/api/notifications/read/route.ts +29 -0
- package/templates/starter-app/src/app/api/notifications/route.ts +26 -0
- package/templates/starter-app/src/app/api/notifications/unread-count/route.ts +14 -0
- package/templates/starter-app/src/app/api/rbac/permissions-version/route.ts +11 -0
- package/templates/starter-app/src/app/api/roles/[id]/route.ts +14 -0
- package/templates/starter-app/src/app/api/roles/route.ts +18 -0
- package/templates/starter-app/src/app/api/tasks/[id]/download/route.ts +52 -0
- package/templates/starter-app/src/app/api/tasks/route.ts +37 -0
- package/templates/starter-app/src/app/api/upload/route.ts +15 -0
- package/templates/starter-app/src/app/globals.css +15 -0
- package/templates/starter-app/src/app/layout.tsx +16 -0
- package/templates/starter-app/src/app/page.tsx +8 -0
- package/templates/starter-app/src/components/layout/main-layout-wrapper.tsx +30 -0
- package/templates/starter-app/src/configs/entities/department.config.ts +24 -0
- package/templates/starter-app/src/configs/entities/index.ts +13 -0
- package/templates/starter-app/src/configs/i18n.ts +12 -0
- package/templates/starter-app/src/configs/permissions/index.ts +45 -0
- package/templates/starter-app/src/configs/permissions/master-data.permissions.ts +31 -0
- package/templates/starter-app/src/configs/permissions/system.permissions.ts +131 -0
- package/templates/starter-app/src/configs/permissions/types.ts +63 -0
- package/templates/starter-app/src/configs/tenant.ts +18 -0
- package/templates/starter-app/src/data/dictionary.ts +8 -0
- package/templates/starter-app/src/data/navigations.ts +61 -0
- package/templates/starter-app/src/instrumentation.ts +105 -0
- package/templates/starter-app/src/lib/api-handler.ts +157 -0
- package/templates/starter-app/src/lib/auth-client.ts +57 -0
- package/templates/starter-app/src/lib/auth.ts +62 -0
- package/templates/starter-app/src/lib/better-auth.ts +107 -0
- package/templates/starter-app/src/lib/branch-scope.ts +53 -0
- package/templates/starter-app/src/lib/cron/db-cron-manager.ts +42 -0
- package/templates/starter-app/src/lib/crud/index.ts +13 -0
- package/templates/starter-app/src/lib/errors/app-error.ts +2 -0
- package/templates/starter-app/src/lib/errors/error-handler.ts +5 -0
- package/templates/starter-app/src/lib/errors/log-server-error.ts +15 -0
- package/templates/starter-app/src/lib/errors/server-error.ts +11 -0
- package/templates/starter-app/src/lib/logger.ts +30 -0
- package/templates/starter-app/src/lib/page-guard.ts +35 -0
- package/templates/starter-app/src/lib/prisma.ts +80 -0
- package/templates/starter-app/src/lib/rbac/access.ts +87 -0
- package/templates/starter-app/src/lib/storage.ts +28 -0
- package/templates/starter-app/src/providers/index.tsx +54 -0
- package/templates/starter-app/src/providers/mode-provider.tsx +31 -0
- package/templates/starter-app/src/providers/theme-provider.tsx +21 -0
- package/templates/starter-app/src/proxy.ts +45 -0
- package/templates/starter-app/src/server/services/notification-service.ts +31 -0
- package/templates/starter-app/src/server/services/system-config-service.ts +163 -0
- package/templates/starter-app/src/server/tasks/handlers/export-departments.ts +58 -0
- package/templates/starter-app/src/server/tasks/index.ts +16 -0
- package/templates/starter-app/src/server/tasks/task-runner.ts +40 -0
- package/templates/starter-app/src/types/session.ts +29 -0
- package/templates/starter-app/tsconfig.json +47 -0
- package/templates/starter-app/vitest.config.ts +17 -0
- package/src/infrastructure/cron/index.ts +0 -6
- package/src/infrastructure/event-bus/event-bus.ts +0 -145
- package/src/infrastructure/event-bus/index.ts +0 -2
- package/src/infrastructure/event-bus/types.ts +0 -22
- package/src/infrastructure/lock/decorators.ts +0 -67
- package/src/infrastructure/lock/index.ts +0 -2
- package/src/infrastructure/lock/lock-manager.ts +0 -33
- package/src/plugin/apps-registry.ts +0 -97
- package/src/plugin/index.ts +0 -5
- package/src/plugin/types.ts +0 -41
- package/src/ui/management/audit-log-page.tsx +0 -14
- package/src/ui/management/job-management.tsx +0 -308
- package/src/workflow/activity-timeline.tsx +0 -412
- package/src/workflow/approval-workflow.tsx +0 -31
- package/src/workflow/index.ts +0 -2
- /package/src/{infrastructure/cron → cron}/types.ts +0 -0
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { format } from "date-fns";
|
|
5
|
-
import { vi } from "date-fns/locale"; // Note: This might need i18n handling if we support EN
|
|
6
|
-
import {
|
|
7
|
-
CheckCircle2,
|
|
8
|
-
Circle,
|
|
9
|
-
Clock,
|
|
10
|
-
XCircle,
|
|
11
|
-
ArrowRight,
|
|
12
|
-
ArrowDown,
|
|
13
|
-
} from "lucide-react";
|
|
14
|
-
import {
|
|
15
|
-
Timeline,
|
|
16
|
-
TimelineContent,
|
|
17
|
-
TimelineDot,
|
|
18
|
-
TimelineHeading,
|
|
19
|
-
TimelineItem,
|
|
20
|
-
TimelineLine,
|
|
21
|
-
Badge,
|
|
22
|
-
AvatarStack,
|
|
23
|
-
DynamicIcon,
|
|
24
|
-
} from "../ui";
|
|
25
|
-
import { cn } from "../utils";
|
|
26
|
-
import type { DynamicIconNameType } from "../types";
|
|
27
|
-
|
|
28
|
-
export interface ActivityItem {
|
|
29
|
-
id: string;
|
|
30
|
-
title: string;
|
|
31
|
-
description?: string;
|
|
32
|
-
date?: Date | string;
|
|
33
|
-
status?: "completed" | "current" | "upcoming" | "error";
|
|
34
|
-
badgeText?: string; // Text to show in the badge (e.g. "Opened", "Resolved")
|
|
35
|
-
iconName?: DynamicIconNameType;
|
|
36
|
-
fill?: string;
|
|
37
|
-
assignedMembers?: Array<{
|
|
38
|
-
name: string;
|
|
39
|
-
avatar?: string;
|
|
40
|
-
href?: string;
|
|
41
|
-
}>;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface SharedActivityTimelineProps {
|
|
45
|
-
items: ActivityItem[];
|
|
46
|
-
className?: string;
|
|
47
|
-
orientation?: "vertical" | "horizontal";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function RenderAvatarStack({
|
|
51
|
-
assignedMembers,
|
|
52
|
-
}: {
|
|
53
|
-
assignedMembers: ActivityItem["assignedMembers"];
|
|
54
|
-
}) {
|
|
55
|
-
if (assignedMembers && assignedMembers.length > 0) {
|
|
56
|
-
return (
|
|
57
|
-
<AvatarStack
|
|
58
|
-
avatars={assignedMembers.map((member) => ({
|
|
59
|
-
src: member.avatar,
|
|
60
|
-
alt: member.name,
|
|
61
|
-
href: member.href || "#",
|
|
62
|
-
}))}
|
|
63
|
-
size="sm"
|
|
64
|
-
className="mt-2"
|
|
65
|
-
/>
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Horizontal Step Component - Clear distinction: completed (active), current (highlighted), upcoming (inactive)
|
|
73
|
-
function HorizontalStep({
|
|
74
|
-
item,
|
|
75
|
-
isLast,
|
|
76
|
-
stepNumber,
|
|
77
|
-
}: {
|
|
78
|
-
item: ActivityItem;
|
|
79
|
-
isLast: boolean;
|
|
80
|
-
stepNumber: number;
|
|
81
|
-
}) {
|
|
82
|
-
const getStepIcon = () => {
|
|
83
|
-
if (item.status === "completed") {
|
|
84
|
-
return (
|
|
85
|
-
<CheckCircle2 className="h-4 w-4 text-green-600 dark:text-green-400" />
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
if (item.status === "error") {
|
|
89
|
-
return <XCircle className="h-4 w-4 text-red-600 dark:text-red-400" />;
|
|
90
|
-
}
|
|
91
|
-
if (item.status === "current") {
|
|
92
|
-
return <Clock className="h-4 w-4 text-blue-600 dark:text-blue-400" />;
|
|
93
|
-
}
|
|
94
|
-
return <Circle className="h-4 w-4 text-muted-foreground opacity-50" />;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const isCurrent = item.status === "current";
|
|
98
|
-
const isCompleted = item.status === "completed";
|
|
99
|
-
const isUpcoming = item.status === "upcoming" || !item.status;
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<div className="flex items-center gap-2 flex-1 relative min-w-0 shrink-0">
|
|
103
|
-
{/* Step Wrapper - Only wrap content, not arrow */}
|
|
104
|
-
<div
|
|
105
|
-
className={cn(
|
|
106
|
-
"flex items-center gap-1.5 flex-1 relative z-10 transition-all shrink-0",
|
|
107
|
-
isCurrent &&
|
|
108
|
-
"bg-blue-50 dark:bg-blue-950/30 border-2 border-blue-500 dark:border-blue-400 rounded-md px-1.5 py-1 shadow-sm",
|
|
109
|
-
)}
|
|
110
|
-
>
|
|
111
|
-
{/* Step Number */}
|
|
112
|
-
<div
|
|
113
|
-
className={cn(
|
|
114
|
-
"shrink-0 w-4 h-4 rounded-full flex items-center justify-center text-[10px] font-bold transition-all",
|
|
115
|
-
isCompleted && "bg-green-500 dark:bg-green-400 text-white",
|
|
116
|
-
isCurrent && "bg-blue-500 dark:bg-blue-400 text-white",
|
|
117
|
-
isUpcoming && "bg-muted text-muted-foreground opacity-50",
|
|
118
|
-
)}
|
|
119
|
-
>
|
|
120
|
-
{stepNumber}
|
|
121
|
-
</div>
|
|
122
|
-
|
|
123
|
-
{/* Step Icon */}
|
|
124
|
-
<div
|
|
125
|
-
className={cn(
|
|
126
|
-
"shrink-0 transition-all",
|
|
127
|
-
isCompleted && "text-green-600 dark:text-green-400",
|
|
128
|
-
isCurrent && "text-blue-600 dark:text-blue-400",
|
|
129
|
-
isUpcoming && "text-muted-foreground opacity-50",
|
|
130
|
-
)}
|
|
131
|
-
>
|
|
132
|
-
{item.iconName ? (
|
|
133
|
-
<div
|
|
134
|
-
className={cn(
|
|
135
|
-
"rounded-full p-0.5 transition-all",
|
|
136
|
-
isCurrent && "scale-105",
|
|
137
|
-
)}
|
|
138
|
-
style={
|
|
139
|
-
item.fill
|
|
140
|
-
? { backgroundColor: item.fill, color: "white" }
|
|
141
|
-
: undefined
|
|
142
|
-
}
|
|
143
|
-
>
|
|
144
|
-
<DynamicIcon name={item.iconName} className="size-3" />
|
|
145
|
-
</div>
|
|
146
|
-
) : (
|
|
147
|
-
<div className="scale-90">{getStepIcon()}</div>
|
|
148
|
-
)}
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
{/* Step Content - Horizontal layout */}
|
|
152
|
-
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
|
153
|
-
<div className="min-w-0 flex-1">
|
|
154
|
-
<div className="flex items-center gap-1.5 flex-wrap">
|
|
155
|
-
<p
|
|
156
|
-
className={cn(
|
|
157
|
-
"text-xs font-medium whitespace-nowrap transition-all leading-tight",
|
|
158
|
-
isCompleted &&
|
|
159
|
-
"text-green-700 dark:text-green-300 font-semibold",
|
|
160
|
-
isCurrent && "text-blue-700 dark:text-blue-300 font-semibold",
|
|
161
|
-
isUpcoming && "text-muted-foreground opacity-60",
|
|
162
|
-
)}
|
|
163
|
-
>
|
|
164
|
-
{item.title}
|
|
165
|
-
</p>
|
|
166
|
-
{item.badgeText && (
|
|
167
|
-
<Badge
|
|
168
|
-
variant={
|
|
169
|
-
isCurrent ? "default" : isCompleted ? "outline" : "outline"
|
|
170
|
-
}
|
|
171
|
-
className={cn(
|
|
172
|
-
"text-[10px] px-1 py-0 h-3.5 transition-all leading-none",
|
|
173
|
-
isCompleted &&
|
|
174
|
-
"border-green-500 dark:border-green-400 text-green-700 dark:text-green-300",
|
|
175
|
-
isCurrent &&
|
|
176
|
-
"bg-blue-600 dark:bg-blue-500 text-white border-blue-600 dark:border-blue-500",
|
|
177
|
-
isUpcoming &&
|
|
178
|
-
"border-muted-foreground/30 text-muted-foreground opacity-50",
|
|
179
|
-
)}
|
|
180
|
-
>
|
|
181
|
-
{item.badgeText}
|
|
182
|
-
</Badge>
|
|
183
|
-
)}
|
|
184
|
-
</div>
|
|
185
|
-
{item.description && (
|
|
186
|
-
<p
|
|
187
|
-
className={cn(
|
|
188
|
-
"text-[10px] mt-0 line-clamp-1 transition-colors leading-tight",
|
|
189
|
-
isCompleted && "text-green-600/80 dark:text-green-400/80",
|
|
190
|
-
isCurrent && "text-blue-600/80 dark:text-blue-400/80",
|
|
191
|
-
isUpcoming && "text-muted-foreground opacity-50",
|
|
192
|
-
)}
|
|
193
|
-
>
|
|
194
|
-
{item.description}
|
|
195
|
-
</p>
|
|
196
|
-
)}
|
|
197
|
-
</div>
|
|
198
|
-
</div>
|
|
199
|
-
</div>
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function SharedActivityTimeline({
|
|
205
|
-
items,
|
|
206
|
-
className,
|
|
207
|
-
orientation = "vertical",
|
|
208
|
-
}: SharedActivityTimelineProps) {
|
|
209
|
-
// Horizontal layout - Compact
|
|
210
|
-
if (orientation === "horizontal") {
|
|
211
|
-
return (
|
|
212
|
-
<div className={cn("flex items-center gap-0.5", className)}>
|
|
213
|
-
{items.map((item, index) => (
|
|
214
|
-
<React.Fragment key={item.id}>
|
|
215
|
-
<HorizontalStep
|
|
216
|
-
item={item}
|
|
217
|
-
isLast={index === items.length - 1}
|
|
218
|
-
stepNumber={index + 1}
|
|
219
|
-
/>
|
|
220
|
-
{/* Arrow between steps */}
|
|
221
|
-
{index < items.length - 1 && (
|
|
222
|
-
<div className="flex items-center justify-center shrink-0 w-3">
|
|
223
|
-
<ArrowRight
|
|
224
|
-
className={cn(
|
|
225
|
-
"h-3.5 w-3.5 transition-colors",
|
|
226
|
-
item.status === "completed"
|
|
227
|
-
? "text-green-600 dark:text-green-400"
|
|
228
|
-
: "text-muted-foreground opacity-50",
|
|
229
|
-
)}
|
|
230
|
-
strokeWidth={2.5}
|
|
231
|
-
/>
|
|
232
|
-
</div>
|
|
233
|
-
)}
|
|
234
|
-
</React.Fragment>
|
|
235
|
-
))}
|
|
236
|
-
</div>
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Vertical layout (default)
|
|
241
|
-
return (
|
|
242
|
-
<Timeline className={cn("pl-2", className)}>
|
|
243
|
-
{items.map((item, index) => {
|
|
244
|
-
const isLast = index === items.length - 1;
|
|
245
|
-
|
|
246
|
-
// Map status to TimelineDot props
|
|
247
|
-
let dotStatus: "done" | "current" | "error" | undefined = undefined;
|
|
248
|
-
let dotClass = "";
|
|
249
|
-
|
|
250
|
-
if (item.status === "completed") {
|
|
251
|
-
dotStatus = "done";
|
|
252
|
-
} else if (item.status === "error") {
|
|
253
|
-
dotStatus = "error";
|
|
254
|
-
} else if (item.status === "current") {
|
|
255
|
-
dotStatus = "current";
|
|
256
|
-
} else {
|
|
257
|
-
// upcoming
|
|
258
|
-
dotClass =
|
|
259
|
-
"bg-muted text-muted-foreground border-muted-foreground/30";
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const isCurrent = item.status === "current";
|
|
263
|
-
const isCompleted = item.status === "completed";
|
|
264
|
-
const isUpcoming = item.status === "upcoming" || !item.status;
|
|
265
|
-
|
|
266
|
-
return (
|
|
267
|
-
<TimelineItem
|
|
268
|
-
key={item.id}
|
|
269
|
-
status={item.status === "completed" ? "done" : "default"}
|
|
270
|
-
className={cn(
|
|
271
|
-
isCurrent &&
|
|
272
|
-
"bg-blue-50 dark:bg-blue-950/30 border-2 border-blue-500 dark:border-blue-400 rounded-lg px-3 py-2 -mx-1 shadow-sm",
|
|
273
|
-
)}
|
|
274
|
-
>
|
|
275
|
-
<div className="flex items-center gap-1.5">
|
|
276
|
-
{/* Step Number */}
|
|
277
|
-
<div
|
|
278
|
-
className={cn(
|
|
279
|
-
"shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold transition-all",
|
|
280
|
-
isCompleted && "bg-green-500 dark:bg-green-400 text-white",
|
|
281
|
-
isCurrent && "bg-blue-500 dark:bg-blue-400 text-white",
|
|
282
|
-
isUpcoming && "bg-muted text-muted-foreground opacity-50",
|
|
283
|
-
)}
|
|
284
|
-
>
|
|
285
|
-
{index + 1}
|
|
286
|
-
</div>
|
|
287
|
-
<TimelineHeading
|
|
288
|
-
className={cn(
|
|
289
|
-
"text-sm font-medium transition-all flex-1 leading-tight",
|
|
290
|
-
isCompleted &&
|
|
291
|
-
"text-green-700 dark:text-green-300 font-semibold",
|
|
292
|
-
isCurrent && "text-blue-700 dark:text-blue-300 font-semibold",
|
|
293
|
-
isUpcoming && "text-muted-foreground opacity-60",
|
|
294
|
-
)}
|
|
295
|
-
>
|
|
296
|
-
{item.title}
|
|
297
|
-
</TimelineHeading>
|
|
298
|
-
</div>
|
|
299
|
-
{item.iconName ? (
|
|
300
|
-
<TimelineDot
|
|
301
|
-
customIconName={item.iconName}
|
|
302
|
-
customStatusName={item.status || "default"}
|
|
303
|
-
className={cn(
|
|
304
|
-
dotClass,
|
|
305
|
-
isCompleted &&
|
|
306
|
-
"bg-green-100 dark:bg-green-900/30 border-green-500 dark:border-green-400",
|
|
307
|
-
isCurrent &&
|
|
308
|
-
"ring-2 ring-blue-400 dark:ring-blue-500 ring-offset-2 ring-offset-background",
|
|
309
|
-
isUpcoming && "opacity-50",
|
|
310
|
-
)}
|
|
311
|
-
style={
|
|
312
|
-
item.fill
|
|
313
|
-
? { backgroundColor: item.fill, color: "white" }
|
|
314
|
-
: undefined
|
|
315
|
-
}
|
|
316
|
-
iconClassName={cn(
|
|
317
|
-
"size-4 text-background p-0.5 transition-all",
|
|
318
|
-
isCurrent && "scale-105",
|
|
319
|
-
)}
|
|
320
|
-
/>
|
|
321
|
-
) : (
|
|
322
|
-
<TimelineDot
|
|
323
|
-
status={dotStatus}
|
|
324
|
-
className={cn(
|
|
325
|
-
dotClass,
|
|
326
|
-
isCompleted &&
|
|
327
|
-
"bg-green-100 dark:bg-green-900/30 border-green-500 dark:border-green-400",
|
|
328
|
-
isCurrent &&
|
|
329
|
-
"ring-2 ring-blue-400 dark:ring-blue-500 ring-offset-2 ring-offset-background",
|
|
330
|
-
isUpcoming && "opacity-50",
|
|
331
|
-
)}
|
|
332
|
-
/>
|
|
333
|
-
)}
|
|
334
|
-
{/* Arrow outside of wrapper */}
|
|
335
|
-
{!isLast && (
|
|
336
|
-
<div className="relative col-start-2 col-end-3 z-20">
|
|
337
|
-
<TimelineLine
|
|
338
|
-
done={isCompleted}
|
|
339
|
-
className={cn(!isCompleted && "opacity-30")}
|
|
340
|
-
/>
|
|
341
|
-
<ArrowDown
|
|
342
|
-
className={cn(
|
|
343
|
-
"absolute left-1/2 -translate-x-1/2 bottom-0 translate-y-1/2 h-3.5 w-3.5 z-20 transition-colors bg-background rounded-full p-0.5",
|
|
344
|
-
isCompleted
|
|
345
|
-
? "text-green-600 dark:text-green-400"
|
|
346
|
-
: "text-muted-foreground opacity-50",
|
|
347
|
-
)}
|
|
348
|
-
strokeWidth={2.5}
|
|
349
|
-
/>
|
|
350
|
-
</div>
|
|
351
|
-
)}
|
|
352
|
-
<TimelineContent className={cn("space-y-1 pb-2 transition-colors")}>
|
|
353
|
-
<div
|
|
354
|
-
className={cn(
|
|
355
|
-
"flex items-center text-sm transition-colors",
|
|
356
|
-
isCompleted && "text-green-600/80 dark:text-green-400/80",
|
|
357
|
-
isCurrent && "text-blue-600/80 dark:text-blue-400/80",
|
|
358
|
-
isUpcoming && "text-muted-foreground opacity-50",
|
|
359
|
-
)}
|
|
360
|
-
>
|
|
361
|
-
{item.date && (
|
|
362
|
-
<span>
|
|
363
|
-
{typeof item.date === "string"
|
|
364
|
-
? item.date
|
|
365
|
-
: format(new Date(item.date), "dd/MM/yyyy HH:mm", {
|
|
366
|
-
locale: vi,
|
|
367
|
-
})}
|
|
368
|
-
</span>
|
|
369
|
-
)}
|
|
370
|
-
{item.badgeText && (
|
|
371
|
-
<Badge
|
|
372
|
-
variant={
|
|
373
|
-
isCurrent
|
|
374
|
-
? "default"
|
|
375
|
-
: isCompleted
|
|
376
|
-
? "outline"
|
|
377
|
-
: "outline"
|
|
378
|
-
}
|
|
379
|
-
className={cn(
|
|
380
|
-
"ms-1.5 text-[10px] px-1 py-0 h-3.5 transition-all leading-none",
|
|
381
|
-
isCompleted &&
|
|
382
|
-
"border-green-500 dark:border-green-400 text-green-700 dark:text-green-300",
|
|
383
|
-
isCurrent &&
|
|
384
|
-
"bg-blue-600 dark:bg-blue-500 text-white border-blue-600 dark:border-blue-500",
|
|
385
|
-
isUpcoming &&
|
|
386
|
-
"border-muted-foreground/30 text-muted-foreground opacity-50",
|
|
387
|
-
)}
|
|
388
|
-
>
|
|
389
|
-
{item.badgeText}
|
|
390
|
-
</Badge>
|
|
391
|
-
)}
|
|
392
|
-
</div>
|
|
393
|
-
{item.description && (
|
|
394
|
-
<p
|
|
395
|
-
className={cn(
|
|
396
|
-
"text-sm transition-colors leading-tight",
|
|
397
|
-
isCompleted && "text-green-600/80 dark:text-green-400/80",
|
|
398
|
-
isCurrent && "text-blue-600/80 dark:text-blue-400/80",
|
|
399
|
-
isUpcoming && "text-muted-foreground opacity-50",
|
|
400
|
-
)}
|
|
401
|
-
>
|
|
402
|
-
{item.description}
|
|
403
|
-
</p>
|
|
404
|
-
)}
|
|
405
|
-
<RenderAvatarStack assignedMembers={item.assignedMembers} />
|
|
406
|
-
</TimelineContent>
|
|
407
|
-
</TimelineItem>
|
|
408
|
-
);
|
|
409
|
-
})}
|
|
410
|
-
</Timeline>
|
|
411
|
-
);
|
|
412
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
|
|
5
|
-
import { cn } from "../utils";
|
|
6
|
-
import { SharedActivityTimeline } from "./activity-timeline";
|
|
7
|
-
import type { ActivityItem } from "./activity-timeline";
|
|
8
|
-
|
|
9
|
-
export type ApprovalStepStatus = ActivityItem["status"];
|
|
10
|
-
|
|
11
|
-
export interface ApprovalStep extends ActivityItem {
|
|
12
|
-
status?: ApprovalStepStatus;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ApprovalWorkflowProps {
|
|
16
|
-
steps: ApprovalStep[];
|
|
17
|
-
orientation?: "horizontal" | "vertical";
|
|
18
|
-
className?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function ApprovalWorkflow({
|
|
22
|
-
steps,
|
|
23
|
-
orientation = "horizontal",
|
|
24
|
-
className,
|
|
25
|
-
}: ApprovalWorkflowProps) {
|
|
26
|
-
return (
|
|
27
|
-
<div className={cn("w-full", className)}>
|
|
28
|
-
<SharedActivityTimeline items={steps} orientation={orientation} />
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
}
|
package/src/workflow/index.ts
DELETED
|
File without changes
|