feat: QMS 체크리스트 템플릿 관리 및 견적/리스트 개선

- QMS 체크리스트 템플릿 에디터 추가 (ChecklistTemplateEditor)
- AuditSettingsPanel, Day1DocumentSection 기능 확장
- 견적 등록(QuoteRegistration) 개선
- IntegratedListTemplateV2 수정
- 건설 카테고리 actions 수정
This commit is contained in:
유병철
2026-03-11 11:06:10 +09:00
parent 81affdc441
commit e9ac2470e1
10 changed files with 1382 additions and 132 deletions

View File

@@ -59,7 +59,8 @@ export async function getCategories(): Promise<{
data: ApiCategory[];
}>('/categories', { params: { per_page: '100' } });
const categories = (response.data || [])
const rawData = Array.isArray(response.data) ? response.data : (response.data as unknown as { data: ApiCategory[] })?.data || [];
const categories = rawData
.map(transformCategory)
.sort((a, b) => a.order - b.order);

View File

@@ -25,7 +25,7 @@ import {
SelectTrigger,
SelectValue,
} from "../ui/select";
// FormField는 실제로 사용되지 않으므로 제거
import { SearchableSelect } from "../ui/searchable-select";
import { LocationListPanel } from "./LocationListPanel";
import { LocationDetailPanel } from "./LocationDetailPanel";
@@ -433,16 +433,6 @@ export function QuoteRegistration({
setFormData((prev) => ({ ...prev, [field]: value }));
}, []);
// 발주처 선택
const handleClientChange = useCallback((clientId: string) => {
const client = clients.find((c) => c.id === clientId);
setFormData((prev) => ({
...prev,
clientId,
clientName: client?.vendorName || "",
}));
}, [clients]);
// 개소 추가 (BOM 계산 성공 시에만 추가, 성공/실패 반환)
const handleAddLocation = useCallback(async (location: Omit<LocationItem, "id">): Promise<boolean> => {
const newLocation: LocationItem = {
@@ -774,37 +764,34 @@ export function QuoteRegistration({
</div>
<div>
<label className="text-sm font-medium text-gray-700"> <span className="text-red-500">*</span></label>
<Select
<SearchableSelect
options={clients.map((c) => ({ value: c.id, label: c.vendorName }))}
value={formData.clientId}
onValueChange={handleClientChange}
onChange={(value, option) => {
setFormData((prev) => ({
...prev,
clientId: value,
clientName: option.label,
}));
}}
placeholder={isLoadingClients ? "로딩 중..." : "수주처를 선택하세요"}
searchPlaceholder="수주처 검색..."
emptyText="수주처가 없습니다"
disabled={isViewMode || isLoadingClients}
>
<SelectTrigger>
<SelectValue placeholder={isLoadingClients ? "로딩 중..." : "수주처를 선택하세요"} />
</SelectTrigger>
<SelectContent>
{clients.map((client) => (
<SelectItem key={client.id} value={client.id}>
{client.vendorName}
</SelectItem>
))}
</SelectContent>
</Select>
isLoading={isLoadingClients}
/>
</div>
<div>
<label className="text-sm font-medium text-gray-700"></label>
<Input
list="siteNameList"
placeholder="현장명을 입력하세요"
<SearchableSelect
options={siteNames.map((name) => ({ value: name, label: name }))}
value={formData.siteName}
onChange={(e) => handleFieldChange("siteName", e.target.value)}
onChange={(value) => handleFieldChange("siteName", value)}
placeholder="현장명을 선택하세요"
searchPlaceholder="현장명 검색..."
emptyText="현장명이 없습니다"
disabled={isViewMode}
/>
<datalist id="siteNameList">
{siteNames.map((name) => (
<option key={name} value={name} />
))}
</datalist>
</div>
</div>

View File

@@ -977,8 +977,8 @@ export function IntegratedListTemplateV2<T = any>({
showActions={tableColumns.some(col => col.key === 'actions')}
/>
) : (
<Table className="table-fixed">
{columnSettings && (
<Table className={columnSettings && Object.keys(columnSettings.columnWidths).length > 0 ? "table-fixed" : "table-auto [&_td]:whitespace-nowrap [&_th]:whitespace-nowrap"}>
{columnSettings && Object.keys(columnSettings.columnWidths).length > 0 && (
<colgroup>
{showCheckbox && <col style={{ width: 50 }} />}
{tableColumns.map((col) => (
@@ -1046,6 +1046,24 @@ export function IntegratedListTemplateV2<T = any>({
e.preventDefault();
const th = (e.target as HTMLElement).parentElement;
if (!th) return;
// 첫 리사이즈 시 모든 컬럼의 현재 너비를 스냅샷 저장
// → table-auto → table-fixed 전환 시 다른 컬럼 크기 유지
if (Object.keys(columnSettings.columnWidths).length === 0) {
const headerRow = th.parentElement;
if (headerRow) {
const allThs = headerRow.querySelectorAll('th');
const offset = showCheckbox ? 1 : 0;
allThs.forEach((cell, idx) => {
if (idx < offset) return;
const colKey = tableColumns[idx - offset]?.key;
if (colKey) {
columnSettings.onColumnResize(colKey, cell.offsetWidth);
}
});
}
}
const startX = e.clientX;
const startWidth = th.offsetWidth;
const onMouseMove = (ev: MouseEvent) => {