Compare commits
3 Commits
3efe493f9f
...
eec18e7179
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eec18e7179 | ||
|
|
dee612fc5c | ||
|
|
7cbc660679 |
@@ -44,6 +44,8 @@ export const DashboardPage: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDashboard();
|
loadDashboard();
|
||||||
|
const t = setInterval(loadDashboard, 3000);
|
||||||
|
return () => clearInterval(t);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const deleteCancelled = async (target: VisitRequestView) => {
|
const deleteCancelled = async (target: VisitRequestView) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { listReportVisits, reportDownloadUrl } from '../api';
|
import { listReportVisits, listTodayAccess, reportDownloadUrl } from '../api';
|
||||||
import { DatePickerField } from '../components/DatePickerField';
|
import { DatePickerField } from '../components/DatePickerField';
|
||||||
import { VisitRequestView } from '../types';
|
import { AccessRecord, VisitRequestView } from '../types';
|
||||||
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
|
import { STATUS_CLASS, STATUS_LABEL, formatDateTime } from '../status';
|
||||||
|
|
||||||
const pad = (n: number) => String(n).padStart(2, '0');
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
@@ -21,21 +21,41 @@ export const ReportPage: React.FC = () => {
|
|||||||
const [from, setFrom] = useState(firstOfMonthISO());
|
const [from, setFrom] = useState(firstOfMonthISO());
|
||||||
const [to, setTo] = useState(todayISO());
|
const [to, setTo] = useState(todayISO());
|
||||||
const [items, setItems] = useState<VisitRequestView[]>([]);
|
const [items, setItems] = useState<VisitRequestView[]>([]);
|
||||||
|
const [accessRecords, setAccessRecords] = useState<AccessRecord[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
listReportVisits(from, to)
|
Promise.all([
|
||||||
.then(setItems)
|
listReportVisits(from, to),
|
||||||
|
listTodayAccess().catch(() => [] as AccessRecord[]),
|
||||||
|
])
|
||||||
|
.then(([visits, records]) => {
|
||||||
|
setItems(visits);
|
||||||
|
setAccessRecords(records);
|
||||||
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
setItems([]);
|
setItems([]);
|
||||||
|
setAccessRecords([]);
|
||||||
setError(e instanceof Error ? e.message : '보고서 조회에 실패했습니다.');
|
setError(e instanceof Error ? e.message : '보고서 조회에 실패했습니다.');
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const accessById = new Map(accessRecords.map((r) => [r.visitRequestId, r]));
|
||||||
|
const statusBadge = (r: VisitRequestView) => {
|
||||||
|
const access = accessById.get(r.id);
|
||||||
|
if (access?.inside) {
|
||||||
|
return { className: 'green', label: '재실중' };
|
||||||
|
}
|
||||||
|
if (access?.checkOutAt) {
|
||||||
|
return { className: 'gray', label: '퇴장' };
|
||||||
|
}
|
||||||
|
return { className: STATUS_CLASS[r.status], label: STATUS_LABEL[r.status] };
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load();
|
load();
|
||||||
// Load the default month range once; explicit 조회 handles later date changes.
|
// Load the default month range once; explicit 조회 handles later date changes.
|
||||||
@@ -113,7 +133,12 @@ export const ReportPage: React.FC = () => {
|
|||||||
<td>{r.workName || '-'}</td>
|
<td>{r.workName || '-'}</td>
|
||||||
<td>{formatDateTime(r.visitFrom)}</td>
|
<td>{formatDateTime(r.visitFrom)}</td>
|
||||||
<td>{formatDateTime(r.visitTo)}</td>
|
<td>{formatDateTime(r.visitTo)}</td>
|
||||||
<td><span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span></td>
|
<td>
|
||||||
|
{(() => {
|
||||||
|
const badge = statusBadge(r);
|
||||||
|
return <span className={`badge badge-${badge.className}`}>{badge.label}</span>;
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { cancelVisitRequest, deleteVisitRequest, listVisitRequests, uploadVisitRequests } from '../api';
|
import { cancelVisitRequest, deleteVisitRequest, listTodayAccess, listVisitRequests, uploadVisitRequests } from '../api';
|
||||||
import { VisitRequestView } from '../types';
|
import { AccessRecord, VisitRequestView } from '../types';
|
||||||
import { STATUS_CLASS, STATUS_LABEL, formatVisitRange } from '../status';
|
import { STATUS_CLASS, STATUS_LABEL, formatVisitRange } from '../status';
|
||||||
import { Dialog } from '../components/Dialog';
|
import { Dialog } from '../components/Dialog';
|
||||||
import { useAuth } from '../auth/AuthContext';
|
import { useAuth } from '../auth/AuthContext';
|
||||||
|
|
||||||
export const VisitRequestListPage: React.FC = () => {
|
export const VisitRequestListPage: React.FC = () => {
|
||||||
const [items, setItems] = useState<VisitRequestView[]>([]);
|
const [items, setItems] = useState<VisitRequestView[]>([]);
|
||||||
|
const [accessRecords, setAccessRecords] = useState<AccessRecord[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [notice, setNotice] = useState<string | null>(null);
|
const [notice, setNotice] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -18,13 +19,35 @@ export const VisitRequestListPage: React.FC = () => {
|
|||||||
|
|
||||||
const load = () => {
|
const load = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
listVisitRequests()
|
Promise.all([
|
||||||
.then(setItems)
|
listVisitRequests(),
|
||||||
|
listTodayAccess().catch(() => [] as AccessRecord[]),
|
||||||
|
])
|
||||||
|
.then(([requests, records]) => {
|
||||||
|
setItems(requests);
|
||||||
|
setAccessRecords(records);
|
||||||
|
})
|
||||||
.catch((e) => setError(e instanceof Error ? e.message : '조회 실패'))
|
.catch((e) => setError(e instanceof Error ? e.message : '조회 실패'))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(load, []);
|
useEffect(() => {
|
||||||
|
load();
|
||||||
|
const t = setInterval(load, 3000);
|
||||||
|
return () => clearInterval(t);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const accessById = new Map(accessRecords.map((r) => [r.visitRequestId, r]));
|
||||||
|
const statusBadge = (r: VisitRequestView) => {
|
||||||
|
const access = accessById.get(r.id);
|
||||||
|
if (access?.inside) {
|
||||||
|
return { className: 'green', label: '재실중' };
|
||||||
|
}
|
||||||
|
if (access?.checkOutAt) {
|
||||||
|
return { className: 'gray', label: '퇴장' };
|
||||||
|
}
|
||||||
|
return { className: STATUS_CLASS[r.status], label: STATUS_LABEL[r.status] };
|
||||||
|
};
|
||||||
|
|
||||||
const confirmCancel = async () => {
|
const confirmCancel = async () => {
|
||||||
const id = cancelingId;
|
const id = cancelingId;
|
||||||
@@ -105,7 +128,12 @@ export const VisitRequestListPage: React.FC = () => {
|
|||||||
<td>{r.purpose || '-'}</td>
|
<td>{r.purpose || '-'}</td>
|
||||||
<td>{r.workName || '-'}</td>
|
<td>{r.workName || '-'}</td>
|
||||||
<td>{formatVisitRange(r.visitFrom, r.visitTo)}</td>
|
<td>{formatVisitRange(r.visitFrom, r.visitTo)}</td>
|
||||||
<td><span className={`badge badge-${STATUS_CLASS[r.status]}`}>{STATUS_LABEL[r.status]}</span></td>
|
<td>
|
||||||
|
{(() => {
|
||||||
|
const badge = statusBadge(r);
|
||||||
|
return <span className={`badge badge-${badge.className}`}>{badge.label}</span>;
|
||||||
|
})()}
|
||||||
|
</td>
|
||||||
<td className="row-actions">
|
<td className="row-actions">
|
||||||
{r.status === 'APPROVED' && (
|
{r.status === 'APPROVED' && (
|
||||||
<button className="btn-link" onClick={() => navigate(`/badge/${r.id}`)}>출입증</button>
|
<button className="btn-link" onClick={() => navigate(`/badge/${r.id}`)}>출입증</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user