Update visit request form layout
This commit is contained in:
@@ -31,6 +31,14 @@ const isPastDate = (iso: string): boolean => {
|
||||
return day < todayStart;
|
||||
};
|
||||
|
||||
const formatPhoneLike = (value: string): string => {
|
||||
const digits = value.replace(/\D/g, '');
|
||||
if (digits.length === 11) {
|
||||
return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const VisitRequestFormPage: React.FC = () => {
|
||||
const [form, setForm] = useState({
|
||||
visitorName: '',
|
||||
@@ -60,6 +68,10 @@ export const VisitRequestFormPage: React.FC = () => {
|
||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>,
|
||||
) => setForm({ ...form, [k]: e.target.value });
|
||||
|
||||
const updateFormattedContact = (k: 'contact' | 'watcher2Contact') => (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
) => setForm({ ...form, [k]: formatPhoneLike(e.target.value) });
|
||||
|
||||
const toggleServerRoom = (room: string) => (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setForm((f) => ({
|
||||
...f,
|
||||
@@ -103,7 +115,7 @@ export const VisitRequestFormPage: React.FC = () => {
|
||||
await createVisitRequest({
|
||||
visitorName: form.visitorName.trim(),
|
||||
company: form.company.trim() || undefined,
|
||||
contact: form.contact.trim(),
|
||||
contact: formatPhoneLike(form.contact.trim()),
|
||||
email: form.email.trim() || undefined,
|
||||
vehicleNo: form.vehicleNo.trim() || undefined,
|
||||
serverRooms: form.serverRooms,
|
||||
@@ -112,7 +124,7 @@ export const VisitRequestFormPage: React.FC = () => {
|
||||
workName: form.workName.trim() || undefined,
|
||||
watcher2Name: form.watcher2Name.trim() || undefined,
|
||||
watcher2Team: form.watcher2Team.trim() || undefined,
|
||||
watcher2Contact: form.watcher2Contact.trim() || undefined,
|
||||
watcher2Contact: formatPhoneLike(form.watcher2Contact.trim()) || undefined,
|
||||
visitFrom: form.visitFrom,
|
||||
visitTo: form.visitTo,
|
||||
});
|
||||
@@ -129,154 +141,165 @@ export const VisitRequestFormPage: React.FC = () => {
|
||||
<div className="page-head"><h2>출입 신청</h2></div>
|
||||
|
||||
{/* noValidate: use our Korean messages instead of the browser's native popups */}
|
||||
<form className="card form-grid" onSubmit={onSubmit} noValidate>
|
||||
<label className="field">
|
||||
<span>방문자 이름 *</span>
|
||||
<input className="ime-ko" value={form.visitorName} onChange={update('visitorName')} autoFocus />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>회사/소속</span>
|
||||
<input value={form.company} onChange={update('company')} />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처 * <em className="hint-inline">: 입력형식은 010-0000-0000 로 작성해 주세요.</em></span>
|
||||
<input type="tel" value={form.contact} onChange={update('contact')} placeholder="010-0000-0000" />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>이메일</span>
|
||||
<input className="ime-en" type="email" value={form.email} onChange={update('email')} placeholder="name@example.com" />
|
||||
</label>
|
||||
<form className="card form-grid visit-form" onSubmit={onSubmit} noValidate>
|
||||
<fieldset className="form-group span-2">
|
||||
<legend>방문자</legend>
|
||||
<div className="group-grid">
|
||||
<label className="field">
|
||||
<span>방문자 이름 <b className="required">*</b></span>
|
||||
<input className="ime-ko" value={form.visitorName} onChange={update('visitorName')} autoFocus />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>회사/소속</span>
|
||||
<input value={form.company} onChange={update('company')} />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처 <b className="required">*</b><em className="hint-inline">: 입력형식은 010-0000-0000 로 작성해 주세요.</em></span>
|
||||
<input type="tel" value={form.contact} onChange={updateFormattedContact('contact')} placeholder="010-0000-0000" />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>이메일</span>
|
||||
<input className="ime-en" type="email" value={form.email} onChange={update('email')} placeholder="name@example.com" />
|
||||
</label>
|
||||
|
||||
<label className="field span-2">
|
||||
<span>차량번호 <em className="hint-inline">: 차량번호가 5부제에 해당될 경우 출입이 제한됩니다.</em></span>
|
||||
<input className="ime-ko" value={form.vehicleNo} onChange={update('vehicleNo')} />
|
||||
</label>
|
||||
<label className="field span-2">
|
||||
<span>차량번호 <em className="hint-inline">: 차량번호가 5부제에 해당될 경우 출입이 제한됩니다.</em></span>
|
||||
<input className="ime-ko" value={form.vehicleNo} onChange={update('vehicleNo')} />
|
||||
</label>
|
||||
|
||||
<div className="field span-2">
|
||||
<span>출입 전산실 <em className="hint-inline">: 선택한 전산실마다 출입증(QR)이 각각 발급됩니다.</em></span>
|
||||
<div className="checkbox-row">
|
||||
{SERVER_ROOM_OPTIONS.map((z) => (
|
||||
<label key={z} className="checkbox-inline">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.serverRooms.includes(z)}
|
||||
onChange={toggleServerRoom(z)}
|
||||
/>
|
||||
<span>{z}</span>
|
||||
<div className="field span-2">
|
||||
<span>출입 전산실 <em className="hint-inline">: 선택한 전산실마다 출입증(QR)이 각각 발급됩니다.</em></span>
|
||||
<div className="checkbox-row">
|
||||
{SERVER_ROOM_OPTIONS.map((z) => (
|
||||
<label key={z} className="checkbox-inline">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={form.serverRooms.includes(z)}
|
||||
onChange={toggleServerRoom(z)}
|
||||
/>
|
||||
<span>{z}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label className="field">
|
||||
<span>추가 구역</span>
|
||||
<select value={form.room} onChange={update('room')}>
|
||||
<option value="">선택하세요</option>
|
||||
{ROOM_OPTIONS.map((r) => <option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{form.room === '기타' ? (
|
||||
<label className="field">
|
||||
<span>기타 구역 입력 <b className="required">*</b></span>
|
||||
<input value={form.roomEtc} onChange={update('roomEtc')} placeholder="추가 구역을 입력하세요" />
|
||||
</label>
|
||||
))}
|
||||
) : <div />}
|
||||
|
||||
<label className="field">
|
||||
<span>출입 목적 <b className="required">*</b></span>
|
||||
<select value={form.purpose} onChange={update('purpose')}>
|
||||
<option value="">선택하세요</option>
|
||||
{PURPOSE_OPTIONS.map((p) => <option key={p} value={p}>{p}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{form.purpose === '기타' ? (
|
||||
<label className="field">
|
||||
<span>기타 목적 입력 <b className="required">*</b></span>
|
||||
<input value={form.purposeEtc} onChange={update('purposeEtc')} placeholder="출입 목적을 입력하세요" />
|
||||
</label>
|
||||
) : <div />}
|
||||
|
||||
<label className="field span-2">
|
||||
<span>작업명 <em className="hint-inline">: 구체적인 작업 내용 (예: Active-Active 회의)</em></span>
|
||||
<input className="ime-ko" value={form.workName} onChange={update('workName')} placeholder="작업 내용을 입력하세요 (선택)" />
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
<span>출입 일시 <b className="required">*</b></span>
|
||||
<DateTimePicker
|
||||
value={form.visitFrom}
|
||||
onChange={(v) => setForm((f) => ({ ...f, visitFrom: v }))}
|
||||
placeholder="출입 일시 선택"
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>퇴실예정일시 <b className="required">*</b></span>
|
||||
<DateTimePicker
|
||||
value={form.visitTo}
|
||||
onChange={(v) => setForm((f) => ({ ...f, visitTo: v }))}
|
||||
placeholder="퇴실예정일시 선택"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<label className="field">
|
||||
<span>추가 구역</span>
|
||||
<select value={form.room} onChange={update('room')}>
|
||||
<option value="">선택하세요</option>
|
||||
{ROOM_OPTIONS.map((r) => <option key={r} value={r}>{r}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{form.room === '기타' ? (
|
||||
<label className="field">
|
||||
<span>기타 구역 입력 *</span>
|
||||
<input value={form.roomEtc} onChange={update('roomEtc')} placeholder="추가 구역을 입력하세요" />
|
||||
</label>
|
||||
) : <div />}
|
||||
<fieldset className="form-group span-2">
|
||||
<legend>출입통제담당자</legend>
|
||||
<div className="group-grid">
|
||||
<label className="field">
|
||||
<span>직원명</span>
|
||||
<input value={user?.fullName ?? ''} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>담당팀</span>
|
||||
<input value={user?.department ?? ''} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처 <em className="hint-inline">: 내선번호/휴대폰번호</em></span>
|
||||
<input value={user?.email ?? ''} readOnly />
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<label className="field">
|
||||
<span>출입 목적 *</span>
|
||||
<select value={form.purpose} onChange={update('purpose')}>
|
||||
<option value="">선택하세요</option>
|
||||
{PURPOSE_OPTIONS.map((p) => <option key={p} value={p}>{p}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
{form.purpose === '기타' ? (
|
||||
<label className="field">
|
||||
<span>기타 목적 입력 *</span>
|
||||
<input value={form.purposeEtc} onChange={update('purposeEtc')} placeholder="출입 목적을 입력하세요" />
|
||||
</label>
|
||||
) : <div />}
|
||||
<fieldset className="form-group span-2">
|
||||
<legend>현장감시자1과2</legend>
|
||||
<div className="subsection-title">현장감시자1 <em className="hint-inline">: 고정 인원 (자동 지정)</em></div>
|
||||
<div className="group-grid">
|
||||
<label className="field">
|
||||
<span>이름</span>
|
||||
<input value={FIXED_WATCHER1.name} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>소속</span>
|
||||
<input value={FIXED_WATCHER1.team} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처</span>
|
||||
<input value={FIXED_WATCHER1.contact} readOnly />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label className="field span-2">
|
||||
<span>작업명 <em className="hint-inline">: 구체적인 작업 내용 (예: Active-Active 회의)</em></span>
|
||||
<input className="ime-ko" value={form.workName} onChange={update('workName')} placeholder="작업 내용을 입력하세요 (선택)" />
|
||||
</label>
|
||||
|
||||
<label className="field">
|
||||
<span>출입 일시 *</span>
|
||||
<DateTimePicker
|
||||
value={form.visitFrom}
|
||||
onChange={(v) => setForm((f) => ({ ...f, visitFrom: v }))}
|
||||
placeholder="출입 일시 선택"
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>퇴실 일시 *</span>
|
||||
<DateTimePicker
|
||||
value={form.visitTo}
|
||||
onChange={(v) => setForm((f) => ({ ...f, visitTo: v }))}
|
||||
placeholder="퇴실 일시 선택"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="form-section span-2">
|
||||
출입통제담당자 <em className="hint-inline">: 로그인한 본인 정보로 자동 등록됩니다.</em>
|
||||
</div>
|
||||
<label className="field">
|
||||
<span>직원명</span>
|
||||
<input value={user?.fullName ?? ''} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>담당팀</span>
|
||||
<input value={user?.department ?? ''} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처</span>
|
||||
<input value={user?.email ?? ''} readOnly />
|
||||
</label>
|
||||
<div />
|
||||
|
||||
<div className="form-section span-2">현장감시자1 <em className="hint-inline">: 고정 인원 (자동 지정)</em></div>
|
||||
<label className="field">
|
||||
<span>이름</span>
|
||||
<input value={FIXED_WATCHER1.name} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>소속</span>
|
||||
<input value={FIXED_WATCHER1.team} readOnly />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처</span>
|
||||
<input value={FIXED_WATCHER1.contact} readOnly />
|
||||
</label>
|
||||
<div />
|
||||
|
||||
<div className="form-section span-2">현장감시자2 <em className="hint-inline">: 작업을 입회할 상주직원</em></div>
|
||||
<label className="field">
|
||||
<span>이름</span>
|
||||
<input className="ime-ko" value={form.watcher2Name} onChange={update('watcher2Name')} />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>소속</span>
|
||||
<select value={form.watcher2Team} onChange={update('watcher2Team')}>
|
||||
<option value="">선택하세요</option>
|
||||
{AFFILIATION_OPTIONS.map((a) => <option key={a} value={a}>{a}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처</span>
|
||||
<input value={form.watcher2Contact} onChange={update('watcher2Contact')} placeholder="내선/휴대번호" />
|
||||
</label>
|
||||
<div />
|
||||
<div className="subsection-title">현장감시자2 <em className="hint-inline">: 작업을 입회할 상주직원</em></div>
|
||||
<div className="group-grid">
|
||||
<label className="field">
|
||||
<span>이름</span>
|
||||
<input className="ime-ko" value={form.watcher2Name} onChange={update('watcher2Name')} />
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>소속</span>
|
||||
<select value={form.watcher2Team} onChange={update('watcher2Team')}>
|
||||
<option value="">선택하세요</option>
|
||||
{AFFILIATION_OPTIONS.map((a) => <option key={a} value={a}>{a}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>연락처 <em className="hint-inline">: 내선번호/휴대폰번호</em></span>
|
||||
<input value={form.watcher2Contact} onChange={updateFormattedContact('watcher2Contact')} placeholder="내선번호/휴대폰번호" />
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div className="span-2 consent-box">
|
||||
<label className="consent-label">
|
||||
<input type="checkbox" checked={consent} onChange={(e) => setConsent(e.target.checked)} />
|
||||
<span>
|
||||
<b>[개인정보 수집·이용 동의]</b><br />
|
||||
<b>[개인정보 수집·이용 동의 확인]</b><br />
|
||||
· 수집 항목: 이름, 연락처, 이메일, 차량번호<br />
|
||||
· 수집·이용 목적: IT센터 출입 신청 접수 및 출입자 관리<br />
|
||||
· 보유·이용 기간: 수집일로부터 1년 (기간 경과 시 지체 없이 파기)<br />
|
||||
· 귀하는 개인정보 수집·이용에 동의를 거부할 권리가 있으며, 동의하지 않을 경우 출입 신청이 제한됩니다.
|
||||
· 방문자 개인정보 수집·이용에 동의를 거부할 권리가 있으며, 동의하지 않을 경우 출입 신청이 제한됨을 고지
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user