teamshare-bridge 0.22.7 → 0.22.8
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/README.md +383 -383
- package/dist/bridge/daemon.js +43 -20
- package/dist/bridge/daemon.js.map +1 -1
- package/dist/bridge/install-verify.js +8 -8
- package/dist/bridge/spawn.d.ts +14 -0
- package/dist/bridge/spawn.js +7 -1
- package/dist/bridge/spawn.js.map +1 -1
- package/dist/bridge/wake-handler.d.ts +8 -15
- package/dist/bridge/wake-handler.js +109 -14
- package/dist/bridge/wake-handler.js.map +1 -1
- package/dist/bridge/wake-outbox.js +7 -0
- package/dist/bridge/wake-outbox.js.map +1 -1
- package/dist/cli/commands/plan.js +4 -0
- package/dist/cli/commands/plan.js.map +1 -1
- package/dist/lib/browser/human-input-guard.js +98 -98
- package/dist/lib/browser/screen-change.js +60 -60
- package/dist/lib/llm/usage-reader.js +4 -4
- package/dist/lib/workspace/classify.d.ts +29 -0
- package/dist/lib/workspace/classify.js +62 -0
- package/dist/lib/workspace/classify.js.map +1 -1
- package/dist/lib/workspace/presence.d.ts +31 -0
- package/dist/lib/workspace/presence.js +35 -1
- package/dist/lib/workspace/presence.js.map +1 -1
- package/dist/lib/workspace/shim-runner.js +23 -0
- package/dist/lib/workspace/shim-runner.js.map +1 -1
- package/package.json +70 -70
- package/skills/THIRD_PARTY_NOTICES.md +26 -26
- package/skills/architect/LICENSE +21 -21
- package/skills/codebase-design/LICENSE +21 -21
- package/skills/diagnosing-bugs/LICENSE +21 -21
- package/skills/domain-modeling/LICENSE +21 -21
- package/skills/improve-codebase-architecture/LICENSE +21 -21
- package/skills/recover/LICENSE +21 -21
- package/skills/review/LICENSE +21 -21
- package/skills/tdd/LICENSE +21 -21
- package/skills/teamshare-auth-rbac/SKILL.md +78 -78
- package/skills/teamshare-cdd/SKILL.md +205 -205
- package/skills/teamshare-modularity/SKILL.md +146 -146
- package/skills/teamshare-orchestrator-ask/SKILL.md +77 -77
- package/skills/teamshare-scaffold/SKILL.md +116 -116
- package/templates/backend/.env.example.tpl +13 -13
- package/templates/backend/nest-cli.json.tpl +8 -8
- package/templates/backend/package.json.tpl +41 -41
- package/templates/backend/prisma/schema.prisma.tpl +39 -39
- package/templates/backend/src/app.module.ts.tpl +24 -24
- package/templates/backend/src/common/filters/all-exceptions.filter.ts.tpl +59 -59
- package/templates/backend/src/common/guards/jwt-auth.guard.ts.tpl +27 -27
- package/templates/backend/src/common/guards/rbac.guard.ts.tpl +48 -48
- package/templates/backend/src/common/interceptors/transform.interceptor.ts.tpl +44 -44
- package/templates/backend/src/common/pipes/zod-validation.pipe.ts.tpl +29 -29
- package/templates/backend/src/common/swagger/envelope.schemas.ts.tpl +53 -53
- package/templates/backend/src/common/swagger/error.schemas.ts.tpl +62 -62
- package/templates/backend/src/main.ts.tpl +27 -27
- package/templates/backend/src/modules/example/example.controller.ts.tpl +102 -102
- package/templates/backend/src/modules/example/example.dto.ts.tpl +14 -14
- package/templates/backend/src/modules/example/example.module.ts.tpl +10 -10
- package/templates/backend/src/modules/example/example.service.ts.tpl +45 -45
- package/templates/backend/tsconfig.json.tpl +26 -26
- package/templates/common/.gitignore.tpl +42 -42
- package/templates/common/AGENTS.md.tpl +58 -58
- package/templates/common/docs/adr/README.md +25 -25
- package/templates/common/docs/api-contract.md.tpl +72 -72
- package/templates/web/next.config.ts.tpl +7 -7
- package/templates/web/package.json.tpl +37 -37
- package/templates/web/src/app/globals.css.tpl +48 -48
- package/templates/web/src/app/layout.tsx.tpl +25 -25
- package/templates/web/src/components/shared/app-button.tsx.tpl +59 -59
- package/templates/web/src/components/shared/app-empty-state.tsx.tpl +39 -39
- package/templates/web/src/components/shared/app-list-toolbar.tsx.tpl +63 -63
- package/templates/web/src/components/shared/app-skeleton.tsx.tpl +14 -14
- package/templates/web/src/components/shared/app-table.tsx.tpl +128 -128
- package/templates/web/src/components/shared/index.ts.tpl +5 -5
- package/templates/web/src/hooks/use-list-state.ts.tpl +72 -72
- package/templates/web/src/hooks/use-url-tab.ts.tpl +25 -25
- package/templates/web/src/lib/api/client.ts.tpl +123 -123
- package/templates/web/src/lib/providers.tsx.tpl +35 -35
- package/templates/web/src/lib/query-keys.ts.tpl +21 -21
- package/templates/web/src/lib/utils.ts.tpl +7 -7
- package/templates/web/src/lib/validation/schemas.ts.tpl +15 -15
- package/templates/web/src/modules/example/views/example-list-view.tsx.tpl +97 -97
- package/templates/web/tsconfig.json.tpl +23 -23
- package/tools/workspace-arbitration-check.mjs +389 -389
|
@@ -1,63 +1,63 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Search } from "lucide-react";
|
|
4
|
-
import { type ReactNode } from "react";
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
|
-
|
|
7
|
-
export interface AppListToolbarProps {
|
|
8
|
-
search?: string;
|
|
9
|
-
onSearchChange?: (value: string) => void;
|
|
10
|
-
searchPlaceholder?: string;
|
|
11
|
-
filters?: ReactNode;
|
|
12
|
-
actions?: ReactNode;
|
|
13
|
-
resultCount?: number;
|
|
14
|
-
className?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function AppListToolbar({
|
|
18
|
-
search,
|
|
19
|
-
onSearchChange,
|
|
20
|
-
searchPlaceholder = "Search...",
|
|
21
|
-
filters,
|
|
22
|
-
actions,
|
|
23
|
-
resultCount,
|
|
24
|
-
className,
|
|
25
|
-
}: AppListToolbarProps) {
|
|
26
|
-
return (
|
|
27
|
-
<div
|
|
28
|
-
data-slot="app-list-toolbar"
|
|
29
|
-
className={cn(
|
|
30
|
-
"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
31
|
-
className
|
|
32
|
-
)}
|
|
33
|
-
>
|
|
34
|
-
<div className="flex flex-1 items-center gap-3">
|
|
35
|
-
{onSearchChange && (
|
|
36
|
-
<div className="relative flex-1 max-w-sm">
|
|
37
|
-
<Search
|
|
38
|
-
className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-tertiary"
|
|
39
|
-
variant="Outline"
|
|
40
|
-
color="currentColor"
|
|
41
|
-
/>
|
|
42
|
-
<input
|
|
43
|
-
type="text"
|
|
44
|
-
placeholder={searchPlaceholder}
|
|
45
|
-
value={search ?? ""}
|
|
46
|
-
onChange={(e) => onSearchChange(e.target.value)}
|
|
47
|
-
className="h-10 w-full rounded-md border border-input bg-background pl-9 pr-3 text-sm placeholder:text-tertiary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
48
|
-
/>
|
|
49
|
-
</div>
|
|
50
|
-
)}
|
|
51
|
-
{filters}
|
|
52
|
-
</div>
|
|
53
|
-
<div className="flex items-center gap-3">
|
|
54
|
-
{resultCount !== undefined && (
|
|
55
|
-
<span className="text-sm text-secondary">
|
|
56
|
-
{resultCount} result{resultCount !== 1 ? "s" : ""}
|
|
57
|
-
</span>
|
|
58
|
-
)}
|
|
59
|
-
{actions}
|
|
60
|
-
</div>
|
|
61
|
-
</div>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Search } from "lucide-react";
|
|
4
|
+
import { type ReactNode } from "react";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
export interface AppListToolbarProps {
|
|
8
|
+
search?: string;
|
|
9
|
+
onSearchChange?: (value: string) => void;
|
|
10
|
+
searchPlaceholder?: string;
|
|
11
|
+
filters?: ReactNode;
|
|
12
|
+
actions?: ReactNode;
|
|
13
|
+
resultCount?: number;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function AppListToolbar({
|
|
18
|
+
search,
|
|
19
|
+
onSearchChange,
|
|
20
|
+
searchPlaceholder = "Search...",
|
|
21
|
+
filters,
|
|
22
|
+
actions,
|
|
23
|
+
resultCount,
|
|
24
|
+
className,
|
|
25
|
+
}: AppListToolbarProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="app-list-toolbar"
|
|
29
|
+
className={cn(
|
|
30
|
+
"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
31
|
+
className
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<div className="flex flex-1 items-center gap-3">
|
|
35
|
+
{onSearchChange && (
|
|
36
|
+
<div className="relative flex-1 max-w-sm">
|
|
37
|
+
<Search
|
|
38
|
+
className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-tertiary"
|
|
39
|
+
variant="Outline"
|
|
40
|
+
color="currentColor"
|
|
41
|
+
/>
|
|
42
|
+
<input
|
|
43
|
+
type="text"
|
|
44
|
+
placeholder={searchPlaceholder}
|
|
45
|
+
value={search ?? ""}
|
|
46
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
47
|
+
className="h-10 w-full rounded-md border border-input bg-background pl-9 pr-3 text-sm placeholder:text-tertiary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
{filters}
|
|
52
|
+
</div>
|
|
53
|
+
<div className="flex items-center gap-3">
|
|
54
|
+
{resultCount !== undefined && (
|
|
55
|
+
<span className="text-sm text-secondary">
|
|
56
|
+
{resultCount} result{resultCount !== 1 ? "s" : ""}
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
{actions}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { cn } from "@/lib/utils";
|
|
2
|
-
|
|
3
|
-
export interface AppSkeletonProps {
|
|
4
|
-
className?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function AppSkeleton({ className }: AppSkeletonProps) {
|
|
8
|
-
return (
|
|
9
|
-
<div
|
|
10
|
-
data-slot="app-skeleton"
|
|
11
|
-
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
12
|
-
/>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
|
|
3
|
+
export interface AppSkeletonProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function AppSkeleton({ className }: AppSkeletonProps) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="app-skeleton"
|
|
11
|
+
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type ColumnDef,
|
|
5
|
-
type PaginationState,
|
|
6
|
-
type SortingState,
|
|
7
|
-
flexRender,
|
|
8
|
-
getCoreRowModel,
|
|
9
|
-
getPaginationRowModel,
|
|
10
|
-
getSortedRowModel,
|
|
11
|
-
useReactTable,
|
|
12
|
-
} from "@tanstack/react-table";
|
|
13
|
-
import { type ReactNode } from "react";
|
|
14
|
-
import { cn } from "@/lib/utils";
|
|
15
|
-
import { AppSkeleton } from "./app-skeleton";
|
|
16
|
-
import { AppEmptyState } from "./app-empty-state";
|
|
17
|
-
|
|
18
|
-
export interface AppTableProps<TData> {
|
|
19
|
-
columns: ColumnDef<TData, unknown>[];
|
|
20
|
-
data: TData[];
|
|
21
|
-
loading?: boolean;
|
|
22
|
-
sorting?: SortingState;
|
|
23
|
-
onSortingChange?: (sorting: SortingState) => void;
|
|
24
|
-
pagination?: PaginationState;
|
|
25
|
-
onPaginationChange?: (pagination: PaginationState) => void;
|
|
26
|
-
pageCount?: number;
|
|
27
|
-
emptyIcon?: ReactNode;
|
|
28
|
-
emptyTitle?: string;
|
|
29
|
-
emptyDescription?: string;
|
|
30
|
-
className?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function AppTable<TData>({
|
|
34
|
-
columns,
|
|
35
|
-
data,
|
|
36
|
-
loading = false,
|
|
37
|
-
sorting,
|
|
38
|
-
onSortingChange,
|
|
39
|
-
pagination,
|
|
40
|
-
onPaginationChange,
|
|
41
|
-
pageCount,
|
|
42
|
-
emptyIcon,
|
|
43
|
-
emptyTitle = "No results",
|
|
44
|
-
emptyDescription,
|
|
45
|
-
className,
|
|
46
|
-
}: AppTableProps<TData>) {
|
|
47
|
-
const table = useReactTable({
|
|
48
|
-
data,
|
|
49
|
-
columns,
|
|
50
|
-
pageCount,
|
|
51
|
-
state: {
|
|
52
|
-
sorting,
|
|
53
|
-
pagination,
|
|
54
|
-
},
|
|
55
|
-
onSortingChange,
|
|
56
|
-
onPaginationChange,
|
|
57
|
-
getCoreRowModel: getCoreRowModel(),
|
|
58
|
-
getSortedRowModel: getSortedRowModel(),
|
|
59
|
-
getPaginationRowModel: getPaginationRowModel(),
|
|
60
|
-
manualPagination: true,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
if (loading) {
|
|
64
|
-
return (
|
|
65
|
-
<div data-slot="app-table" className={cn("rounded-md border", className)}>
|
|
66
|
-
<div className="space-y-2 p-4">
|
|
67
|
-
{Array.from({ length: 5 }).map((_, i) => (
|
|
68
|
-
<AppSkeleton key={i} className="h-12 w-full" />
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (data.length === 0) {
|
|
76
|
-
return (
|
|
77
|
-
<div data-slot="app-table" className={cn("rounded-md border", className)}>
|
|
78
|
-
<AppEmptyState
|
|
79
|
-
icon={emptyIcon}
|
|
80
|
-
title={emptyTitle}
|
|
81
|
-
description={emptyDescription}
|
|
82
|
-
/>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<div data-slot="app-table" className={cn("rounded-md border", className)}>
|
|
89
|
-
<div className="overflow-auto">
|
|
90
|
-
<table className="w-full caption-bottom text-sm">
|
|
91
|
-
<thead className="border-b bg-muted/50">
|
|
92
|
-
{table.getHeaderGroups().map((headerGroup) => (
|
|
93
|
-
<tr key={headerGroup.id}>
|
|
94
|
-
{headerGroup.headers.map((header) => (
|
|
95
|
-
<th
|
|
96
|
-
key={header.id}
|
|
97
|
-
className="h-10 px-4 text-left font-medium text-secondary"
|
|
98
|
-
>
|
|
99
|
-
{header.isPlaceholder
|
|
100
|
-
? null
|
|
101
|
-
: flexRender(
|
|
102
|
-
header.column.columnDef.header,
|
|
103
|
-
header.getContext()
|
|
104
|
-
)}
|
|
105
|
-
</th>
|
|
106
|
-
))}
|
|
107
|
-
</tr>
|
|
108
|
-
))}
|
|
109
|
-
</thead>
|
|
110
|
-
<tbody>
|
|
111
|
-
{table.getRowModel().rows.map((row) => (
|
|
112
|
-
<tr
|
|
113
|
-
key={row.id}
|
|
114
|
-
className="border-b transition-colors hover:bg-muted/50"
|
|
115
|
-
>
|
|
116
|
-
{row.getVisibleCells().map((cell) => (
|
|
117
|
-
<td key={cell.id} className="p-4">
|
|
118
|
-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
119
|
-
</td>
|
|
120
|
-
))}
|
|
121
|
-
</tr>
|
|
122
|
-
))}
|
|
123
|
-
</tbody>
|
|
124
|
-
</table>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type ColumnDef,
|
|
5
|
+
type PaginationState,
|
|
6
|
+
type SortingState,
|
|
7
|
+
flexRender,
|
|
8
|
+
getCoreRowModel,
|
|
9
|
+
getPaginationRowModel,
|
|
10
|
+
getSortedRowModel,
|
|
11
|
+
useReactTable,
|
|
12
|
+
} from "@tanstack/react-table";
|
|
13
|
+
import { type ReactNode } from "react";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import { AppSkeleton } from "./app-skeleton";
|
|
16
|
+
import { AppEmptyState } from "./app-empty-state";
|
|
17
|
+
|
|
18
|
+
export interface AppTableProps<TData> {
|
|
19
|
+
columns: ColumnDef<TData, unknown>[];
|
|
20
|
+
data: TData[];
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
sorting?: SortingState;
|
|
23
|
+
onSortingChange?: (sorting: SortingState) => void;
|
|
24
|
+
pagination?: PaginationState;
|
|
25
|
+
onPaginationChange?: (pagination: PaginationState) => void;
|
|
26
|
+
pageCount?: number;
|
|
27
|
+
emptyIcon?: ReactNode;
|
|
28
|
+
emptyTitle?: string;
|
|
29
|
+
emptyDescription?: string;
|
|
30
|
+
className?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function AppTable<TData>({
|
|
34
|
+
columns,
|
|
35
|
+
data,
|
|
36
|
+
loading = false,
|
|
37
|
+
sorting,
|
|
38
|
+
onSortingChange,
|
|
39
|
+
pagination,
|
|
40
|
+
onPaginationChange,
|
|
41
|
+
pageCount,
|
|
42
|
+
emptyIcon,
|
|
43
|
+
emptyTitle = "No results",
|
|
44
|
+
emptyDescription,
|
|
45
|
+
className,
|
|
46
|
+
}: AppTableProps<TData>) {
|
|
47
|
+
const table = useReactTable({
|
|
48
|
+
data,
|
|
49
|
+
columns,
|
|
50
|
+
pageCount,
|
|
51
|
+
state: {
|
|
52
|
+
sorting,
|
|
53
|
+
pagination,
|
|
54
|
+
},
|
|
55
|
+
onSortingChange,
|
|
56
|
+
onPaginationChange,
|
|
57
|
+
getCoreRowModel: getCoreRowModel(),
|
|
58
|
+
getSortedRowModel: getSortedRowModel(),
|
|
59
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
60
|
+
manualPagination: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (loading) {
|
|
64
|
+
return (
|
|
65
|
+
<div data-slot="app-table" className={cn("rounded-md border", className)}>
|
|
66
|
+
<div className="space-y-2 p-4">
|
|
67
|
+
{Array.from({ length: 5 }).map((_, i) => (
|
|
68
|
+
<AppSkeleton key={i} className="h-12 w-full" />
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (data.length === 0) {
|
|
76
|
+
return (
|
|
77
|
+
<div data-slot="app-table" className={cn("rounded-md border", className)}>
|
|
78
|
+
<AppEmptyState
|
|
79
|
+
icon={emptyIcon}
|
|
80
|
+
title={emptyTitle}
|
|
81
|
+
description={emptyDescription}
|
|
82
|
+
/>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div data-slot="app-table" className={cn("rounded-md border", className)}>
|
|
89
|
+
<div className="overflow-auto">
|
|
90
|
+
<table className="w-full caption-bottom text-sm">
|
|
91
|
+
<thead className="border-b bg-muted/50">
|
|
92
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
93
|
+
<tr key={headerGroup.id}>
|
|
94
|
+
{headerGroup.headers.map((header) => (
|
|
95
|
+
<th
|
|
96
|
+
key={header.id}
|
|
97
|
+
className="h-10 px-4 text-left font-medium text-secondary"
|
|
98
|
+
>
|
|
99
|
+
{header.isPlaceholder
|
|
100
|
+
? null
|
|
101
|
+
: flexRender(
|
|
102
|
+
header.column.columnDef.header,
|
|
103
|
+
header.getContext()
|
|
104
|
+
)}
|
|
105
|
+
</th>
|
|
106
|
+
))}
|
|
107
|
+
</tr>
|
|
108
|
+
))}
|
|
109
|
+
</thead>
|
|
110
|
+
<tbody>
|
|
111
|
+
{table.getRowModel().rows.map((row) => (
|
|
112
|
+
<tr
|
|
113
|
+
key={row.id}
|
|
114
|
+
className="border-b transition-colors hover:bg-muted/50"
|
|
115
|
+
>
|
|
116
|
+
{row.getVisibleCells().map((cell) => (
|
|
117
|
+
<td key={cell.id} className="p-4">
|
|
118
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
119
|
+
</td>
|
|
120
|
+
))}
|
|
121
|
+
</tr>
|
|
122
|
+
))}
|
|
123
|
+
</tbody>
|
|
124
|
+
</table>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { AppButton, buttonVariants, type AppButtonProps } from "./app-button";
|
|
2
|
-
export { AppEmptyState, type AppEmptyStateProps } from "./app-empty-state";
|
|
3
|
-
export { AppSkeleton, type AppSkeletonProps } from "./app-skeleton";
|
|
4
|
-
export { AppTable, type AppTableProps } from "./app-table";
|
|
5
|
-
export { AppListToolbar, type AppListToolbarProps } from "./app-list-toolbar";
|
|
1
|
+
export { AppButton, buttonVariants, type AppButtonProps } from "./app-button";
|
|
2
|
+
export { AppEmptyState, type AppEmptyStateProps } from "./app-empty-state";
|
|
3
|
+
export { AppSkeleton, type AppSkeletonProps } from "./app-skeleton";
|
|
4
|
+
export { AppTable, type AppTableProps } from "./app-table";
|
|
5
|
+
export { AppListToolbar, type AppListToolbarProps } from "./app-list-toolbar";
|
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useRouter, useSearchParams } from "next/navigation";
|
|
4
|
-
import { useCallback, useTransition } from "react";
|
|
5
|
-
|
|
6
|
-
export interface ListState {
|
|
7
|
-
search: string;
|
|
8
|
-
page: number;
|
|
9
|
-
limit: number;
|
|
10
|
-
sort?: string;
|
|
11
|
-
order?: "asc" | "desc";
|
|
12
|
-
[key: string]: string | number | undefined;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function useListState(defaults: Partial<ListState> = {}) {
|
|
16
|
-
const router = useRouter();
|
|
17
|
-
const searchParams = useSearchParams();
|
|
18
|
-
const [isPending, startTransition] = useTransition();
|
|
19
|
-
|
|
20
|
-
const state: ListState = {
|
|
21
|
-
search: searchParams.get("search") ?? defaults.search ?? "",
|
|
22
|
-
page: Number(searchParams.get("page")) || defaults.page || 1,
|
|
23
|
-
limit: Number(searchParams.get("limit")) || defaults.limit || 20,
|
|
24
|
-
sort: searchParams.get("sort") ?? defaults.sort,
|
|
25
|
-
order: (searchParams.get("order") as "asc" | "desc") ?? defaults.order,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const setSearchParams = useCallback(
|
|
29
|
-
(updates: Partial<ListState>) => {
|
|
30
|
-
const params = new URLSearchParams(searchParams.toString());
|
|
31
|
-
Object.entries(updates).forEach(([key, value]) => {
|
|
32
|
-
if (value === undefined || value === null || value === "") {
|
|
33
|
-
params.delete(key);
|
|
34
|
-
} else {
|
|
35
|
-
params.set(key, String(value));
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
if (updates.search !== undefined) {
|
|
39
|
-
params.set("page", "1");
|
|
40
|
-
}
|
|
41
|
-
startTransition(() => {
|
|
42
|
-
router.push(`?${params.toString()}`, { scroll: false });
|
|
43
|
-
});
|
|
44
|
-
},
|
|
45
|
-
[router, searchParams, startTransition]
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
const setSearch = useCallback(
|
|
49
|
-
(search: string) => setSearchParams({ search }),
|
|
50
|
-
[setSearchParams]
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const setPage = useCallback(
|
|
54
|
-
(page: number) => setSearchParams({ page }),
|
|
55
|
-
[setSearchParams]
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
const setSort = useCallback(
|
|
59
|
-
(sort: string, order: "asc" | "desc" = "asc") =>
|
|
60
|
-
setSearchParams({ sort, order }),
|
|
61
|
-
[setSearchParams]
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
state,
|
|
66
|
-
setSearch,
|
|
67
|
-
setPage,
|
|
68
|
-
setSort,
|
|
69
|
-
setParams: setSearchParams,
|
|
70
|
-
isPending,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
4
|
+
import { useCallback, useTransition } from "react";
|
|
5
|
+
|
|
6
|
+
export interface ListState {
|
|
7
|
+
search: string;
|
|
8
|
+
page: number;
|
|
9
|
+
limit: number;
|
|
10
|
+
sort?: string;
|
|
11
|
+
order?: "asc" | "desc";
|
|
12
|
+
[key: string]: string | number | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useListState(defaults: Partial<ListState> = {}) {
|
|
16
|
+
const router = useRouter();
|
|
17
|
+
const searchParams = useSearchParams();
|
|
18
|
+
const [isPending, startTransition] = useTransition();
|
|
19
|
+
|
|
20
|
+
const state: ListState = {
|
|
21
|
+
search: searchParams.get("search") ?? defaults.search ?? "",
|
|
22
|
+
page: Number(searchParams.get("page")) || defaults.page || 1,
|
|
23
|
+
limit: Number(searchParams.get("limit")) || defaults.limit || 20,
|
|
24
|
+
sort: searchParams.get("sort") ?? defaults.sort,
|
|
25
|
+
order: (searchParams.get("order") as "asc" | "desc") ?? defaults.order,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const setSearchParams = useCallback(
|
|
29
|
+
(updates: Partial<ListState>) => {
|
|
30
|
+
const params = new URLSearchParams(searchParams.toString());
|
|
31
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
32
|
+
if (value === undefined || value === null || value === "") {
|
|
33
|
+
params.delete(key);
|
|
34
|
+
} else {
|
|
35
|
+
params.set(key, String(value));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (updates.search !== undefined) {
|
|
39
|
+
params.set("page", "1");
|
|
40
|
+
}
|
|
41
|
+
startTransition(() => {
|
|
42
|
+
router.push(`?${params.toString()}`, { scroll: false });
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
[router, searchParams, startTransition]
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const setSearch = useCallback(
|
|
49
|
+
(search: string) => setSearchParams({ search }),
|
|
50
|
+
[setSearchParams]
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const setPage = useCallback(
|
|
54
|
+
(page: number) => setSearchParams({ page }),
|
|
55
|
+
[setSearchParams]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const setSort = useCallback(
|
|
59
|
+
(sort: string, order: "asc" | "desc" = "asc") =>
|
|
60
|
+
setSearchParams({ sort, order }),
|
|
61
|
+
[setSearchParams]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
state,
|
|
66
|
+
setSearch,
|
|
67
|
+
setPage,
|
|
68
|
+
setSort,
|
|
69
|
+
setParams: setSearchParams,
|
|
70
|
+
isPending,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useSearchParams } from "next/navigation";
|
|
4
|
-
import { useCallback } from "react";
|
|
5
|
-
|
|
6
|
-
export function useUrlTab(defaultValue: string = "overview") {
|
|
7
|
-
const searchParams = useSearchParams();
|
|
8
|
-
const tab = searchParams.get("tab") ?? defaultValue;
|
|
9
|
-
|
|
10
|
-
const setTab = useCallback(
|
|
11
|
-
(value: string) => {
|
|
12
|
-
const url = new URL(window.location.href);
|
|
13
|
-
if (value === defaultValue) {
|
|
14
|
-
url.searchParams.delete("tab");
|
|
15
|
-
} else {
|
|
16
|
-
url.searchParams.set("tab", value);
|
|
17
|
-
}
|
|
18
|
-
window.history.replaceState({}, "", url.toString());
|
|
19
|
-
window.dispatchEvent(new Event("popstate"));
|
|
20
|
-
},
|
|
21
|
-
[defaultValue]
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
return { tab, setTab };
|
|
25
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSearchParams } from "next/navigation";
|
|
4
|
+
import { useCallback } from "react";
|
|
5
|
+
|
|
6
|
+
export function useUrlTab(defaultValue: string = "overview") {
|
|
7
|
+
const searchParams = useSearchParams();
|
|
8
|
+
const tab = searchParams.get("tab") ?? defaultValue;
|
|
9
|
+
|
|
10
|
+
const setTab = useCallback(
|
|
11
|
+
(value: string) => {
|
|
12
|
+
const url = new URL(window.location.href);
|
|
13
|
+
if (value === defaultValue) {
|
|
14
|
+
url.searchParams.delete("tab");
|
|
15
|
+
} else {
|
|
16
|
+
url.searchParams.set("tab", value);
|
|
17
|
+
}
|
|
18
|
+
window.history.replaceState({}, "", url.toString());
|
|
19
|
+
window.dispatchEvent(new Event("popstate"));
|
|
20
|
+
},
|
|
21
|
+
[defaultValue]
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return { tab, setTab };
|
|
25
|
+
}
|