mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
feat(housekeeping): in-client admin panel
Adds the Housekeeping in-client admin panel — a Modtools-adjacent
surface that runs entirely inside the React client, talking to the
emulator over the existing wire instead of a separate REST/CMS layer.
Surface:
- `src/components/housekeeping/` — panel shell + 5 tabs (Dashboard,
Users, Rooms, Economy, Audit). Each tab drives one domain of the
matching emulator handlers (find/sanction/admin/economy/catalog/
hotel-wide).
- `src/api/housekeeping/` — composer/parser orchestration:
`HousekeepingApi.ts` exposes 30+ typed actions, each one running
through `runHkAction()` which awaits the shared
`HousekeepingActionResultEvent` correlated by action key.
- `src/hooks/housekeeping/` — `useHousekeeping` (the public hook),
`useHousekeepingStore` (useBetween singleton: shared selection +
audit polling + sanction templates), `useHousekeepingActions`,
`useHousekeepingConfirm`.
- `src/api/nitro/awaitMessageEvent.ts` — Promise adapter over
`CommunicationManager.subscribeMessage` with a sync `select`
callback that snapshots the parser INSIDE the subscribe handler
before the renderer recycles the parser instance after the
Promise resolves.
- `public/configuration/housekeeping-texts-{en,it}.example` —
149 EN + 149 IT i18n keys under `housekeeping.*` for every panel
string + every server-side error slug the emulator may emit.
Wiring (additive only):
- `src/components/MainView.tsx` — `<HousekeepingView />` mounted
alongside `<ModToolsView />`.
- `src/api/index.ts`, `src/hooks/index.ts`, `src/api/nitro/index.ts`
— added the `housekeeping` and `awaitMessageEvent` re-exports.
Wire contract: pairs against the Arcturus PR (#120 on
duckietm/Arcturus-Morningstar-Extended) and the renderer PR (#77 on
duckietm/Nitro_Render_V3). Incoming events 9100..9129, outgoing
composers 9200..9207. Permission gate `acc_housekeeping` enforced
server-side; the panel is hidden client-side via
`housekeeping.enabled` in the runtime ui-config.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { FC, useEffect } from 'react';
|
||||
import { FaCheckCircle, FaExclamationTriangle, FaTimes } from 'react-icons/fa';
|
||||
import { LocalizeText } from '../../api';
|
||||
import { useHousekeepingStore } from '../../hooks';
|
||||
|
||||
const localizeOrPassthrough = (key: string | null): string =>
|
||||
{
|
||||
if(!key) return '';
|
||||
if(!key.includes('.')) return key;
|
||||
|
||||
const localized = LocalizeText(key);
|
||||
|
||||
return (localized === key) ? key : localized;
|
||||
};
|
||||
|
||||
const AUTO_DISMISS_MS = 4000;
|
||||
|
||||
export const HousekeepingStatusBanner: FC = () =>
|
||||
{
|
||||
const { lastError, lastSuccess, clearStatus, isActionPending } = useHousekeepingStore();
|
||||
const visible = !!(lastError || lastSuccess);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
if(!lastSuccess) return;
|
||||
|
||||
const handle = window.setTimeout(() => clearStatus(), AUTO_DISMISS_MS);
|
||||
|
||||
return () => window.clearTimeout(handle);
|
||||
}, [ lastSuccess, clearStatus ]);
|
||||
|
||||
if(!visible && !isActionPending) return null;
|
||||
|
||||
if(isActionPending && !visible)
|
||||
{
|
||||
return (
|
||||
<div className="flex items-center gap-2 px-2 py-1 text-xs bg-zinc-100 border-y border-zinc-200 text-zinc-700">
|
||||
<span className="inline-block h-3 w-3 rounded-full border-2 border-zinc-400 border-t-transparent animate-spin" />
|
||||
<span>{ LocalizeText('housekeeping.action.pending') }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isError = !!lastError;
|
||||
const message = localizeOrPassthrough(lastError ?? lastSuccess);
|
||||
const tone = isError
|
||||
? 'bg-rose-100 border-rose-200 text-rose-900'
|
||||
: 'bg-emerald-100 border-emerald-200 text-emerald-900';
|
||||
const Icon = isError ? FaExclamationTriangle : FaCheckCircle;
|
||||
|
||||
return (
|
||||
<div className={ `flex items-center gap-2 px-2 py-1 text-xs border-y ${ tone }` } role="status">
|
||||
<Icon size={ 12 } />
|
||||
<span className="grow truncate">{ message }</span>
|
||||
<button
|
||||
className="inline-flex items-center justify-center w-5 h-5 rounded hover:bg-black/10"
|
||||
onClick={ () => clearStatus() }
|
||||
title={ LocalizeText('housekeeping.status.dismiss') }>
|
||||
<FaTimes size={ 10 } />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user