ERP-node/frontend/components/report/designer/modals/TextLayoutTabs.tsx

164 lines
5.7 KiB
TypeScript

"use client";
/**
* TextLayoutTabs.tsx — 텍스트 컴포넌트 설정
*
* [역할]
* - 텍스트 내용 입력 + 데이터 바인딩(queryId/fieldName) 설정
* - ComponentSettingsModal 내에 직접 임베드되어 사용
*
* [사용처]
* - TextProperties.tsx (section="data"일 때)
*/
import React, { useCallback, useMemo } from "react";
import { Textarea } from "@/components/ui/textarea";
import { Input } from "@/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Type, Database, Link2 } from "lucide-react";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import { Section, TabContent, Field, FieldGroup, InfoBox } from "./shared";
import type { ComponentConfig } from "@/types/report";
interface TextLayoutTabsProps {
component: ComponentConfig;
}
export function TextLayoutTabs({ component }: TextLayoutTabsProps) {
const { updateComponent, queries, getQueryResult } = useReportDesigner();
const update = useCallback(
(updates: Partial<ComponentConfig>) => updateComponent(component.id, updates),
[component.id, updateComponent],
);
const selectedQueryFields = useMemo(() => {
if (!component.queryId) return [];
const result = getQueryResult(component.queryId);
if (result?.fields) return result.fields;
return [];
}, [component.queryId, getQueryResult]);
const hasBinding = !!(component.queryId && component.fieldName);
return (
<TabContent>
{/* 데이터 바인딩 섹션 */}
<Section
emphasis
icon={<Database className="h-3.5 w-3.5" />}
title="데이터 바인딩"
>
<FieldGroup>
<Field label="데이터 소스 (쿼리)">
<Select
value={component.queryId || "none"}
onValueChange={(value) =>
update({
queryId: value === "none" ? undefined : value,
fieldName: value === "none" ? undefined : component.fieldName,
})
}
>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="쿼리 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> ( )</SelectItem>
{queries.map((q) => (
<SelectItem key={q.id} value={q.id}>
{q.name} ({q.type})
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
{component.queryId && (
<Field label="바인딩 필드">
{selectedQueryFields.length > 0 ? (
<Select
value={component.fieldName || "none"}
onValueChange={(value) =>
update({ fieldName: value === "none" ? undefined : value })
}
>
<SelectTrigger className="h-9 text-sm">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{selectedQueryFields.map((field) => (
<SelectItem key={field} value={field}>
{field}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
value={component.fieldName || ""}
onChange={(e) => update({ fieldName: e.target.value || undefined })}
placeholder="필드명 직접 입력 (예: doc_number)"
className="h-9 text-sm"
/>
)}
</Field>
)}
{!component.queryId && component.fieldName && (
<Field label="바인딩 필드 (쿼리 미연결)">
<div className="flex items-center gap-2">
<Link2 className="h-3.5 w-3.5 shrink-0 text-amber-500" />
<Input
value={component.fieldName || ""}
onChange={(e) => update({ fieldName: e.target.value || undefined })}
placeholder="필드명"
className="h-9 text-sm"
/>
</div>
<p className="mt-1 text-[11px] text-amber-600">
.
</p>
</Field>
)}
</FieldGroup>
{hasBinding && (
<InfoBox variant="blue">
<code className="rounded bg-blue-100 px-1 text-xs font-mono">{`{${component.fieldName}}`}</code> .
</InfoBox>
)}
</Section>
{/* 기본값 / 정적 텍스트 */}
<Section icon={<Type className="h-3.5 w-3.5" />} title={hasBinding ? "기본값 (데이터 없을 때)" : "텍스트 내용"}>
<FieldGroup>
<Field label={hasBinding ? "기본 표시 텍스트" : "표시 텍스트"}>
<Textarea
value={component.defaultValue || ""}
onChange={(e) => update({ defaultValue: e.target.value })}
placeholder={
hasBinding
? "데이터가 없을 때 표시할 텍스트"
: "텍스트 내용을 입력하세요 (엔터로 줄바꿈 가능)"
}
className="min-h-[72px] resize-y text-sm"
/>
</Field>
</FieldGroup>
</Section>
<InfoBox variant="gray">
, , .
</InfoBox>
</TabContent>
);
}