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

@@ -8,6 +8,8 @@ import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { NumberInput } from '@/components/ui/number-input';
import { CurrencyInput } from '@/components/ui/currency-input';
import {
Table,
TableBody,
@@ -265,18 +267,18 @@ export default function BOMSection({
/>
</TableCell>
<TableCell>
<Input
type="number"
<NumberInput
value={line.quantity}
onChange={(e) => {
onChange={(value) => {
setBomLines(
bomLines.map((l) =>
l.id === line.id ? { ...l, quantity: Number(e.target.value) } : l
l.id === line.id ? { ...l, quantity: value ?? 0 } : l
)
);
}}
min="0"
step="0.01"
min={0}
step={0.01}
allowDecimal
className="w-full"
/>
</TableCell>
@@ -284,18 +286,16 @@ export default function BOMSection({
<Badge variant="secondary">{line.unit}</Badge>
</TableCell>
<TableCell>
<Input
type="number"
<CurrencyInput
value={line.unitPrice || 0}
onChange={(e) => {
onChange={(value) => {
setBomLines(
bomLines.map((l) =>
l.id === line.id ? { ...l, unitPrice: Number(e.target.value) } : l
l.id === line.id ? { ...l, unitPrice: value ?? 0 } : l
)
);
}}
min="0"
className="w-full text-right"
className="w-full"
/>
</TableCell>
<TableCell>

View File

@@ -6,6 +6,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { NumberInput } from '@/components/ui/number-input';
import { FileImage, Plus, Trash2, X, Download, Loader2 } from 'lucide-react';
import type { BendingDetail } from '@/types/item';
import type { UseFormSetValue } from 'react-hook-form';
@@ -357,36 +358,34 @@ export default function BendingDiagramSection({
<tr key={detail.id} className={detail.shaded ? 'bg-gray-100' : ''}>
<td className="px-3 py-2 text-center border-b">{detail.no}</td>
<td className="px-3 py-2 border-b">
<Input
type="number"
<NumberInput
value={detail.input}
onChange={(e) => {
onChange={(value) => {
const newDetails = [...bendingDetails];
const value = e.target.value === '' ? 0 : parseFloat(e.target.value);
newDetails[index] = {
...detail,
input: isNaN(value) ? 0 : value,
input: value ?? 0,
};
setBendingDetails(newDetails);
updateWidthSum(newDetails);
}}
allowDecimal
className="h-8 text-center"
/>
</td>
<td className="px-3 py-2 border-b">
<Input
type="number"
<NumberInput
value={detail.elongation}
onChange={(e) => {
onChange={(value) => {
const newDetails = [...bendingDetails];
const value = e.target.value === '' ? -1 : parseFloat(e.target.value);
newDetails[index] = {
...detail,
elongation: isNaN(value) ? -1 : value,
elongation: value ?? -1,
};
setBendingDetails(newDetails);
updateWidthSum(newDetails);
}}
allowDecimal
className="h-8 text-center"
/>
</td>
@@ -409,17 +408,17 @@ export default function BendingDiagramSection({
/>
</td>
<td className="px-3 py-2 border-b">
<Input
type="number"
value={detail.aAngle || ''}
onChange={(e) => {
<NumberInput
value={detail.aAngle ?? undefined}
onChange={(value) => {
const newDetails = [...bendingDetails];
newDetails[index] = {
...detail,
aAngle: parseFloat(e.target.value) || undefined,
aAngle: value ?? undefined,
};
setBendingDetails(newDetails);
}}
allowDecimal
className="h-8 text-center"
placeholder="각도"
/>

View File

@@ -6,6 +6,7 @@
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { NumberInput } from '@/components/ui/number-input';
import {
Select,
SelectContent,
@@ -206,13 +207,13 @@ export default function AssemblyPartForm({
<Label>
() <span className="text-red-500">*</span>
</Label>
<Input
type="number"
<NumberInput
placeholder="예: 50"
value={sideSpecWidth}
onChange={(e) => {
setSideSpecWidth(e.target.value);
setValue('sideSpecWidth', e.target.value);
value={parseFloat(sideSpecWidth) || 0}
onChange={(value) => {
const strValue = String(value ?? 0);
setSideSpecWidth(strValue);
setValue('sideSpecWidth', strValue);
}}
/>
</div>
@@ -220,13 +221,13 @@ export default function AssemblyPartForm({
<Label>
() <span className="text-red-500">*</span>
</Label>
<Input
type="number"
<NumberInput
placeholder="예: 100"
value={sideSpecHeight}
onChange={(e) => {
setSideSpecHeight(e.target.value);
setValue('sideSpecHeight', e.target.value);
value={parseFloat(sideSpecHeight) || 0}
onChange={(value) => {
const strValue = String(value ?? 0);
setSideSpecHeight(strValue);
setValue('sideSpecHeight', strValue);
}}
/>
</div>

View File

@@ -5,6 +5,7 @@
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { NumberInput } from '@/components/ui/number-input';
import {
Select,
SelectContent,
@@ -156,15 +157,16 @@ export default function BendingPartForm({
<span className="text-red-500">*</span>
</Label>
<div className="flex items-center gap-2">
<Input
type="number"
value={widthSum}
onChange={(e) => {
setWidthSum(e.target.value);
setValue('length', e.target.value);
<NumberInput
value={parseFloat(widthSum) || 0}
onChange={(value) => {
const strValue = String(value ?? 0);
setWidthSum(strValue);
setValue('length', strValue);
}}
placeholder="전개도 상세를 입력해주세요"
readOnly={bendingDetailsLength > 0}
disabled={bendingDetailsLength > 0}
allowDecimal
className={`${bendingDetailsLength > 0 ? "bg-blue-50 font-medium" : ""} ${(errors as any).length ? 'border-red-500' : ''}`}
/>
<span className="text-sm text-muted-foreground">mm</span>

View File

@@ -7,6 +7,8 @@ import { useMemo } from 'react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { NumberInput } from '@/components/ui/number-input';
import { QuantityInput } from '@/components/ui/quantity-input';
import {
Select,
SelectContent,
@@ -171,7 +173,7 @@ export default function PurchasedPartForm({
<div className="md:col-span-2 grid grid-cols-2 gap-4 p-4 bg-green-50 rounded-lg border border-green-200">
<div>
<Label> (kg) *</Label>
<Input type="number" placeholder="예: 1.5" step="0.1" />
<NumberInput placeholder="예: 1.5" step={0.1} allowDecimal />
</div>
<div>
<Label> (V) *</Label>
@@ -229,7 +231,7 @@ export default function PurchasedPartForm({
</div>
<div>
<Label> ( ) *</Label>
<Input type="number" placeholder="예: 100" />
<QuantityInput placeholder="예: 100" />
</div>
</div>
)}