@goplusvn/core 0.1.22 → 0.1.24
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/CHANGELOG.md +33 -0
- package/package.json +1 -1
- package/src/rbac/components/roles/role-card.tsx +25 -24
- package/src/rbac/components/roles/role-toolbar.tsx +70 -84
- package/src/rbac/index.ts +8 -0
- package/src/rbac/lib/fetch-error.ts +26 -0
- package/src/rbac/pages/action-list-page.tsx +581 -19
- package/src/rbac/pages/index.ts +13 -0
- package/src/rbac/pages/resource-list-page.tsx +1415 -19
- package/src/rbac/pages/role-form-page.tsx +704 -0
- package/src/rbac/pages/role-list-page.tsx +14 -12
- package/src/ui/forms/multi-select.tsx +10 -9
- package/src/ui/layout/horizontal-layout.tsx +1 -1
- package/src/ui/primitives/sidebar.tsx +3 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useMemo, useCallback, useState } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { Loader2, UserX, KeyRound } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
7
8
|
Badge,
|
|
9
|
+
StatusIndicator,
|
|
8
10
|
AlertDialog,
|
|
9
11
|
AlertDialogAction,
|
|
10
12
|
AlertDialogCancel,
|
|
@@ -59,6 +61,8 @@ export interface RoleListPageProps {
|
|
|
59
61
|
description: string | null;
|
|
60
62
|
isDefault?: boolean;
|
|
61
63
|
}>;
|
|
64
|
+
/** Nút Import app-specific (vd ImportDialog) — render trong toolbar khi có quyền import. */
|
|
65
|
+
importSlot?: ReactNode;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
export function RoleListPage({
|
|
@@ -68,6 +72,7 @@ export function RoleListPage({
|
|
|
68
72
|
initialData,
|
|
69
73
|
resources,
|
|
70
74
|
actions,
|
|
75
|
+
importSlot,
|
|
71
76
|
}: RoleListPageProps) {
|
|
72
77
|
const canCreate = permissions.create;
|
|
73
78
|
const canUpdate = permissions.update;
|
|
@@ -199,7 +204,9 @@ export function RoleListPage({
|
|
|
199
204
|
|
|
200
205
|
if (error) {
|
|
201
206
|
return (
|
|
202
|
-
<div className="text-
|
|
207
|
+
<div className="text-destructive">
|
|
208
|
+
Không thể tải dữ liệu: {error.message}
|
|
209
|
+
</div>
|
|
203
210
|
);
|
|
204
211
|
}
|
|
205
212
|
|
|
@@ -223,6 +230,7 @@ export function RoleListPage({
|
|
|
223
230
|
canCreate={canCreate}
|
|
224
231
|
canImport={canImport}
|
|
225
232
|
canExport={canExport}
|
|
233
|
+
importSlot={importSlot}
|
|
226
234
|
/>
|
|
227
235
|
|
|
228
236
|
{/* Roles Grid */}
|
|
@@ -342,15 +350,9 @@ export function RoleListPage({
|
|
|
342
350
|
</div>
|
|
343
351
|
<div className="flex items-center space-x-2">
|
|
344
352
|
{user.isActive ? (
|
|
345
|
-
<
|
|
346
|
-
<UserCheck className="h-3 w-3 mr-1" />
|
|
347
|
-
Active
|
|
348
|
-
</Badge>
|
|
353
|
+
<StatusIndicator status="active" />
|
|
349
354
|
) : (
|
|
350
|
-
<
|
|
351
|
-
<UserX className="h-3 w-3 mr-1" />
|
|
352
|
-
Inactive
|
|
353
|
-
</Badge>
|
|
355
|
+
<StatusIndicator status="inactive" />
|
|
354
356
|
)}
|
|
355
357
|
</div>
|
|
356
358
|
</div>
|
|
@@ -681,7 +681,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
681
681
|
{open && (
|
|
682
682
|
<div
|
|
683
683
|
ref={dropdownRef}
|
|
684
|
-
className="absolute z-[100] mt-1 w-full rounded-md border bg-popover/95 backdrop-blur-md text-popover-foreground shadow-lg shadow-black/5 animate-in fade-in-0 zoom-in-95 slide-in-from-top-1"
|
|
684
|
+
className="absolute z-[100] mt-1 w-full min-w-[15rem] rounded-md border bg-popover/95 backdrop-blur-md text-popover-foreground shadow-lg shadow-black/5 animate-in fade-in-0 zoom-in-95 slide-in-from-top-1"
|
|
685
685
|
style={{
|
|
686
686
|
top: "100%",
|
|
687
687
|
left: 0,
|
|
@@ -746,14 +746,15 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
746
746
|
)}
|
|
747
747
|
</div>
|
|
748
748
|
|
|
749
|
-
{/* Action Buttons
|
|
750
|
-
|
|
751
|
-
|
|
749
|
+
{/* Action Buttons — hàng gọn, không cho tràn: nhóm trái min-w-0,
|
|
750
|
+
nhóm phải shrink-0; nút đóng màu trung tính (không phải xóa) */}
|
|
751
|
+
<div className="flex items-center justify-between gap-1 overflow-hidden border-b p-1">
|
|
752
|
+
<div className="flex min-w-0 items-center">
|
|
752
753
|
<Button
|
|
753
754
|
type="button"
|
|
754
755
|
variant="ghost"
|
|
755
756
|
size="sm"
|
|
756
|
-
className="h-
|
|
757
|
+
className="h-7 px-1.5 text-xs"
|
|
757
758
|
onClick={(e) => {
|
|
758
759
|
e.stopPropagation();
|
|
759
760
|
handleSelectAll();
|
|
@@ -765,7 +766,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
765
766
|
type="button"
|
|
766
767
|
variant="ghost"
|
|
767
768
|
size="sm"
|
|
768
|
-
className="h-
|
|
769
|
+
className="h-7 px-1.5 text-xs"
|
|
769
770
|
onClick={(e) => {
|
|
770
771
|
e.stopPropagation();
|
|
771
772
|
handleClearAll();
|
|
@@ -774,7 +775,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
774
775
|
Bỏ chọn
|
|
775
776
|
</Button>
|
|
776
777
|
</div>
|
|
777
|
-
<div className="flex items-center">
|
|
778
|
+
<div className="flex shrink-0 items-center">
|
|
778
779
|
<span
|
|
779
780
|
className="whitespace-nowrap px-1 text-[11px] tabular-nums text-muted-foreground"
|
|
780
781
|
title={`Đã chọn ${value.length} / ${
|
|
@@ -790,13 +791,13 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
790
791
|
size="sm"
|
|
791
792
|
aria-label="Đóng"
|
|
792
793
|
title="Đóng"
|
|
793
|
-
className="h-
|
|
794
|
+
className="h-7 w-7 shrink-0 p-0 text-muted-foreground hover:text-foreground"
|
|
794
795
|
onClick={(e) => {
|
|
795
796
|
e.stopPropagation();
|
|
796
797
|
setOpen(false);
|
|
797
798
|
}}
|
|
798
799
|
>
|
|
799
|
-
<X className="h-
|
|
800
|
+
<X className="h-3.5 w-3.5" />
|
|
800
801
|
</Button>
|
|
801
802
|
</div>
|
|
802
803
|
</div>
|
|
@@ -35,7 +35,7 @@ export function HorizontalLayout({
|
|
|
35
35
|
searchResults={searchResults}
|
|
36
36
|
searchLoading={searchLoading}
|
|
37
37
|
/>
|
|
38
|
-
<div className="w-full min-h-screen flex flex-col bg-
|
|
38
|
+
<div className="w-full min-h-screen flex flex-col bg-background">
|
|
39
39
|
<HorizontalLayoutHeader
|
|
40
40
|
dictionary={dictionary}
|
|
41
41
|
navigation={navigation}
|
|
@@ -396,7 +396,9 @@ const SidebarInset = React.forwardRef<
|
|
|
396
396
|
<main
|
|
397
397
|
ref={ref}
|
|
398
398
|
className={cn(
|
|
399
|
-
|
|
399
|
+
// Canvas app dùng token bg-background (app tự quyết tông — vinhhoa/wu
|
|
400
|
+
// muốn nền TRẮNG); trước đây bg-muted/40 phủ xám toàn bộ trang.
|
|
401
|
+
"relative flex min-h-svh flex-1 flex-col bg-background transition-[margin] duration-200 ease-linear",
|
|
400
402
|
// Inset: content is a card floating with a uniform m-2 gap on ALL sides
|
|
401
403
|
// (no ml-0 override) so there's a small gap between the sidebar and the
|
|
402
404
|
// content/header instead of them touching.
|