import React, { useState, useEffect, useMemo } from "react"; import { Form, Input, InputNumber, Button, Card, Typography, Modal, message, Divider, Select, } from "antd"; import { PlusOutlined, DeleteOutlined, EditOutlined, CheckOutlined, CloseOutlined, } from "@ant-design/icons"; import { v4 as uuidv4 } from "uuid"; import { supabase } from "@/config/supabase"; import { supabaseService } from "@/hooks/supabaseService"; const { Text } = Typography; import { defaultSymbol, formatExchangeRate } from "@/utils/exchange_rate"; const SectionList = ({ form, isView, formValues, type, currentCurrency = "TWD", taxRate, setTaxRate, }) => { const [editingSectionIndex, setEditingSectionIndex] = useState(null); const [editingSectionName, setEditingSectionName] = useState(""); const [templateModalVisible, setTemplateModalVisible] = useState(false); const [availableSections, setAvailableSections] = useState([]); const [loading, setLoading] = useState(false); const [units, setUnits] = useState([]); const [loadingUnits, setLoadingUnits] = useState(false); const fetchAvailableSections = async () => { try { setLoading(true); const { data: sections, error } = await supabase .from("resources") .select("*") .eq("type", "sections") .eq("attributes->>template_type", [type]) .order("created_at", { ascending: false }); if (error) throw error; setAvailableSections(sections || []); } catch (error) { message.error("获取小节模板失败"); console.error(error); } finally { setLoading(false); } }; const handleSectionNameEdit = (sectionIndex, initialValue) => { setEditingSectionIndex(sectionIndex); setEditingSectionName(initialValue || ""); }; const handleSectionNameSave = () => { if (!editingSectionName.trim()) { message.error("请输入小节名称"); return; } const sections = form.getFieldValue("sections"); const newSections = [...sections]; newSections[editingSectionIndex] = { ...newSections[editingSectionIndex], sectionName: editingSectionName.trim(), }; form.setFieldValue("sections", newSections); setEditingSectionIndex(null); setEditingSectionName(""); }; const handleAddItem = (add) => { add({ key: uuidv4(), name: "", description: "", quantity: 1, price: 0, unit: "", }); }; const handleUseTemplate = (template, add) => { const newSection = { key: uuidv4(), sectionName: template.attributes.name, items: (template.attributes.items || []).map((item) => ({ key: uuidv4(), name: item.name || "", description: item.description || "", price: item.price || 0, quantity: item.quantity || 1, unit: item.unit || "", })), }; add(newSection); setTemplateModalVisible(false); message.success("套用模版成功"); }; const handleCreateCustom = (add, fieldsLength) => { add({ key: uuidv4(), sectionName: `服务类型 ${fieldsLength + 1}`, items: [ { key: uuidv4(), name: "", description: "", quantity: 1, price: 0, unit: "", }, ], }); setTemplateModalVisible(false); }; const fetchUnits = async () => { setLoadingUnits(true); try { const { data: units } = await supabaseService.select("resources", { filter: { type: { eq: "units" }, "attributes->>template_type": { in: `(${type},common)` }, }, order: { column: "created_at", ascending: false, }, }); setUnits(units || []); } catch (error) { message.error("获取单位列表失败"); console.error(error); } finally { setLoadingUnits(false); } }; useEffect(() => { fetchUnits(); }, []); const handleAddUnit = async (unitName) => { try { const { error } = await supabase.from("resources").insert([ { type: "units", attributes: { name: unitName, template_type: type, }, schema_version: 1, }, ]); if (error) throw error; message.success("新增单位成功"); fetchUnits(); return true; } catch (error) { message.error("新增单位失败"); console.error(error); return false; } }; const renderTemplateModalContent = (add, fieldsLength) => (
{availableSections.length > 0 ? (
{availableSections.map((section) => (
handleUseTemplate(section, add)} >

{section.attributes.name}

{section.attributes.items?.length || 0} 个项目
{(section.attributes.items || []) .slice(0, 3) .map((item, index) => (
{item.name} {formatExchangeRate(currentCurrency, item.price)}
))} {(section.attributes.items || []).length > 3 && (
还有 {section.attributes.items.length - 3} 个项目...
)}
总金额 {formatExchangeRate( currentCurrency, (section.attributes.items || []).reduce( (sum, item) => sum + (item.price * (item.quantity || 1) || 0), 0 ) )}
))}
) : (

暂无可用模板

您可以选择创建一个自定义模块开始使用

)}
); const ItemSubtotal = React.memo(({ quantity, price, currentCurrency }) => { const subtotal = useMemo(() => { const safeQuantity = Number(quantity) || 0; const safePrice = Number(price) || 0; return safeQuantity * safePrice; }, [quantity, price]); return (
{formatExchangeRate(currentCurrency, subtotal)}
); }); const SectionTotal = React.memo(({ items, currentCurrency }) => { const total = useMemo(() => { if (!Array.isArray(items)) return 0; return items.reduce((sum, item) => { if (!item) return sum; const safeQuantity = Number(item.quantity) || 0; const safePrice = Number(item.price) || 0; return sum + safeQuantity * safePrice; }, 0); }, [items]); return (
小计总额: {formatExchangeRate(currentCurrency, total)}
); }); return ( <> {(fields, { add, remove }) => ( <>
{fields.map((field, sectionIndex) => (
{editingSectionIndex === sectionIndex ? (
setEditingSectionName(e.target.value) } onPressEnter={handleSectionNameSave} autoFocus className="w-48" />
) : (
{form.getFieldValue([ "sections", sectionIndex, "sectionName", ]) || `服务类型 ${sectionIndex + 1}`} {!isView && (
)}
{!isView && (
} > {(itemFields, { add: addItem, remove: removeItem }) => ( <>
项目明细
描述/备注
单位
数量
单价({defaultSymbol})
小计
{itemFields.map((itemField, itemIndex) => { const { key, ...restItemField } = itemField; return (