mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
75815fa022
The ModTools template refresh introduced ~80 hardcoded English strings
(labels, placeholders, tooltips, empty-state copy, button text). Move
every one of them onto the modtools.* namespace and read via
LocalizeText so the panels translate alongside the rest of the client.
UITexts.example (versioned template) extended with the full set:
modtools.window.* Launcher box (toolbar item, tools,
selected-user state, ticket count)
modtools.userinfo.* User info card — already had the
legacy modtools.userinfo.{userName,
cfhCount, …} keys from before; added
refresh tooltip, presence pill labels
(in_room / online / offline with
matching .title tooltips), section
headings, action button labels, stat
card labels
modtools.roominfo.* Room info card — title, refresh, loading,
owner pill (here/away + tooltips), stat
labels, action buttons, moderate panel
heading + checkboxes + textarea
placeholder + caution/alert CTAs
modtools.user.message.* Send-message dialog (recipient label,
body label, placeholder, char counter,
empty state, send button)
modtools.user.modaction.* Mod Action form — header, sanctioning
label, 3-step section titles, select
placeholders, message label + optional
note, message placeholder, preview
heading, default/apply buttons, every
sendAlert error message
modtools.user.visits.* Room visits — title, header strip
heading, entry count (singular/plural),
empty state, column headers, visit
button + tooltip
modtools.user.chatlog.* User chatlog — title (with username
variant), loading state
modtools.room.chatlog.* Room chatlog title
modtools.chatlog.* Shared ChatlogView — column headers,
empty state, room-separator Visit/Tools
buttons
modtools.tickets.* Tickets window — title, tab labels
(open/mine/picked), column headers,
empty states, action buttons (pick/
handle/release), issue resolution
window (title, label, details heading,
field labels, chatlog toggle, resolve-as
heading, resolution buttons, release
back to queue), CFH chatlog title
The same 130 entries land in Nitro-Files/.../UITexts.json (runtime).
Both files validate as JSON. The runtime additions take effect on
next client reload; the template additions ship the strings to any
fresh deploy.
Notes:
- The MOD_ACTION_DEFINITIONS sanction names ("Alert", "Mute 1h",
"Ban 18h" …) stay hardcoded for now since they're keyed off
server-side action IDs that don't have an existing locale key
convention. Worth a follow-up if needed.
- help.cfh.topic.* keys (CFH topic display names) are already in
ExternalTexts.json and were already read via LocalizeText, so
they didn't need changes.
typecheck + vitest 214/214 + lint:hooks all clean.
88 lines
4.7 KiB
TypeScript
88 lines
4.7 KiB
TypeScript
import { GetRoomVisitsMessageComposer, RoomVisitsData, RoomVisitsEvent } from '@nitrots/nitro-renderer';
|
|
import { FC, useEffect, useState } from 'react';
|
|
import { FaClock, FaDoorOpen, FaSignInAlt } from 'react-icons/fa';
|
|
import { LocalizeText, SendMessageComposer, TryVisitRoom } from '../../../../api';
|
|
import { DraggableWindowPosition, InfiniteScroll, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
|
import { useMessageEvent } from '../../../../hooks';
|
|
|
|
interface ModToolsUserRoomVisitsViewProps
|
|
{
|
|
userId: number;
|
|
onCloseClick: () => void;
|
|
}
|
|
|
|
export const ModToolsUserRoomVisitsView: FC<ModToolsUserRoomVisitsViewProps> = props =>
|
|
{
|
|
const { userId = null, onCloseClick = null } = props;
|
|
const [ roomVisitData, setRoomVisitData ] = useState<RoomVisitsData>(null);
|
|
|
|
useMessageEvent<RoomVisitsEvent>(RoomVisitsEvent, event =>
|
|
{
|
|
const parser = event.getParser();
|
|
|
|
if(parser.data.userId !== userId) return;
|
|
|
|
setRoomVisitData(parser.data);
|
|
});
|
|
|
|
useEffect(() =>
|
|
{
|
|
SendMessageComposer(new GetRoomVisitsMessageComposer(userId));
|
|
}, [ userId ]);
|
|
|
|
if(!userId) return null;
|
|
|
|
const rows = roomVisitData?.rooms ?? [];
|
|
const isEmpty = rows.length === 0;
|
|
|
|
const countLabel = rows.length === 1
|
|
? LocalizeText('modtools.user.visits.entries.one', [ 'count' ], [ rows.length.toString() ])
|
|
: LocalizeText('modtools.user.visits.entries.many', [ 'count' ], [ rows.length.toString() ]);
|
|
|
|
return (
|
|
<NitroCardView className="nitro-mod-tools-user-visits min-w-[400px] max-w-[460px] max-h-[460px]" theme="primary-slim" windowPosition={ DraggableWindowPosition.TOP_LEFT }>
|
|
<NitroCardHeaderView headerText={ LocalizeText('modtools.user.visits.title') } onCloseClick={ onCloseClick } />
|
|
<NitroCardContentView className="text-black" gap={ 1 }>
|
|
{/* Header strip */}
|
|
<div className="flex items-center gap-2 bg-gradient-to-r from-sky-50 to-transparent rounded p-2 border border-sky-100">
|
|
<FaDoorOpen className="text-sky-600 shrink-0" size={ 14 } />
|
|
<div className="text-sm font-semibold leading-tight grow">{ LocalizeText('modtools.user.visits.recent') }</div>
|
|
<span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium border bg-white border-zinc-200">
|
|
{ countLabel }
|
|
</span>
|
|
</div>
|
|
|
|
{/* Table head */}
|
|
<div className="grid grid-cols-[60px_1fr_80px] gap-2 text-[.7rem] uppercase tracking-wide opacity-60 font-semibold border-b border-zinc-200 pb-1 px-1">
|
|
<div className="flex items-center gap-1"><FaClock size={ 10 } /> { LocalizeText('modtools.user.visits.time') }</div>
|
|
<div>{ LocalizeText('modtools.user.visits.room') }</div>
|
|
<div className="text-right">{ LocalizeText('modtools.user.visits.action') }</div>
|
|
</div>
|
|
|
|
{/* Rows */}
|
|
{ isEmpty
|
|
? <div className="flex flex-col items-center justify-center gap-1 py-6 opacity-50 text-sm">
|
|
<FaDoorOpen size={ 22 } />
|
|
<span>{ LocalizeText('modtools.user.visits.empty') }</span>
|
|
</div>
|
|
: <div className="flex flex-col grow min-h-0 overflow-hidden">
|
|
<InfiniteScroll rowRender={ row => (
|
|
<div className="grid grid-cols-[60px_1fr_80px] gap-2 items-center px-1 py-1.5 text-sm border-b border-zinc-100 even:bg-black/[0.02] hover:bg-sky-50 transition-colors">
|
|
<span className="font-mono text-[.75rem] opacity-70 tabular-nums">
|
|
{ row.enterHour.toString().padStart(2, '0') }:{ row.enterMinute.toString().padStart(2, '0') }
|
|
</span>
|
|
<span className="truncate font-medium">{ row.roomName }</span>
|
|
<button
|
|
className="inline-flex items-center justify-end gap-1 text-sky-700 hover:text-sky-900 hover:underline text-xs"
|
|
onClick={ () => TryVisitRoom(row.roomId) }
|
|
title={ LocalizeText('modtools.user.visits.visit.title') }>
|
|
<FaSignInAlt size={ 10 } /> { LocalizeText('modtools.user.visits.visit') }
|
|
</button>
|
|
</div>
|
|
) } rows={ rows } />
|
|
</div> }
|
|
</NitroCardContentView>
|
|
</NitroCardView>
|
|
);
|
|
};
|