ksh-v2-work의 POP 화면 디자이너 기능을 main에 병합한다. [병합 내용] - pop-card-list-v2: 슬롯 기반 CSS Grid 카드 컴포넌트 (12종 셀 타입) - pop-status-bar: 독립 상태 칩 컴포넌트 (카운트 순환 문제 해결) - pop-scanner: 바코드/QR 스캐너 + 멀티필드 파싱 - pop-profile: 사용자 프로필/PC전환/로그아웃 컴포넌트 - pop-button: 설정 패널 UX 전면 개선 + 제어 실행 기능 - pop-search: 날짜 입력 타입 + 연결 탭 일관성 통합 - POP 모드 네비게이션: PC <-> POP 양방향 전환 + 로그인 POP 모드 토글 - 타임라인 범용화 + 상태 값 매핑 동적 배열 전환 - 다중 액션 체이닝 + 외부 테이블 선택 + 카드 클릭 모달 [충돌 해결 4건] - authController.ts: 양쪽 통합 (스마트공장 로그 + POP 랜딩 경로) - AppLayout.tsx: 양쪽 통합 (메뉴 드래그 + POP 모드 메뉴, 리디자인 UI + POP 모드 항목) - ConnectionEditor.tsx: ksh-v2-work 선택 (하위 테이블 필터 구조) + CSS 변수 적용 - pop-button.tsx: ksh-v2-work 선택 (자연어 UX + 제어 실행) + CSS 변수 스타일 유지
123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Eye, EyeOff, Loader2, Monitor } from "lucide-react";
|
|
import { LoginFormData } from "@/types/auth";
|
|
import { ErrorMessage } from "./ErrorMessage";
|
|
|
|
interface LoginFormProps {
|
|
formData: LoginFormData;
|
|
isLoading: boolean;
|
|
error: string;
|
|
showPassword: boolean;
|
|
isPopMode: boolean;
|
|
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
onSubmit: (e: React.FormEvent) => void;
|
|
onTogglePassword: () => void;
|
|
onTogglePop: () => void;
|
|
}
|
|
|
|
/**
|
|
* 로그인 폼 컴포넌트
|
|
*/
|
|
export function LoginForm({
|
|
formData,
|
|
isLoading,
|
|
error,
|
|
showPassword,
|
|
isPopMode,
|
|
onInputChange,
|
|
onSubmit,
|
|
onTogglePassword,
|
|
onTogglePop,
|
|
}: LoginFormProps) {
|
|
return (
|
|
<Card className="border shadow-lg">
|
|
<CardHeader className="space-y-1">
|
|
<CardTitle className="text-center text-2xl">로그인</CardTitle>
|
|
<CardDescription className="text-center">계정 정보를 입력해주세요</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<ErrorMessage message={error} />
|
|
|
|
<form onSubmit={onSubmit} className="space-y-4">
|
|
{/* 연결 테스트 버튼 */}
|
|
|
|
{/* 사용자 ID */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="userId">사용자 ID</Label>
|
|
<Input
|
|
id="userId"
|
|
name="userId"
|
|
type="text"
|
|
placeholder="사용자 ID를 입력하세요"
|
|
value={formData.userId}
|
|
onChange={onInputChange}
|
|
disabled={isLoading}
|
|
className="h-11"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
{/* 비밀번호 */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="password">비밀번호</Label>
|
|
<div className="relative">
|
|
<Input
|
|
id="password"
|
|
name="password"
|
|
type={showPassword ? "text" : "password"}
|
|
placeholder="비밀번호를 입력하세요"
|
|
value={formData.password}
|
|
onChange={onInputChange}
|
|
disabled={isLoading}
|
|
className="h-11 pr-10"
|
|
required
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={onTogglePassword}
|
|
className="absolute top-1/2 right-3 -translate-y-1/2 transform text-muted-foreground transition-colors hover:text-foreground"
|
|
disabled={isLoading}
|
|
>
|
|
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* POP 모드 토글 */}
|
|
<div className="flex items-center justify-between rounded-lg border border-slate-200 bg-slate-50 px-3 py-2.5">
|
|
<div className="flex items-center gap-2">
|
|
<Monitor className="h-4 w-4 text-slate-500" />
|
|
<span className="text-sm text-slate-600">POP 모드</span>
|
|
</div>
|
|
<Switch
|
|
checked={isPopMode}
|
|
onCheckedChange={onTogglePop}
|
|
disabled={isLoading}
|
|
/>
|
|
</div>
|
|
|
|
{/* 로그인 버튼 */}
|
|
<Button
|
|
type="submit"
|
|
className="h-11 w-full font-medium"
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
로그인 중...
|
|
</>
|
|
) : (
|
|
"로그인"
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|