generated from pricelees/issue-pr-template
<!-- 제목 양식 --> <!-- [이슈번호] 작업 요약 (예시: [#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>
124 lines
4.4 KiB
TypeScript
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;
|