Files
Nitro-V3/src/components/housekeeping/HousekeepingStatusBanner.tsx
T
simoleo89 eeab548917 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.
2026-05-24 16:38:16 +02:00

64 lines
2.2 KiB
TypeScript

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>
);
};