Agent-Logs-Url: https://github.com/pdf114514/CarReservation/sessions/c0a4b7dc-228e-4e7d-a985-61b9a17de159 Co-authored-by: pdf114514 <57948770+pdf114514@users.noreply.github.com>
192 lines
6.4 KiB
JavaScript
192 lines
6.4 KiB
JavaScript
import { useState, useEffect } from 'react';
|
|
import { format } from 'date-fns';
|
|
import styles from './ReservationModal.module.css';
|
|
|
|
const PERIOD_OPTIONS = [
|
|
{ value: '', label: '指定なし' },
|
|
{ value: '午前', label: '午前' },
|
|
{ value: '午後', label: '午後' },
|
|
];
|
|
|
|
export default function ReservationModal({ cars, reservation, onSave, onDelete, onClose }) {
|
|
const isEdit = !!reservation?.id;
|
|
|
|
const [carId, setCarId] = useState('');
|
|
const [startDate, setStartDate] = useState('');
|
|
const [startPeriod, setStartPeriod] = useState('');
|
|
const [endDate, setEndDate] = useState('');
|
|
const [endPeriod, setEndPeriod] = useState('');
|
|
const [customerName, setCustomerName] = useState('');
|
|
const [notes, setNotes] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (reservation) {
|
|
setCarId(String(reservation.car_id || (cars[0]?.id ?? '')));
|
|
setStartDate(reservation.start_date || format(new Date(), 'yyyy-MM-dd'));
|
|
setStartPeriod(reservation.start_period || '');
|
|
setEndDate(reservation.end_date || format(new Date(), 'yyyy-MM-dd'));
|
|
setEndPeriod(reservation.end_period || '');
|
|
setCustomerName(reservation.customer_name || '');
|
|
setNotes(reservation.notes || '');
|
|
}
|
|
}, [reservation, cars]);
|
|
|
|
const handleSubmit = async (e) => {
|
|
e.preventDefault();
|
|
if (!carId || !startDate || !endDate) return;
|
|
if (startDate > endDate) {
|
|
alert('開始日は終了日以前に設定してください');
|
|
return;
|
|
}
|
|
try {
|
|
setSubmitting(true);
|
|
await onSave({
|
|
car_id: Number(carId),
|
|
start_date: startDate,
|
|
start_period: startPeriod,
|
|
end_date: endDate,
|
|
end_period: endPeriod,
|
|
customer_name: customerName,
|
|
notes,
|
|
});
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async () => {
|
|
if (!confirm('この予約を削除しますか?')) return;
|
|
try {
|
|
setSubmitting(true);
|
|
await onDelete(reservation.id);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={styles.overlay} onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
|
|
<div className={styles.modal}>
|
|
<div className={styles.modalHeader}>
|
|
<h2 className={styles.modalTitle}>
|
|
{isEdit ? '予約を編集' : '新しい予約を作成'}
|
|
</h2>
|
|
<button className={styles.closeBtn} onClick={onClose}>✕</button>
|
|
</div>
|
|
|
|
<form className={styles.form} onSubmit={handleSubmit}>
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>代車 <span className={styles.required}>*</span></label>
|
|
<select
|
|
className={styles.select}
|
|
value={carId}
|
|
onChange={(e) => setCarId(e.target.value)}
|
|
required
|
|
>
|
|
<option value="">選択してください</option>
|
|
{cars.map((car) => (
|
|
<option key={car.id} value={car.id}>{car.name}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div className={styles.fieldRow}>
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>開始日 <span className={styles.required}>*</span></label>
|
|
<input
|
|
type="date"
|
|
className={styles.input}
|
|
value={startDate}
|
|
onChange={(e) => setStartDate(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>開始時間帯</label>
|
|
<select
|
|
className={styles.select}
|
|
value={startPeriod}
|
|
onChange={(e) => setStartPeriod(e.target.value)}
|
|
>
|
|
{PERIOD_OPTIONS.map((o) => (
|
|
<option key={o.value} value={o.value}>{o.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.fieldRow}>
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>終了日 <span className={styles.required}>*</span></label>
|
|
<input
|
|
type="date"
|
|
className={styles.input}
|
|
value={endDate}
|
|
min={startDate}
|
|
onChange={(e) => setEndDate(e.target.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>終了時間帯</label>
|
|
<select
|
|
className={styles.select}
|
|
value={endPeriod}
|
|
onChange={(e) => setEndPeriod(e.target.value)}
|
|
>
|
|
{PERIOD_OPTIONS.map((o) => (
|
|
<option key={o.value} value={o.value}>{o.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>お客様名</label>
|
|
<input
|
|
type="text"
|
|
className={styles.input}
|
|
placeholder="例:山田 太郎"
|
|
value={customerName}
|
|
onChange={(e) => setCustomerName(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.field}>
|
|
<label className={styles.label}>備考</label>
|
|
<textarea
|
|
className={styles.textarea}
|
|
placeholder="メモを入力..."
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<div className={styles.actions}>
|
|
{isEdit && (
|
|
<button
|
|
type="button"
|
|
className={styles.btnDelete}
|
|
onClick={handleDelete}
|
|
disabled={submitting}
|
|
>
|
|
削除
|
|
</button>
|
|
)}
|
|
<div className={styles.rightActions}>
|
|
<button type="button" className={styles.btnCancel} onClick={onClose} disabled={submitting}>
|
|
キャンセル
|
|
</button>
|
|
<button type="submit" className={styles.btnSave} disabled={submitting}>
|
|
{submitting ? '保存中...' : (isEdit ? '更新' : '作成')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|