feat: add Node ACS admin workflows

This commit is contained in:
unknown
2026-07-16 11:00:09 +09:00
parent a25872394a
commit c6f342a861
24 changed files with 3442 additions and 266 deletions

View File

@@ -1,6 +1,8 @@
import React, { useState } from 'react';
import { reportDownloadUrl } from '../api';
import React, { useEffect, useState } from 'react';
import { listReportVisits, reportDownloadUrl } from '../api';
import { DatePickerField } from '../components/DatePickerField';
import { VisitRequestView } from '../types';
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
const pad = (n: number) => String(n).padStart(2, '0');
@@ -18,6 +20,27 @@ function firstOfMonthISO(): string {
export const ReportPage: React.FC = () => {
const [from, setFrom] = useState(firstOfMonthISO());
const [to, setTo] = useState(todayISO());
const [items, setItems] = useState<VisitRequestView[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const load = () => {
setLoading(true);
setError(null);
listReportVisits(from, to)
.then(setItems)
.catch((e) => {
setItems([]);
setError(e instanceof Error ? e.message : '보고서 조회에 실패했습니다.');
})
.finally(() => setLoading(false));
};
useEffect(() => {
load();
// Load the default month range once; explicit 조회 handles later date changes.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onDownload = () => {
// Trigger a download in-place via a temporary anchor. Using window.open left
@@ -37,7 +60,6 @@ export const ReportPage: React.FC = () => {
<div className="page-head"><h2> </h2></div>
<div className="card">
<p className="muted"> [ ] , (.xlsx) . ( )</p>
<div className="report-row">
<label className="field">
<span></span>
@@ -47,9 +69,57 @@ export const ReportPage: React.FC = () => {
<span></span>
<DatePickerField value={to} onChange={setTo} />
</label>
<button className="btn-ghost" onClick={load} disabled={loading}></button>
<button className="btn-primary" onClick={onDownload}> </button>
</div>
</div>
{error && <div className="alert alert-error">{error}</div>}
<div className="card">
<div className="page-head">
<h3> ({items.length})</h3>
</div>
{loading ? (
<p className="muted"> ...</p>
) : items.length === 0 ? (
<p className="muted"> .</p>
) : (
<table className="table">
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{items.map((r) => (
<tr key={r.id}>
<td>{r.visitorName}</td>
<td>{r.company || '-'}</td>
<td>{r.contact || '-'}</td>
<td>{r.zoneName || '-'}</td>
<td>{r.hostName}</td>
<td>{r.purpose || '-'}</td>
<td>{r.workName || '-'}</td>
<td>{formatDateTime(r.visitFrom)}</td>
<td>{formatDateTime(r.visitTo)}</td>
<td><span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span></td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
);
};