refactor(cms): separate dashboard data and editor components with focused UX fixes
This commit is contained in:
1 parent
3ae4f21217
commit
e17346ab9f
11 files changed
+1212
-1118
No files matched your search
@@ -0,0 +1,56 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
/* ─── Helpers ────────────────────────────────────────────── */
|
||||
|
||||
export function categorizePerms(keys: string[]) {
|
||||
const commands: string[] = [];
|
||||
const account: string[] = [];
|
||||
const other: string[] = [];
|
||||
for (const key of keys.sort()) {
|
||||
if (key.startsWith("cmd_") || key === "kiss_cmd") commands.push(key);
|
||||
else if (key.startsWith("acc_")) account.push(key);
|
||||
else other.push(key);
|
||||
}
|
||||
return { commands, account, other };
|
||||
}
|
||||
|
||||
/* ─── Tri-state Toggle ───────────────────────────────────── */
|
||||
|
||||
export function PermToggle({
|
||||
value,
|
||||
maxValue,
|
||||
onChange,
|
||||
}: {
|
||||
value: string;
|
||||
maxValue: number;
|
||||
onChange: (v: string) => void;
|
||||
}) {
|
||||
const values = Array.from({ length: Math.max(1, maxValue) + 1 }, (_, i) =>
|
||||
String(i),
|
||||
);
|
||||
return (
|
||||
<div className="flex gap-0.5 shrink-0">
|
||||
{values.map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
aria-pressed={v === value}
|
||||
aria-label={`Permission value ${v}`}
|
||||
onClick={() => onChange(v)}
|
||||
className={cn(
|
||||
"w-7 h-6 text-[11px] font-mono rounded transition-all font-medium",
|
||||
v === value
|
||||
? v === "0"
|
||||
? "bg-[var(--admin-text-muted)] text-[var(--color-surface-text-readable)] shadow-sm"
|
||||
: v === "1"
|
||||
? "bg-[var(--admin-success)] text-[var(--admin-success-foreground)] shadow-sm"
|
||||
: "bg-[var(--admin-warning)] text-[var(--admin-warning-foreground)] shadow-sm"
|
||||
: "bg-muted/60 text-muted-foreground/40 hover:bg-muted hover:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{v}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -31,104 +31,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { useServerAction } from "@/hooks/use-server-action";
|
||||
import { getAvatarUrl } from "@/lib/imager";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { categorizePerms, PermToggle } from "./permission-controls";
|
||||
import type { RankEditorProps } from "./rank-editor-types";
|
||||
/* ─── Types ──────────────────────────────────────────────── */
|
||||
|
||||
interface RankData {
|
||||
id: number;
|
||||
rank_name: string;
|
||||
badge: string;
|
||||
level: number;
|
||||
prefix: string;
|
||||
prefix_color: string;
|
||||
hidden_rank: string;
|
||||
log_commands: string;
|
||||
room_effect: number;
|
||||
auto_credits_amount: number;
|
||||
auto_pixels_amount: number;
|
||||
auto_gotw_amount: number;
|
||||
auto_points_amount: number;
|
||||
permissions: Record<string, string>;
|
||||
permissionMaxValues: Record<string, number>;
|
||||
}
|
||||
|
||||
interface RankUser {
|
||||
id: number;
|
||||
username: string;
|
||||
look: string;
|
||||
}
|
||||
interface CmsRoleData {
|
||||
id: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
permissionSlugs: string[];
|
||||
}
|
||||
interface CmsPermission {
|
||||
id: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
}
|
||||
interface Props {
|
||||
rank: RankData;
|
||||
users: RankUser[];
|
||||
totalUsers: number;
|
||||
cmsRole: CmsRoleData | null;
|
||||
allCmsPermissions: CmsPermission[];
|
||||
}
|
||||
|
||||
/* ─── Helpers ────────────────────────────────────────────── */
|
||||
|
||||
function categorizePerms(keys: string[]) {
|
||||
const commands: string[] = [];
|
||||
const account: string[] = [];
|
||||
const other: string[] = [];
|
||||
for (const key of keys.sort()) {
|
||||
if (key.startsWith("cmd_") || key === "kiss_cmd") commands.push(key);
|
||||
else if (key.startsWith("acc_")) account.push(key);
|
||||
else other.push(key);
|
||||
}
|
||||
return { commands, account, other };
|
||||
}
|
||||
|
||||
/* ─── Tri-state Toggle ───────────────────────────────────── */
|
||||
|
||||
function PermToggle({
|
||||
value,
|
||||
maxValue,
|
||||
onChange,
|
||||
}: {
|
||||
value: string;
|
||||
maxValue: number;
|
||||
onChange: (v: string) => void;
|
||||
}) {
|
||||
const values = Array.from({ length: Math.max(1, maxValue) + 1 }, (_, i) =>
|
||||
String(i),
|
||||
);
|
||||
return (
|
||||
<div className="flex gap-0.5 shrink-0">
|
||||
{values.map((v) => (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
onClick={() => onChange(v)}
|
||||
className={cn(
|
||||
"w-7 h-6 text-[11px] font-mono rounded transition-all font-medium",
|
||||
v === value
|
||||
? v === "0"
|
||||
? "bg-[var(--admin-text-muted)] text-[var(--color-surface-text-readable)] shadow-sm"
|
||||
: v === "1"
|
||||
? "bg-[var(--admin-success)] text-[var(--admin-success-foreground)] shadow-sm"
|
||||
: "bg-[var(--admin-warning)] text-[var(--admin-warning-foreground)] shadow-sm"
|
||||
: "bg-muted/60 text-muted-foreground/40 hover:bg-muted hover:text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
{v}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ─── Main Component ─────────────────────────────────────── */
|
||||
|
||||
export function RankEditClient({
|
||||
@@ -137,7 +43,7 @@ export function RankEditClient({
|
||||
totalUsers,
|
||||
cmsRole: initialCmsRole,
|
||||
allCmsPermissions,
|
||||
}: Props) {
|
||||
}: RankEditorProps) {
|
||||
const { run, isPending } = useServerAction();
|
||||
|
||||
const [general, setGeneral] = useState({
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
interface RankData {
|
||||
id: number;
|
||||
rank_name: string;
|
||||
badge: string;
|
||||
level: number;
|
||||
prefix: string;
|
||||
prefix_color: string;
|
||||
hidden_rank: string;
|
||||
log_commands: string;
|
||||
room_effect: number;
|
||||
auto_credits_amount: number;
|
||||
auto_pixels_amount: number;
|
||||
auto_gotw_amount: number;
|
||||
auto_points_amount: number;
|
||||
permissions: Record<string, string>;
|
||||
permissionMaxValues: Record<string, number>;
|
||||
}
|
||||
|
||||
interface RankUser {
|
||||
id: number;
|
||||
username: string;
|
||||
look: string;
|
||||
}
|
||||
interface CmsRoleData {
|
||||
id: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
permissionSlugs: string[];
|
||||
}
|
||||
interface CmsPermission {
|
||||
id: number;
|
||||
slug: string;
|
||||
title: string;
|
||||
}
|
||||
export interface RankEditorProps {
|
||||
rank: RankData;
|
||||
users: RankUser[];
|
||||
totalUsers: number;
|
||||
cmsRole: CmsRoleData | null;
|
||||
allCmsPermissions: CmsPermission[];
|
||||
}
|
||||
Reference in new issue
Block a user