184 lines
6.2 KiB
React
184 lines
6.2 KiB
React
|
|
import { useState, useEffect } from 'react'
|
||
|
|
|
||
|
|
function Field({ label, hint, children }) {
|
||
|
|
return (
|
||
|
|
<div>
|
||
|
|
<label className="block text-xs font-semibold text-gray-300 mb-1">
|
||
|
|
{label}
|
||
|
|
{hint && <span className="ml-1.5 text-gray-500 font-normal">{hint}</span>}
|
||
|
|
</label>
|
||
|
|
{children}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
const INPUT_CLS =
|
||
|
|
'w-full bg-gray-900 border border-gray-600 rounded-md px-3 py-1.5 text-sm text-gray-200 placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono'
|
||
|
|
|
||
|
|
const TEXTAREA_CLS = INPUT_CLS + ' resize-none'
|
||
|
|
|
||
|
|
export default function SettingsModal({ isOpen, onClose, settings, onSave }) {
|
||
|
|
const [form, setForm] = useState({ ...settings })
|
||
|
|
|
||
|
|
// Sync if parent settings change while modal is closed
|
||
|
|
useEffect(() => {
|
||
|
|
if (!isOpen) setForm({ ...settings })
|
||
|
|
}, [isOpen, settings])
|
||
|
|
|
||
|
|
if (!isOpen) return null
|
||
|
|
|
||
|
|
function set(key, value) {
|
||
|
|
setForm((f) => ({ ...f, [key]: value }))
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleSave() {
|
||
|
|
onSave(form)
|
||
|
|
onClose()
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleBackdropClick(e) {
|
||
|
|
if (e.target === e.currentTarget) onClose()
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
|
||
|
|
onClick={handleBackdropClick}
|
||
|
|
>
|
||
|
|
<div className="relative bg-gray-900 border border-gray-700 rounded-xl shadow-2xl w-full max-w-xl max-h-[90vh] flex flex-col">
|
||
|
|
{/* Modal header */}
|
||
|
|
<div className="flex items-center justify-between px-5 py-4 border-b border-gray-700">
|
||
|
|
<h2 className="text-base font-semibold text-white">Settings</h2>
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="text-gray-500 hover:text-white transition-colors"
|
||
|
|
title="Close"
|
||
|
|
>
|
||
|
|
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||
|
|
</svg>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Scrollable body */}
|
||
|
|
<div className="flex flex-col gap-5 overflow-y-auto px-5 py-5">
|
||
|
|
|
||
|
|
{/* Section: Paths */}
|
||
|
|
<div>
|
||
|
|
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||
|
|
File Paths
|
||
|
|
</p>
|
||
|
|
<div className="flex flex-col gap-3">
|
||
|
|
<Field label="P2P / COE CSV">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
placeholder="/path/to/p2p_coe_tests.csv"
|
||
|
|
value={form.p2pCoeCsvPath ?? ''}
|
||
|
|
onChange={(e) => set('p2pCoeCsvPath', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
<Field label="P3P CSV">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
placeholder="/path/to/p3p_tests.csv"
|
||
|
|
value={form.p3pCsvPath ?? ''}
|
||
|
|
onChange={(e) => set('p3pCsvPath', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
<Field label="REF Result Directory">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
placeholder="/path/to/ref/results"
|
||
|
|
value={form.refResultDir ?? ''}
|
||
|
|
onChange={(e) => set('refResultDir', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
<Field label="DUT Result Directory">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
placeholder="/path/to/dut/results"
|
||
|
|
value={form.dutResultDir ?? ''}
|
||
|
|
onChange={(e) => set('dutResultDir', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<hr className="border-gray-700" />
|
||
|
|
|
||
|
|
{/* Section: Test Exclusion */}
|
||
|
|
<div>
|
||
|
|
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||
|
|
Test Exclusion
|
||
|
|
</p>
|
||
|
|
<Field label="Excluded Test IDs" hint="(comma-separated)">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
placeholder="P2PRXAX001, COERXBE002…"
|
||
|
|
value={form.testExclusion ?? ''}
|
||
|
|
onChange={(e) => set('testExclusion', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<hr className="border-gray-700" />
|
||
|
|
|
||
|
|
{/* Section: Holidays */}
|
||
|
|
<div>
|
||
|
|
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||
|
|
Holidays
|
||
|
|
</p>
|
||
|
|
<Field label="Holiday Dates" hint="(comma-separated, YYYY-MM-DD)">
|
||
|
|
<input
|
||
|
|
type="text"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
placeholder="2026-07-04, 2026-12-25…"
|
||
|
|
value={form.holidays ?? ''}
|
||
|
|
onChange={(e) => set('holidays', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<hr className="border-gray-700" />
|
||
|
|
|
||
|
|
{/* Section: Schedule */}
|
||
|
|
<div>
|
||
|
|
<p className="text-[11px] font-bold uppercase tracking-widest text-gray-500 mb-3">
|
||
|
|
Schedule
|
||
|
|
</p>
|
||
|
|
<Field label="Start Date Override" hint="(YYYY-MM-DD, leave blank for today)">
|
||
|
|
<input
|
||
|
|
type="date"
|
||
|
|
className={INPUT_CLS}
|
||
|
|
value={form.startDateOverride ?? ''}
|
||
|
|
onChange={(e) => set('startDateOverride', e.target.value)}
|
||
|
|
/>
|
||
|
|
</Field>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Footer */}
|
||
|
|
<div className="flex justify-end gap-2 px-5 py-4 border-t border-gray-700">
|
||
|
|
<button
|
||
|
|
onClick={onClose}
|
||
|
|
className="px-4 py-1.5 text-sm text-gray-300 bg-gray-800 border border-gray-600 rounded-md hover:bg-gray-700 transition-colors"
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</button>
|
||
|
|
<button
|
||
|
|
onClick={handleSave}
|
||
|
|
className="px-4 py-1.5 text-sm font-semibold text-white bg-blue-600 hover:bg-blue-500 border border-blue-500 rounded-md transition-colors"
|
||
|
|
>
|
||
|
|
Save
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|