pricelees bdc99c7883 [#37] 테마 스키마 재정의 (#38)
<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) -->

## 📝 관련 이슈 및 PR

**PR과 관련된 이슈 번호**
- #37

##  작업 내용
<!-- 어떤 작업을 했는지 알려주세요! -->
- 가격, 시간 등 테마를 정의하는데 필요하다고 느껴지는 필드 추가
- JPA Auditing으로 감사 정보 확인 기능 추가
- 프론트엔드 페이지 디자인 변경 및 새로운 API 반영

## 🧪 테스트
<!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! -->
6db81feb9b 을 바탕으로 향후 다른 모든 기능의 테스트를 통합 테스트로 전환할 예정. 지금은 불필요한 테스트가 너무 많다고 느껴짐.

## 📚 참고 자료 및 기타
<!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! -->
- FInder / Writer / Validator 구조를 수정할 필요가 있음. 복잡하고 가독성이 낮은 로직만 별도로 빼는 것이 더 효율적이라고 판단됨.

Reviewed-on: #38
Co-authored-by: pricelees <priceelees@gmail.com>
Co-committed-by: pricelees <priceelees@gmail.com>
2025-09-03 02:03:37 +00:00

124 lines
4.4 KiB
TypeScript

import { createTime, delTime, fetchTimes } from '@_api/time/timeAPI';
import type { TimeCreateRequest } from '@_api/time/timeTypes';
import React, { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { isLoginRequiredError } from '@_api/apiClient';
import '../../css/admin-time-page.css';
const AdminTimePage: React.FC = () => {
const [times, setTimes] = useState<any[]>([]);
const [isEditing, setIsEditing] = useState(false);
const [newTime, setNewTime] = useState('');
const navigate = useNavigate();
const location = useLocation();
const handleError = (err: any) => {
if (isLoginRequiredError(err)) {
alert('로그인이 필요해요.');
navigate('/login', { state: { from: location } });
} else {
const message = err.response?.data?.message || '알 수 없는 오류가 발생했습니다.';
alert(message);
console.error(err);
}
};
useEffect(() => {
const fetchData = async () => {
await fetchTimes()
.then(response => setTimes(response.times))
.catch(handleError);
}
fetchData();
}, []);
const handleAddClick = () => {
setIsEditing(true);
};
const handleCancelClick = () => {
setIsEditing(false);
setNewTime('');
};
const handleSaveClick = async () => {
if (!newTime) {
alert('시간을 입력해주세요.');
return;
}
if (!/^\d{2}:\d{2}$/.test(newTime)) {
alert('시간 형식이 올바르지 않습니다. HH:MM 형식으로 입력해주세요.');
return;
}
const request: TimeCreateRequest = {
startAt: newTime
};
await createTime(request)
.then((response) => {
setTimes([...times, response]);
alert('시간을 추가했어요.');
handleCancelClick();
})
.catch(handleError);
};
const deleteTime = async (id: string) => {
if (!window.confirm('정말 삭제하시겠어요?')) {
return;
}
await delTime(id)
.then(() => {
setTimes(times.filter(time => time.id !== id));
alert('시간을 삭제했어요.');
})
.catch(handleError);
};
return (
<div className="admin-time-container">
<h2 className="page-title"> </h2>
<div className="section-card">
<div className="table-header">
<button className="btn btn-primary" onClick={handleAddClick}> </button>
</div>
<div className="table-container">
<table>
<thead>
<tr>
<th>ID</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{times.map(time => (
<tr key={time.id}>
<td>{time.id}</td>
<td>{time.startAt}</td>
<td>
<button className="btn btn-danger" onClick={() => deleteTime(time.id)}></button>
</td>
</tr>
))}
{isEditing && (
<tr className="editing-row">
<td></td>
<td><input type="time" className="form-input" value={newTime} onChange={e => setNewTime(e.target.value)} /></td>
<td>
<button className="btn btn-primary" onClick={handleSaveClick}></button>
<button className="btn btn-secondary" onClick={handleCancelClick}></button>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default AdminTimePage;