refactor(cms): separate dashboard data and editor components with focused UX fixes

This commit is contained in:
Simo committed 2026-09-05 18:46:03 +02:00
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[];
}