diff --git a/static/app.js b/static/app.js index 675ad03..d5097eb 100644 --- a/static/app.js +++ b/static/app.js @@ -174,52 +174,39 @@ window.createFinance = (event) => createResource(event, "finance"); window.switchTab = switchTab; function renderProjects() { - // Merge sales_leads and operation_projects into one table - const salesItems = state.data.sales.map((x) => ({ - name: x.target_customer, - version: "", - type: "opportunity", - status: x.status, - amount: 0, - stage: "", - files: 0, - followup: x.latest_follow_up_record, - resource: "sales", - id: x.id, - })); - const opItems = state.data.operations.map((x) => ({ - name: x.project_name, - version: x.project_version, - type: x.project_type, - status: x.project_status, - amount: x.expected_contract_amount || 0, - stage: x.current_stage || x.sop_stage, - files: x.files.length, - followup: x.latest_follow_up_record, - resource: "operations", - id: x.id, - })); - const allItems = [...salesItems, ...opItems]; - const items = state.opFilter === "all" ? allItems : allItems.filter((x) => x.type === state.opFilter || (state.opFilter === "opportunity" && x.type === "opportunity")); - const rows = items.map((x) => [`${x.name}${x.version ? `

${x.version}

` : ""}`, badge(x.type), badge(x.status), x.amount ? money(x.amount) : "—", text(x.stage), text(x.followup)]); - const clicks = items.map((x) => ({ resource: x.resource, id: x.id })); + const items = state.data.operations; + const rows = items.map((x) => [ + `${x.project_name}

${x.project_version}

`, + text(x.customer_need || x.notes), + badge(x.current_stage || x.project_status), + x.expected_contract_amount ? money(x.expected_contract_amount) : "—", + text(x.owner || "—"), + `` + ]); document.querySelector("#projects").innerHTML = `
- ${card(formHtml([ - { label: "业务机会", input: `` }, - { label: "优先级", input: `` }, - { label: "状态", input: `` }, - ], { handler: "createSales", text: `新增业务机会` }), "p-4")} ${card(formHtml([ { label: "项目名称", input: `` }, { label: "项目版本", input: `` }, - { label: "项目类型", input: `` }, - { label: "状态", input: `` }, + { label: "当前阶段", input: `` }, + { label: "项目金额", input: `` }, + { label: "负责人", input: `` }, ], { handler: "createOperation", text: "新增项目" }), "p-4")} -
${[["all","全部"],["opportunity","业务机会"],["execution","已签约执行"]].map(([k,v]) => ``).join("")}
- ${renderTable(["项目/客户", "类型", "状态", "金额", "当前阶段", "最新跟进"], rows, clicks)} + ${renderTable(["项目", "项目说明", "当前阶段", "项目金额", "负责人", "进展"], rows, items.map((x) => ({ resource: "operations", id: x.id })))}
`; } +function showTaskModal(projectId) { + const project = state.data.operations.find((x) => x.id === projectId); + const tasks = (state.data.tasks || []).filter((t) => t.project_id === projectId); + const phases = ["项目准备", "项目执行", "项目验收", "验收完毕"]; + document.querySelector("#taskModal").innerHTML = `

${project.project_name} · 任务清单

${phases.map((phase) => { + const pt = tasks.filter((t) => t.phase === phase); + return `
${phase}${pt.length ? ` (${pt.filter(t=>t.task).length}/${pt.length})` : ""}
${pt.length ? pt.map((t) => `

${t.milestone ? `${t.milestone}:` : ""}${t.task}

${t.owner ? `

👤 ${t.owner}${t.due_date ? " · 📅 " + t.due_date : ""}

` : ""}
`).join("") : `

暂无任务

`}
`; + }).join("")}
`; + document.querySelector("#taskModal").classList.add("active"); +} +window.closeTaskModal = () => document.querySelector("#taskModal").classList.remove("active"); + function renderProposals() { const proposalRows = state.data.proposals.map((p) => [p.customer_or_project_name, p.version, badge(p.status), p.files.length + " 个"]); const proposalClicks = state.data.proposals.map((p) => ({ resource: "proposals", id: p.id })); @@ -354,20 +341,6 @@ function openDrawer(resource, id) {
${fields.map(([key,label]) => drawerField(fieldIcons[key] || "circle", label, key, item[key], multilineFields.includes(key))).join("")}
${resource === "proposals" ? `

方案文件

${["方案","成本","SOP","财务流程"].map((cat) => fileGroup("proposal", item.id, item.version, cat, item.files.filter((f) => f.file_category === cat))).join("")}
` : ""} - ${resource === "operations" ? (() => { - const tasks = (state.data.tasks || []).filter((t) => t.project_id === id); - if (!tasks.length) return ""; - const phases = [...new Set(tasks.map((t) => t.phase))]; - return `

项目任务

${phases.map((phase) => { - const pt = tasks.filter((t) => t.phase === phase); - return `

${phase}

${pt.map((t) => { - const due = t.due_date ? `📅 ${t.due_date}` : ""; - const owner = t.owner ? `👤 ${t.owner}` : ""; - const blocker = t.blockers ? `

⚠ ${t.blockers}

` : ""; - return `

${t.milestone ? t.milestone + ":": ""}${t.task}${due}${owner}

${blocker}
`; - }).join("")}
`; - }).join("")}
`; - })() : ""} ${followupTarget ? `

活动 / 跟进

${(item.followups || []).map((f) => `
${f.follower} · ${f.follow_up_method}${f.followed_at}
${f.next_action ? `

下一步:${text(f.next_action)}

` : ""}
`).join("")}
diff --git a/static/styles.css b/static/styles.css index 014fb65..3e5acbf 100644 --- a/static/styles.css +++ b/static/styles.css @@ -489,3 +489,16 @@ td { color: #64748b; margin: 4px 0; } + +/* Task Modal */ +.task-modal { display: none; } +.task-modal.active { display: block; } +.task-overlay { + position: fixed; inset: 0; background: rgba(15,23,42,0.4); z-index: 200; + display: flex; align-items: flex-start; justify-content: center; + padding-top: 60px; overflow-y: auto; +} +.task-panel { + background: white; border-radius: 12px; width: 800px; max-width: 90vw; + box-shadow: 0 20px 60px rgba(0,0,0,0.15); margin-bottom: 60px; +} diff --git a/templates/index.html b/templates/index.html index 37595b7..c4c6c58 100644 --- a/templates/index.html +++ b/templates/index.html @@ -51,6 +51,7 @@ +