mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 11:11:10 +00:00
admin v2: implement design from ui_design/v2/plugin/admin.html
- Design system: v2 CSS variables (surface-0/1/2, border-hair, accent-amber/cyan/red/green/blue)
and utility classes (.btn, .inp, .pill, .chip, .bracket, .panel, .seg, .swatch,
.type-sq, .grid-bg, .ibtn, .checkbox, .tab); v1 az-* names aliased to v2 vars
so other pages still render. Google Fonts (IBM Plex Sans + JetBrains Mono)
loaded via <link> in index.html <head> to avoid FOUT.
- Header rebuilt to v2: amber wordmark + // divider, amber-bordered flight pill
with cyan live dot, tab-style nav with amber underline on active, LINK status
pill, cog + sign-out icon buttons.
- AdminPage rewritten to 3-column layout (340 / flex / 280):
- Detection Classes: search + ADD button, table with #/Name/Hex/Ops columns,
name-only inline edit with ringed swatch, sibling-row error alert.
- AI Recognition Engine + GPS Device Link panels with corner-bracket borders,
number steppers, segmented protocol control, dashed telemetry footers.
Hooks (useAiSettings, useGpsSettings) seed factory defaults so the UI is
interactive when GET fails (no backend).
- Default Aircrafts: P/C/F type chips, isDefault star toggle, + ADD AIRCRAFT
modal with model/type/resolution/maxMinutes/default fields.
- Co-located components: Modal (backdrop + ESC + body-scroll-lock),
NumberStepper (▲▼ with clamp on click but not on typing), ClassEditRow.
- Types: Aircraft extended with FixedWing + optional resolution/maxMinutes;
new AiRecognitionSettings/Telemetry, GpsDeviceSettings/Telemetry, GpsProtocol.
- Endpoints: /api/admin/ai-settings, /api/admin/gps-settings (+ /ping, /reconnect).
POST /api/flights/aircrafts (plural REST collection).
- MSW: stateful admin-settings handler with resetAdminSettingsSeed() wired into
tests/setup.ts. Aircraft seed expanded to 6 entries matching the mockup.
- i18n: full admin.{classes,aiEngine,gpsDevice,aircrafts} key sets in en+ua;
nav.dataset shortened to "Dataset"; obsolete users-management keys removed.
- Tests: new AdminPage AI/GPS/aircraft test suites; admin_class_edit selectors
updated for the name-only inline editor and the modal-based add flow.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { useEffect, type ReactNode, type KeyboardEvent, type MouseEvent } from 'react'
|
||||
|
||||
interface ModalProps {
|
||||
open: boolean
|
||||
title: ReactNode
|
||||
onClose: () => void
|
||||
width?: number
|
||||
footer?: ReactNode
|
||||
children: ReactNode
|
||||
closeLabel?: string
|
||||
}
|
||||
|
||||
export function Modal({ open, title, onClose, width = 420, footer, children, closeLabel = 'Close' }: ModalProps) {
|
||||
useEffect(() => {
|
||||
if (!open) return
|
||||
const onKey = (e: globalThis.KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', onKey)
|
||||
// Lock body scroll while the modal is open.
|
||||
const prev = document.body.style.overflow
|
||||
document.body.style.overflow = 'hidden'
|
||||
return () => {
|
||||
document.removeEventListener('keydown', onKey)
|
||||
document.body.style.overflow = prev
|
||||
}
|
||||
}, [open, onClose])
|
||||
|
||||
if (!open) return null
|
||||
|
||||
const onBackdropClick = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (e.target === e.currentTarget) onClose()
|
||||
}
|
||||
const onPanelKey = (e: KeyboardEvent<HTMLDivElement>) => {
|
||||
// Stop Escape from bubbling to other key handlers in the page; the
|
||||
// document listener above already handles closing.
|
||||
if (e.key === 'Escape') e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={typeof title === 'string' ? title : undefined}
|
||||
onClick={onBackdropClick}
|
||||
style={{
|
||||
position: 'fixed', inset: 0, zIndex: 100,
|
||||
background: 'rgba(0, 0, 0, 0.6)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="bracket panel"
|
||||
onKeyDown={onPanelKey}
|
||||
style={{ width, padding: 20 }}
|
||||
>
|
||||
<span className="br" />
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="sect-head">{title}</span>
|
||||
<button type="button" onClick={onClose} className="ibtn" aria-label={closeLabel} title={closeLabel}>
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8">
|
||||
<line x1="18" y1="6" x2="6" y2="18" />
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">{children}</div>
|
||||
|
||||
{footer && (
|
||||
<div
|
||||
className="mt-5 pt-4 flex items-center justify-end gap-2"
|
||||
style={{ borderTop: '1px dashed var(--border-hair)' }}
|
||||
>
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user