feat(WEB): 입력 컴포넌트 공통화 및 UI 개선

- 숫자/통화/전화번호/사업자번호 등 특수 입력 컴포넌트 추가
- MobileCard 컴포넌트 통합 (ListMobileCard 제거)
- IntegratedListTemplateV2 페이지네이션 버그 수정 (NaN 이슈)
- IntegratedDetailTemplate 타이틀 중복 수정
- 문서 시스템 컴포넌트 추가
- 헤더 벨 아이콘 포커스 스타일 개선

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-21 20:56:17 +09:00
parent cfa72fe19b
commit 835c06ce94
190 changed files with 8575 additions and 2354 deletions

View File

@@ -15,6 +15,8 @@ import { Package, Settings, Plus, Trash2 } from "lucide-react";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { NumberInput } from "../ui/number-input";
import { QuantityInput } from "../ui/quantity-input";
import {
Select,
SelectContent,
@@ -245,18 +247,16 @@ export function LocationDetailPanel({
<div className="flex items-center gap-4">
<span className="text-sm text-gray-600 w-20"></span>
<div className="flex items-center gap-2">
<Input
type="number"
<NumberInput
value={location.openWidth}
onChange={(e) => handleFieldChange("openWidth", parseFloat(e.target.value) || 0)}
onChange={(value) => handleFieldChange("openWidth", value ?? 0)}
disabled={disabled}
className="w-24 h-8 text-center font-bold"
/>
<span className="text-gray-400">×</span>
<Input
type="number"
<NumberInput
value={location.openHeight}
onChange={(e) => handleFieldChange("openHeight", parseFloat(e.target.value) || 0)}
onChange={(value) => handleFieldChange("openHeight", value ?? 0)}
disabled={disabled}
className="w-24 h-8 text-center font-bold"
/>
@@ -284,13 +284,12 @@ export function LocationDetailPanel({
</div>
<div>
<span className="text-gray-500"></span>
<Input
type="number"
min="1"
<QuantityInput
value={location.quantity}
onChange={(e) => handleFieldChange("quantity", parseInt(e.target.value) || 1)}
onChange={(value) => handleFieldChange("quantity", value ?? 1)}
disabled={disabled}
className="w-24 h-7 text-center font-semibold"
min={1}
/>
</div>
</div>
@@ -366,12 +365,12 @@ export function LocationDetailPanel({
<TableCell className="font-medium">{item.item_name}</TableCell>
<TableCell className="text-center text-gray-600">{item.manufacture_size || "-"}</TableCell>
<TableCell className="text-center">
<Input
type="number"
defaultValue={item.quantity}
<QuantityInput
value={item.quantity}
onChange={() => {}}
className="w-16 h-8 text-center"
min={1}
readOnly={disabled}
disabled={disabled}
/>
</TableCell>
<TableCell className="text-center">
@@ -437,12 +436,12 @@ export function LocationDetailPanel({
</Select>
</TableCell>
<TableCell className="text-center">
<Input
type="number"
defaultValue={item.quantity}
<QuantityInput
value={item.quantity}
onChange={() => {}}
className="w-16 h-8 text-center"
min={1}
readOnly={disabled}
disabled={disabled}
/>
</TableCell>
<TableCell className="text-center">
@@ -495,12 +494,12 @@ export function LocationDetailPanel({
<TableCell className="text-center text-gray-600">{item.type || "-"}</TableCell>
<TableCell className="text-center text-gray-600">{item.spec || "-"}</TableCell>
<TableCell className="text-center">
<Input
type="number"
defaultValue={item.quantity}
<QuantityInput
value={item.quantity}
onChange={() => {}}
className="w-16 h-8 text-center"
min={1}
readOnly={disabled}
disabled={disabled}
/>
</TableCell>
<TableCell className="text-center">
@@ -563,12 +562,12 @@ export function LocationDetailPanel({
</Select>
</TableCell>
<TableCell className="text-center">
<Input
type="number"
defaultValue={item.quantity}
<QuantityInput
value={item.quantity}
onChange={() => {}}
className="w-16 h-8 text-center"
min={1}
readOnly={disabled}
disabled={disabled}
/>
</TableCell>
<TableCell className="text-center">

View File

@@ -14,6 +14,8 @@ import { toast } from "sonner";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { NumberInput } from "../ui/number-input";
import { QuantityInput } from "../ui/quantity-input";
import {
Select,
SelectContent,
@@ -398,21 +400,19 @@ export function LocationListPanel({
</div>
<div>
<label className="text-xs text-gray-600"></label>
<Input
type="number"
<NumberInput
placeholder="5000"
value={formData.openWidth}
onChange={(e) => handleFormChange("openWidth", e.target.value)}
value={formData.openWidth ? Number(formData.openWidth) : undefined}
onChange={(value) => handleFormChange("openWidth", value?.toString() ?? "")}
className="h-8 text-sm"
/>
</div>
<div>
<label className="text-xs text-gray-600"></label>
<Input
type="number"
<NumberInput
placeholder="3000"
value={formData.openHeight}
onChange={(e) => handleFormChange("openHeight", e.target.value)}
value={formData.openHeight ? Number(formData.openHeight) : undefined}
onChange={(value) => handleFormChange("openHeight", value?.toString() ?? "")}
className="h-8 text-sm"
/>
</div>
@@ -436,12 +436,11 @@ export function LocationListPanel({
</div>
<div>
<label className="text-xs text-gray-600"></label>
<Input
type="number"
min="1"
value={formData.quantity}
onChange={(e) => handleFormChange("quantity", e.target.value)}
<QuantityInput
value={formData.quantity ? Number(formData.quantity) : undefined}
onChange={(value) => handleFormChange("quantity", value?.toString() ?? "")}
className="h-8 text-sm"
min={1}
/>
</div>
</div>

View File

@@ -41,7 +41,7 @@ import {
type StatCard,
type ListParams,
} from '@/components/templates/UniversalListPage';
import { ListMobileCard, InfoField } from '@/components/organisms/ListMobileCard';
import { ListMobileCard, InfoField } from '@/components/organisms/MobileCard';
import { StandardDialog } from '@/components/molecules/StandardDialog';
import {
AlertDialog,

View File

@@ -11,6 +11,9 @@
import { useState, useEffect, useMemo, useCallback } from "react";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
import { QuantityInput } from "../ui/quantity-input";
import { CurrencyInput } from "../ui/currency-input";
import { PhoneInput } from "../ui/phone-input";
import {
Select,
SelectContent,
@@ -697,11 +700,11 @@ export function QuoteRegistration({
</FormField>
<FormField label="연락처" htmlFor="contact" type="custom">
<Input
<PhoneInput
id="contact"
placeholder="010-1234-5678"
value={formData.contact}
onChange={(e) => handleFieldChange("contact", e.target.value)}
onChange={(value) => handleFieldChange("contact", value)}
/>
</FormField>
</FormFieldGrid>
@@ -983,14 +986,13 @@ export function QuoteRegistration({
error={errors[`item-${activeItemIndex}-quantity`]}
htmlFor={`quantity-${activeItemIndex}`}
>
<Input
<QuantityInput
id={`quantity-${activeItemIndex}`}
type="number"
min="1"
value={formData.items[activeItemIndex].quantity}
onChange={(e) =>
handleItemChange(activeItemIndex, "quantity", parseInt(e.target.value) || 1)
onChange={(value) =>
handleItemChange(activeItemIndex, "quantity", value ?? 1)
}
min={1}
/>
</FormField>
@@ -1014,13 +1016,12 @@ export function QuoteRegistration({
type="custom"
htmlFor={`inspectionFee-${activeItemIndex}`}
>
<Input
<CurrencyInput
id={`inspectionFee-${activeItemIndex}`}
type="number"
placeholder="예: 50000"
value={formData.items[activeItemIndex].inspectionFee}
onChange={(e) =>
handleItemChange(activeItemIndex, "inspectionFee", parseInt(e.target.value) || 0)
onChange={(value) =>
handleItemChange(activeItemIndex, "inspectionFee", value ?? 0)
}
/>
</FormField>

View File

@@ -18,6 +18,7 @@ import { Button } from "../ui/button";
import { Badge } from "../ui/badge";
import { Input } from "../ui/input";
import { Textarea } from "../ui/textarea";
import { PhoneInput } from "../ui/phone-input";
import {
Select,
SelectContent,
@@ -503,10 +504,10 @@ export function QuoteRegistrationV2({
</div>
<div>
<label className="text-sm font-medium text-gray-700"></label>
<Input
<PhoneInput
placeholder="010-1234-5678"
value={formData.contact}
onChange={(e) => handleFieldChange("contact", e.target.value)}
onChange={(value) => handleFieldChange("contact", value)}
disabled={isViewMode}
/>
</div>