revert fb8e77bb68
Local Build and Deploy / deploy (push) Successful in 1m11s
Local Build and Deploy / deploy (push) Successful in 1m11s
revert style: clean up code with prettier and eslint
This commit is contained in:
1 parent
27de078f54
commit
e85e4d74ea
378 files changed
+20710
-22428
No files matched your search
@@ -1,4 +1,4 @@
|
||||
"use client";
|
||||
'use client'
|
||||
|
||||
import {
|
||||
ChevronLeft,
|
||||
@@ -9,56 +9,62 @@ import {
|
||||
RotateCcw,
|
||||
Save,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { saveEmulatorSettings } from "@/actions/emulator";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { useServerAction } from "@/hooks/use-server-action";
|
||||
} from 'lucide-react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useEffect, useMemo, useState, useTransition } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { saveEmulatorSettings } from '@/actions/emulator'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useServerAction } from '@/hooks/use-server-action'
|
||||
|
||||
interface Setting {
|
||||
key: string;
|
||||
value: string;
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface GroupSummary {
|
||||
name: string;
|
||||
count: number;
|
||||
name: string
|
||||
count: number
|
||||
}
|
||||
|
||||
interface EmulatorSettingsFormProps {
|
||||
settings: Setting[];
|
||||
pagination: { total: number; page: number; perPage: number; lastPage: number };
|
||||
search: string;
|
||||
allGroups?: GroupSummary[];
|
||||
activeGroup?: string;
|
||||
settings: Setting[]
|
||||
pagination: { total: number; page: number; perPage: number; lastPage: number }
|
||||
search: string
|
||||
allGroups?: GroupSummary[]
|
||||
activeGroup?: string
|
||||
}
|
||||
|
||||
const PER_PAGE_OPTIONS = [25, 50, 100, 200] as const;
|
||||
const PER_PAGE_OPTIONS = [25, 50, 100, 200] as const
|
||||
|
||||
function getSettingGroup(key: string): string {
|
||||
const parts = key.split(".");
|
||||
return parts.length > 1 ? parts[0] : "general";
|
||||
const parts = key.split('.')
|
||||
return parts.length > 1 ? parts[0] : 'general'
|
||||
}
|
||||
|
||||
const GROUP_BORDER_COLORS: Record<string, string> = {
|
||||
hotel: "border-l-blue-500",
|
||||
navigator: "border-l-green-500",
|
||||
catalog: "border-l-purple-500",
|
||||
room: "border-l-orange-500",
|
||||
camera: "border-l-pink-500",
|
||||
commands: "border-l-cyan-500",
|
||||
wordfilter: "border-l-red-500",
|
||||
pet: "border-l-yellow-500",
|
||||
matchmaking: "border-l-indigo-500",
|
||||
message: "border-l-emerald-500",
|
||||
notification: "border-l-fuchsia-500",
|
||||
general: "border-l-gray-400",
|
||||
};
|
||||
hotel: 'border-l-blue-500',
|
||||
navigator: 'border-l-green-500',
|
||||
catalog: 'border-l-purple-500',
|
||||
room: 'border-l-orange-500',
|
||||
camera: 'border-l-pink-500',
|
||||
commands: 'border-l-cyan-500',
|
||||
wordfilter: 'border-l-red-500',
|
||||
pet: 'border-l-yellow-500',
|
||||
matchmaking: 'border-l-indigo-500',
|
||||
message: 'border-l-emerald-500',
|
||||
notification: 'border-l-fuchsia-500',
|
||||
general: 'border-l-gray-400',
|
||||
}
|
||||
|
||||
export function EmulatorSettingsForm({
|
||||
settings,
|
||||
@@ -67,130 +73,130 @@ export function EmulatorSettingsForm({
|
||||
allGroups,
|
||||
activeGroup,
|
||||
}: EmulatorSettingsFormProps) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
// `values` only contains overrides typed by the user. Display falls back
|
||||
// to `setting.value`. `originalValues` accumulates the on-disk values for
|
||||
// every setting we have ever rendered (across pages) so we can detect when
|
||||
// an edit returns to its original state.
|
||||
const [values, setValues] = useState<Record<string, string>>({});
|
||||
const [originalValues, setOriginalValues] = useState<Record<string, string>>({});
|
||||
const [changedKeys, setChangedKeys] = useState<Set<string>>(new Set());
|
||||
const [isNavigating, startTransition] = useTransition();
|
||||
const { run, isPending } = useServerAction();
|
||||
const [searchValue, setSearchValue] = useState(search);
|
||||
const [values, setValues] = useState<Record<string, string>>({})
|
||||
const [originalValues, setOriginalValues] = useState<Record<string, string>>({})
|
||||
const [changedKeys, setChangedKeys] = useState<Set<string>>(new Set())
|
||||
const [isNavigating, startTransition] = useTransition()
|
||||
const { run, isPending } = useServerAction()
|
||||
const [searchValue, setSearchValue] = useState(search)
|
||||
|
||||
useEffect(() => {
|
||||
setOriginalValues((prev) => {
|
||||
let dirty = false;
|
||||
const next = { ...prev };
|
||||
let dirty = false
|
||||
const next = { ...prev }
|
||||
for (const s of settings) {
|
||||
if (!(s.key in next)) {
|
||||
next[s.key] = s.value;
|
||||
dirty = true;
|
||||
next[s.key] = s.value
|
||||
dirty = true
|
||||
}
|
||||
}
|
||||
return dirty ? next : prev;
|
||||
});
|
||||
}, [settings]);
|
||||
return dirty ? next : prev
|
||||
})
|
||||
}, [settings])
|
||||
|
||||
// Prefer the canonical list passed by the server (covers all groups, not
|
||||
// just those visible on the current page). Fall back to deriving from the
|
||||
// current page when the prop is omitted.
|
||||
const groupSummary = useMemo<GroupSummary[]>(() => {
|
||||
if (allGroups && allGroups.length > 0) return allGroups;
|
||||
const map = new Map<string, number>();
|
||||
if (allGroups && allGroups.length > 0) return allGroups
|
||||
const map = new Map<string, number>()
|
||||
for (const s of settings) {
|
||||
const g = getSettingGroup(s.key);
|
||||
map.set(g, (map.get(g) ?? 0) + 1);
|
||||
const g = getSettingGroup(s.key)
|
||||
map.set(g, (map.get(g) ?? 0) + 1)
|
||||
}
|
||||
return Array.from(map.entries())
|
||||
.map(([name, count]) => ({ name, count }))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}, [allGroups, settings]);
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}, [allGroups, settings])
|
||||
|
||||
function toggleGroup(name: string) {
|
||||
updateParams({
|
||||
group: activeGroup === name ? "" : name,
|
||||
page: "1",
|
||||
});
|
||||
group: activeGroup === name ? '' : name,
|
||||
page: '1',
|
||||
})
|
||||
}
|
||||
|
||||
function getDisplayValue(setting: Setting): string {
|
||||
return values[setting.key] ?? setting.value;
|
||||
return values[setting.key] ?? setting.value
|
||||
}
|
||||
|
||||
function updateValue(key: string, val: string) {
|
||||
setValues((prev) => ({ ...prev, [key]: val }));
|
||||
setValues((prev) => ({ ...prev, [key]: val }))
|
||||
setChangedKeys((prev) => {
|
||||
const next = new Set(prev);
|
||||
const next = new Set(prev)
|
||||
if (originalValues[key] !== undefined && val === originalValues[key]) {
|
||||
next.delete(key);
|
||||
next.delete(key)
|
||||
} else {
|
||||
next.add(key);
|
||||
next.add(key)
|
||||
}
|
||||
return next;
|
||||
});
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function revertChange(key: string) {
|
||||
setValues((prev) => {
|
||||
const next = { ...prev };
|
||||
delete next[key];
|
||||
return next;
|
||||
});
|
||||
const next = { ...prev }
|
||||
delete next[key]
|
||||
return next
|
||||
})
|
||||
setChangedKeys((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(key);
|
||||
return next;
|
||||
});
|
||||
const next = new Set(prev)
|
||||
next.delete(key)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
function updateParams(updates: Record<string, string>) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
const params = new URLSearchParams(searchParams.toString())
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value) params.set(key, value);
|
||||
else params.delete(key);
|
||||
if (value) params.set(key, value)
|
||||
else params.delete(key)
|
||||
}
|
||||
startTransition(() => {
|
||||
router.push(`?${params.toString()}`);
|
||||
});
|
||||
router.push(`?${params.toString()}`)
|
||||
})
|
||||
}
|
||||
|
||||
function handleSearch(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
updateParams({ search: searchValue, page: "1" });
|
||||
e.preventDefault()
|
||||
updateParams({ search: searchValue, page: '1' })
|
||||
}
|
||||
|
||||
function handleSaveAll() {
|
||||
if (changedKeys.size === 0) {
|
||||
toast.info("No changes to save.");
|
||||
return;
|
||||
toast.info('No changes to save.')
|
||||
return
|
||||
}
|
||||
const changedSettings: Record<string, string> = {};
|
||||
const changedSettings: Record<string, string> = {}
|
||||
for (const key of changedKeys) {
|
||||
changedSettings[key] = values[key];
|
||||
changedSettings[key] = values[key]
|
||||
}
|
||||
const count = changedKeys.size;
|
||||
const count = changedKeys.size
|
||||
run(() => saveEmulatorSettings({ settings: changedSettings }), {
|
||||
successMessage: `${count} setting(s) saved and synced with emulator.`,
|
||||
onSuccess: () => {
|
||||
setChangedKeys(new Set());
|
||||
setValues({});
|
||||
setChangedKeys(new Set())
|
||||
setValues({})
|
||||
// Refresh server data so the on-disk values reflect the save and the
|
||||
// originalValues snapshot rebuilds on next render.
|
||||
setOriginalValues({});
|
||||
router.refresh();
|
||||
setOriginalValues({})
|
||||
router.refresh()
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function jumpToPage(raw: string) {
|
||||
const n = parseInt(raw, 10);
|
||||
if (!Number.isFinite(n)) return;
|
||||
const clamped = Math.max(1, Math.min(pagination.lastPage, n));
|
||||
if (clamped !== pagination.page) updateParams({ page: String(clamped) });
|
||||
const n = parseInt(raw, 10)
|
||||
if (!Number.isFinite(n)) return
|
||||
const clamped = Math.max(1, Math.min(pagination.lastPage, n))
|
||||
if (clamped !== pagination.page) updateParams({ page: String(clamped) })
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -243,11 +249,11 @@ export function EmulatorSettingsForm({
|
||||
</button>
|
||||
)}
|
||||
{groupSummary.map(({ name, count }) => {
|
||||
const isActive = activeGroup === name;
|
||||
const isActive = activeGroup === name
|
||||
const color = (GROUP_BORDER_COLORS[name] || GROUP_BORDER_COLORS.general).replace(
|
||||
"border-l-",
|
||||
"bg-",
|
||||
);
|
||||
'border-l-',
|
||||
'bg-',
|
||||
)
|
||||
return (
|
||||
<button
|
||||
key={name}
|
||||
@@ -255,8 +261,8 @@ export function EmulatorSettingsForm({
|
||||
onClick={() => toggleGroup(name)}
|
||||
className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md border text-[10px] font-mono transition-colors ${
|
||||
isActive
|
||||
? "border-primary bg-primary/10 text-foreground"
|
||||
: "border-border bg-background text-muted-foreground hover:text-foreground hover:bg-accent"
|
||||
? 'border-primary bg-primary/10 text-foreground'
|
||||
: 'border-border bg-background text-muted-foreground hover:text-foreground hover:bg-accent'
|
||||
}`}
|
||||
title={isActive ? `Rimuovi filtro: ${name}` : `Filtra: ${name}`}
|
||||
disabled={isNavigating}
|
||||
@@ -265,7 +271,7 @@ export function EmulatorSettingsForm({
|
||||
{name}
|
||||
<span className="text-muted-foreground">{count}</span>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
@@ -273,18 +279,20 @@ export function EmulatorSettingsForm({
|
||||
{/* Settings list — flat rows with always-on inline editing */}
|
||||
<div className="rounded-md border bg-card divide-y">
|
||||
{settings.length === 0 ? (
|
||||
<div className="px-4 py-8 text-center text-sm text-muted-foreground">No settings found.</div>
|
||||
<div className="px-4 py-8 text-center text-sm text-muted-foreground">
|
||||
No settings found.
|
||||
</div>
|
||||
) : (
|
||||
settings.map((setting) => {
|
||||
const group = getSettingGroup(setting.key);
|
||||
const borderColor = GROUP_BORDER_COLORS[group] || GROUP_BORDER_COLORS.general;
|
||||
const isChanged = changedKeys.has(setting.key);
|
||||
const display = getDisplayValue(setting);
|
||||
const group = getSettingGroup(setting.key)
|
||||
const borderColor = GROUP_BORDER_COLORS[group] || GROUP_BORDER_COLORS.general
|
||||
const isChanged = changedKeys.has(setting.key)
|
||||
const display = getDisplayValue(setting)
|
||||
return (
|
||||
<div
|
||||
key={setting.key}
|
||||
className={`flex items-center gap-3 px-3 py-1.5 border-l-4 ${borderColor} ${
|
||||
isChanged ? "bg-[var(--admin-warning-subtle)] dark:bg-[var(--admin-warning-subtle)]" : ""
|
||||
isChanged ? 'bg-[var(--admin-warning-subtle)] dark:bg-[var(--admin-warning-subtle)]' : ''
|
||||
}`}
|
||||
>
|
||||
<code
|
||||
@@ -297,7 +305,7 @@ export function EmulatorSettingsForm({
|
||||
value={display}
|
||||
onChange={(e) => updateValue(setting.key, e.target.value)}
|
||||
className={`flex-1 h-8 text-xs font-mono ${
|
||||
isChanged ? "border-[var(--admin-warning-border)]" : ""
|
||||
isChanged ? 'border-[var(--admin-warning-border)]' : ''
|
||||
}`}
|
||||
/>
|
||||
{isChanged && (
|
||||
@@ -312,7 +320,7 @@ export function EmulatorSettingsForm({
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
@@ -323,7 +331,7 @@ export function EmulatorSettingsForm({
|
||||
<span>Per page</span>
|
||||
<Select
|
||||
value={String(pagination.perPage)}
|
||||
onValueChange={(v) => updateParams({ perPage: v, page: "1" })}
|
||||
onValueChange={(v) => updateParams({ perPage: v, page: '1' })}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-[80px]">
|
||||
<SelectValue />
|
||||
@@ -338,7 +346,7 @@ export function EmulatorSettingsForm({
|
||||
</Select>
|
||||
<span className="ml-2">
|
||||
{pagination.total === 0
|
||||
? "0 results"
|
||||
? '0 results'
|
||||
: `${(pagination.page - 1) * pagination.perPage + 1}–${Math.min(
|
||||
pagination.page * pagination.perPage,
|
||||
pagination.total,
|
||||
@@ -351,7 +359,7 @@ export function EmulatorSettingsForm({
|
||||
variant="outline"
|
||||
size="icon"
|
||||
disabled={pagination.page <= 1 || isNavigating}
|
||||
onClick={() => updateParams({ page: "1" })}
|
||||
onClick={() => updateParams({ page: '1' })}
|
||||
title="First page"
|
||||
>
|
||||
<ChevronsLeft className="h-4 w-4" />
|
||||
@@ -373,7 +381,7 @@ export function EmulatorSettingsForm({
|
||||
key={pagination.page}
|
||||
onBlur={(e) => jumpToPage(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") jumpToPage((e.target as HTMLInputElement).value);
|
||||
if (e.key === 'Enter') jumpToPage((e.target as HTMLInputElement).value)
|
||||
}}
|
||||
className="h-8 w-14 text-center text-xs"
|
||||
/>
|
||||
@@ -399,5 +407,5 @@ export function EmulatorSettingsForm({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user