Files
acc-server-manager-web/src/components/server/ServerConfigurationTabs.tsx

306 lines
9.9 KiB
TypeScript
Executable File

'use client';
import { type Configurations, ServerTab } from '@/lib/schemas/config';
import { ConfigurationEditor } from '@/components/configuration/ConfigurationEditor';
import { AssistRulesEditor } from '@/components/configuration/AssistRulesEditor';
import { EventConfigEditor } from '@/components/configuration/EventConfigEditor';
import { EventRulesEditor } from '@/components/configuration/EventRulesEditor';
import { ServerSettingsEditor } from '@/components/configuration/ServerSettingsEditor';
import { StatisticsDashboard } from '@/components/statistics/StatisticsDashboard';
import { LeaderboardManager } from '@/components/leaderboard/LeaderboardManager';
import { ActivityLogTable } from '@/components/activityLog/ActivityLogTable';
import { ServerLogViewer } from '@/components/server/ServerLogViewer';
import { PresetPickerModal } from '@/components/server/PresetPickerModal';
import { SavePresetModal } from '@/components/server/SavePresetModal';
import { useState } from 'react';
import { StateHistoryStats, hasPermission, type User } from '@/lib/schemas';
import { Leaderboard } from '@/lib/schemas/leaderboard';
import {
bulkUpdateConfigurationsAction,
getConfigurationsAction
} from '@/lib/actions/configuration';
interface ServerConfigurationTabsProps {
serverId: string;
configurations: Configurations;
statistics: StateHistoryStats;
leaderboard: Leaderboard;
user: User;
}
const tabs = [
{ id: ServerTab.statistics, name: 'Statistics', icon: '📊' },
{ id: ServerTab.configuration, name: 'Configuration', icon: '⚙️' },
{ id: ServerTab.assistRules, name: 'Assist Rules', icon: '🚗' },
{ id: ServerTab.event, name: 'Event Config', icon: '🏁' },
{ id: ServerTab.eventRules, name: 'Event Rules', icon: '📋' },
{ id: ServerTab.settings, name: 'Server Settings', icon: '🔧' },
{ id: ServerTab.leaderboard, name: 'Leaderboard', icon: '🏆' },
{ id: ServerTab.activityLog, name: 'History', icon: '📋' },
{ id: ServerTab.logs, name: 'Logs', icon: '📄' }
];
const configTabs = new Set([
ServerTab.configuration,
ServerTab.assistRules,
ServerTab.event,
ServerTab.eventRules,
ServerTab.settings
]);
export function ServerConfigurationTabs({
serverId,
configurations,
statistics,
leaderboard,
user
}: ServerConfigurationTabsProps) {
const canViewPresets = hasPermission(user, 'preset.view');
const canCreatePresets = hasPermission(user, 'preset.create');
const [currentTab, setCurrentTab] = useState(ServerTab.statistics);
const [configData, setConfigData] = useState(configurations.configuration);
const [assistData, setAssistData] = useState(configurations.assistRules);
const [eventData, setEventData] = useState(configurations.event);
const [eventRulesData, setEventRulesData] = useState(configurations.eventRules);
const [settingsData, setSettingsData] = useState(configurations.settings);
const [dirtyConfigs, setDirtyConfigs] = useState<Set<keyof Configurations>>(new Set());
const [restart, setRestart] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const [saveSuccess, setSaveSuccess] = useState(false);
const [isPresetModalOpen, setIsPresetModalOpen] = useState(false);
const [isSavePresetModalOpen, setIsSavePresetModalOpen] = useState(false);
const reloadConfigs = async () => {
const result = await getConfigurationsAction(serverId);
if (!result.success) return;
setConfigData(result.data.configuration);
setAssistData(result.data.assistRules);
setEventData(result.data.event);
setEventRulesData(result.data.eventRules);
setSettingsData(result.data.settings);
setDirtyConfigs(new Set());
};
const markDirty = (key: keyof Configurations) =>
setDirtyConfigs((prev) => new Set(prev).add(key));
const handleSaveAll = async () => {
if (dirtyConfigs.size === 0) return;
setIsSubmitting(true);
setSaveError(null);
setSaveSuccess(false);
const payload: Partial<Configurations> = {};
if (dirtyConfigs.has('configuration')) payload.configuration = configData;
if (dirtyConfigs.has('assistRules')) payload.assistRules = assistData;
if (dirtyConfigs.has('event')) payload.event = eventData;
if (dirtyConfigs.has('eventRules')) payload.eventRules = eventRulesData;
if (dirtyConfigs.has('settings')) payload.settings = settingsData;
const result = await bulkUpdateConfigurationsAction(serverId, payload, restart);
if (result.success) {
setDirtyConfigs(new Set());
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 3000);
} else {
setSaveError(result.message);
}
setIsSubmitting(false);
};
const isConfigTab = configTabs.has(currentTab);
const renderTabContent = () => {
switch (currentTab) {
case ServerTab.statistics:
return <StatisticsDashboard stats={statistics} />;
case ServerTab.configuration:
return (
<ConfigurationEditor
formData={configData}
disabled={isSubmitting}
onFormDataChange={(data) => {
setConfigData(data);
markDirty('configuration');
}}
/>
);
case ServerTab.assistRules:
return (
<AssistRulesEditor
formData={assistData}
disabled={isSubmitting}
onFormDataChange={(data) => {
setAssistData(data);
markDirty('assistRules');
}}
/>
);
case ServerTab.event:
return (
<EventConfigEditor
formData={eventData}
disabled={isSubmitting}
onFormDataChange={(data) => {
setEventData(data);
markDirty('event');
}}
/>
);
case ServerTab.eventRules:
return (
<EventRulesEditor
formData={eventRulesData}
disabled={isSubmitting}
onFormDataChange={(data) => {
setEventRulesData(data);
markDirty('eventRules');
}}
/>
);
case ServerTab.settings:
return (
<ServerSettingsEditor
formData={settingsData}
disabled={isSubmitting}
onFormDataChange={(data) => {
setSettingsData(data);
markDirty('settings');
}}
/>
);
case ServerTab.leaderboard:
return <LeaderboardManager serverId={serverId} initialData={leaderboard} />;
case ServerTab.activityLog:
return <ActivityLogTable serverId={serverId} />;
case ServerTab.logs:
return <ServerLogViewer serverId={serverId} />;
default:
return (
<div className="py-12 text-center">
<div className="mb-4 text-6xl">🚧</div>
<h3 className="mb-2 text-xl font-semibold text-white">Tab Not Found</h3>
<p className="text-gray-400">The requested tab could not be found.</p>
</div>
);
}
};
return (
<div className="flex flex-1 flex-col overflow-hidden">
<nav
className="border-border-muted flex overflow-x-auto border-b px-5 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
aria-label="Tabs"
>
{tabs.map((tab) => {
const isActive = currentTab === tab.id;
return (
<button
key={tab.id}
onClick={() => setCurrentTab(tab.id)}
className={`flex shrink-0 items-center gap-1.5 border-b-2 px-3 py-3 text-sm font-medium whitespace-nowrap transition-colors ${
isActive
? 'border-orange text-primary'
: 'text-muted hover:text-primary border-transparent'
}`}
>
<span className="text-sm">{tab.icon}</span>
<span>{tab.name}</span>
</button>
);
})}
</nav>
{isConfigTab && (
<div className="border-border-muted bg-overlay flex items-center justify-between border-b px-5 py-3">
<div className="flex items-center gap-3">
{saveError && (
<ul className="flex flex-col gap-0.5">
{saveError.split('\n').map((line, i) => (
<li key={i} className="text-red text-sm">• {line}</li>
))}
</ul>
)}
{saveSuccess && <p className="text-green text-sm">All configurations saved.</p>}
</div>
<div className="flex items-center gap-4">
<label className="text-muted flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={restart}
onChange={(e) => setRestart(e.target.checked)}
className="border-border bg-overlay accent-green focus:ring-blue h-4 w-4 rounded focus:ring-1"
/>
Restart after saving
</label>
{canCreatePresets && (
<button
onClick={() => setIsSavePresetModalOpen(true)}
disabled={isSubmitting}
className="border-border text-secondary hover:bg-hover rounded-md border px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40"
>
Save as Preset
</button>
)}
{canViewPresets && (
<button
onClick={() => setIsPresetModalOpen(true)}
disabled={isSubmitting}
className="border-border text-secondary hover:bg-hover rounded-md border px-4 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-40"
>
Load Preset
</button>
)}
<button
onClick={handleSaveAll}
disabled={isSubmitting || dirtyConfigs.size === 0}
className="bg-btn-green border-btn-green hover:bg-btn-green-hover rounded-md border px-5 py-2 text-sm font-medium text-white transition-colors disabled:cursor-not-allowed disabled:opacity-40"
>
{isSubmitting ? 'Saving…' : 'Save All'}
</button>
</div>
</div>
)}
<div className="flex-1 overflow-y-auto p-5">{renderTabContent()}</div>
<SavePresetModal
isOpen={isSavePresetModalOpen}
onClose={() => setIsSavePresetModalOpen(false)}
configurations={{
configuration: configData,
assistRules: assistData,
event: eventData,
eventRules: eventRulesData,
settings: settingsData
}}
/>
<PresetPickerModal
isOpen={isPresetModalOpen}
onClose={() => setIsPresetModalOpen(false)}
serverId={serverId}
onApplied={async () => {
await reloadConfigs();
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 3000);
}}
/>
</div>
);
}