(null)
-
- const afterAuthenticated = async () => {
- const me = await api.get('/auth/me')
- localStorage.setItem('hrm_user', JSON.stringify(me.data.data))
- nav('/dashboard')
- }
-
const login = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true); setError('')
try {
const { data } = await api.post('/auth/login', form)
- const payload = data.data || {}
- if (payload.twofa === 'true') {
- // 2FA 활성 — verifyMethod 로 분기(access 토큰 미발급). 시크릿/QR은 화면 표시용만.
- setTwofa({
- verifyToken: payload.verifyToken,
- verifyMethod: (payload.verifyMethod as VerifyMethod) || 'EMAIL',
- maskedEmail: payload.maskedEmail || '',
- qrImage: payload.qrImage || '',
- secret: payload.secret || '',
- })
- setCode('')
- return
- }
- localStorage.setItem('hrm_token', payload.token)
- await afterAuthenticated()
- } catch (err: any) {
- const msg = err?.response?.data?.message || ''
- setError(msg.includes('잠겼') || msg.includes('승인')
- ? msg : '아이디 또는 비밀번호가 올바르지 않습니다.')
- } finally { setLoading(false) }
- }
-
- const verify = async (e: React.FormEvent) => {
- e.preventDefault()
- if (!twofa) return
- setLoading(true); setError('')
- try {
- // OTP·OTP_SETUP 은 /verify-otp, 이메일은 /verify(하위호환)
- const { data } = isOtp
- ? await verifyOtp(twofa.verifyToken, code)
- : await verify2fa(twofa.verifyToken, code)
localStorage.setItem('hrm_token', data.data.token)
- await afterAuthenticated()
+ const me = await api.get('/auth/me')
+ localStorage.setItem('hrm_user', JSON.stringify(me.data.data))
+ nav('/dashboard')
} catch {
- setError('인증 코드가 올바르지 않거나 만료되었습니다.')
+ setError('아이디 또는 비밀번호가 올바르지 않습니다.')
} finally { setLoading(false) }
}
@@ -83,154 +29,31 @@ export default function LoginPage() {
GUARDiA HRM
AI 기반 통합 인사관리 플랫폼
-
- {!twofa ? (
-
- ) : (
-
- )}
+
-
- {helper && setHelper(null)} />}
-
- )
-}
-
-/** 로그인 보조 3종 모달 — 회원가입 / 아이디찾기 / 비밀번호 초기화. */
-function HelperModal({ kind, onClose }: { kind: Exclude; onClose: () => void }) {
- const [f, setF] = useState({ username: '', password: '', displayName: '', email: '' })
- const [msg, setMsg] = useState('')
- const [busy, setBusy] = useState(false)
- const set = (k: string, v: string) => setF({ ...f, [k]: v })
-
- const TITLE: Record = { signup: '회원가입', findId: '아이디 찾기', resetPw: '비밀번호 초기화' }
-
- const submit = async (e: React.FormEvent) => {
- e.preventDefault()
- setBusy(true); setMsg('')
- try {
- if (kind === 'signup') {
- const { data } = await signup({ username: f.username, password: f.password, displayName: f.displayName, email: f.email })
- setMsg(data.data?.message || '처리되었습니다.')
- } else if (kind === 'findId') {
- const { data } = await findId({ displayName: f.displayName, email: f.email })
- setMsg(data.data?.found ? `회원님의 아이디: ${data.data.maskedUsername}` : '일치하는 계정을 찾을 수 없습니다.')
- } else {
- const { data } = await resetPasswordHelper({ username: f.username, email: f.email })
- setMsg(data.data?.message || '처리되었습니다.')
- }
- } catch {
- setMsg('요청 처리 중 오류가 발생했습니다.')
- } finally { setBusy(false) }
- }
-
- const input = (k: string, ph: string, type = 'text') => (
- set(k, e.target.value)}
- className="w-full border border-slate-200 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
- placeholder={ph} required />
- )
-
- return (
-
)
}
diff --git a/frontend/src/pages/uiws/ScheduleCalendar.tsx b/frontend/src/pages/uiws/ScheduleCalendar.tsx
index 5a03426..f9abbab 100644
--- a/frontend/src/pages/uiws/ScheduleCalendar.tsx
+++ b/frontend/src/pages/uiws/ScheduleCalendar.tsx
@@ -84,16 +84,22 @@ export default function ScheduleCalendar() {
)
}
+// datetime-local(yyyy-MM-ddTHH:mm) → API 계약(yyyy-MM-ddTHH:mm:ss) 보정: 초 없으면 :00 부가
+function toApiDt(v: string): string {
+ if (!v) return v
+ return v.length === 16 ? `${v}:00` : v
+}
+
function ScheduleForm({ date, type, onClose, onSaved }: { date: string; type: string; onClose: () => void; onSaved: () => void }) {
const [title, setTitle] = useState('')
- const [startDt, setStartDt] = useState(`${date}T09:00:00`)
- const [endDt, setEndDt] = useState(`${date}T18:00:00`)
+ const [startDt, setStartDt] = useState(`${date}T09:00`)
+ const [endDt, setEndDt] = useState(`${date}T18:00`)
const [content, setContent] = useState('')
const [err, setErr] = useState('')
const save = async () => {
setErr('')
try {
- await createSchedule({ scheType: type, title, startDt, endDt, content, attachmentIds: [] })
+ await createSchedule({ scheType: type, title, startDt: toApiDt(startDt), endDt: toApiDt(endDt), content, attachmentIds: [] })
onSaved()
} catch (e: any) { setErr(e?.response?.data?.message || '저장 실패') }
}
@@ -101,8 +107,8 @@ function ScheduleForm({ date, type, onClose, onSaved }: { date: string; type: st
>}>
-
-
+
+
{err && {err}
}