diff --git a/package-lock.json b/package-lock.json index 846af9e..cbc8cc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "lucide-react": "^0.545.0", "mailparser": "^3.9.15", "next": "16.2.12", + "nodemailer": "^9.1.0", "pdf-parse": "^2.4.5", "pizzip": "^3.2.0", "react": "19.2.4", @@ -32,6 +33,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/mailparser": "^3.4.6", "@types/node": "^20", + "@types/nodemailer": "^8.0.1", "@types/react": "^19", "@types/react-dom": "^19", "@types/web-push": "^3.6.4", @@ -2596,6 +2598,16 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/nodemailer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-8.0.1.tgz", + "integrity": "sha512-PxpaInm8V1JQDd4j0ds5HfvWQk8JupS1C0Picb96QJsrrRDjBH+DlK7L4ZdNSqNULhiZRQHc40nLVShaGxXAMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/react": { "version": "19.2.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", @@ -6630,6 +6642,15 @@ "tlds": "1.261.0" } }, + "node_modules/mailparser/node_modules/nodemailer": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.5.tgz", + "integrity": "sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6911,9 +6932,10 @@ } }, "node_modules/nodemailer": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.5.tgz", - "integrity": "sha512-wvjiKvjczmsN7U/8006JOdXubgBk2XFAbioDMbT+sM7cPs0QrhJTa6KBRX7P5REGGkDcLUz/EarWidb8G8C1jQ==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.1.0.tgz", + "integrity": "sha512-xj1Ri5Sau3qpPffHJwi2bY0oWVVWYq62Ph17l+2v1xXpxjqTls3YPc3L8dub9mcJpxj+1ucNnuYVqLlgh4pmDA==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } diff --git a/package.json b/package.json index eb2bc2d..02df20a 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "lucide-react": "^0.545.0", "mailparser": "^3.9.15", "next": "16.2.12", + "nodemailer": "^9.1.0", "pdf-parse": "^2.4.5", "pizzip": "^3.2.0", "react": "19.2.4", @@ -36,6 +37,7 @@ "@types/better-sqlite3": "^7.6.13", "@types/mailparser": "^3.4.6", "@types/node": "^20", + "@types/nodemailer": "^8.0.1", "@types/react": "^19", "@types/react-dom": "^19", "@types/web-push": "^3.6.4", diff --git a/src/app/(admin)/inquiries/[id]/inquiry-actions.tsx b/src/app/(admin)/inquiries/[id]/inquiry-actions.tsx index d8f9d4b..53487d8 100644 --- a/src/app/(admin)/inquiries/[id]/inquiry-actions.tsx +++ b/src/app/(admin)/inquiries/[id]/inquiry-actions.tsx @@ -2,10 +2,28 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; +import { DeclineWithReason } from "@/components/decline-with-reason"; + +const STATUS_OPTIONS = [ + { value: "kp_preparation", label: "Подготовить и отправить КП" }, + { value: "kp_response", label: "Получить ОС по КП" }, + { value: "negotiation", label: "Переговоры" }, + { value: "signing", label: "Подписание документов" }, +]; + +const DECLINE_REASONS = [ + { value: "too_expensive", label: "Дорого" }, + { value: "unreachable", label: "Пропал/Не выходит на связь" }, + { value: "lost_to_competitor", label: "Ушли к конкурентам" }, + { value: "postponed", label: "Отложили заявку в обработку" }, + { value: "pricing_calc", label: "Калькуляция" }, + { value: "slow_processing", label: "Долгое время обработки запроса" }, + { value: "other", label: "Другая причина отказа" }, +]; export function InquiryActions({ inquiryId, status }: { inquiryId: string; status: string }) { const router = useRouter(); - const [loading, setLoading] = useState<"convert" | "decline" | "answered" | null>(null); + const [loading, setLoading] = useState<"convert" | "status" | null>(null); const [error, setError] = useState(null); async function convert() { @@ -22,19 +40,12 @@ export function InquiryActions({ inquiryId, status }: { inquiryId: string; statu router.push(`/orders/${order.id}`); } - async function decline() { - setLoading("decline"); - await fetch(`/api/inquiries/${inquiryId}/decline`, { method: "POST" }); - setLoading(null); - router.refresh(); - } - - async function toggleAnswered(answered: boolean) { - setLoading("answered"); - await fetch(`/api/inquiries/${inquiryId}/answered`, { - method: "POST", + async function changeStatus(next: string) { + setLoading("status"); + await fetch(`/api/inquiries/${inquiryId}`, { + method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ answered }), + body: JSON.stringify({ status: next }), }); setLoading(null); router.refresh(); @@ -43,22 +54,31 @@ export function InquiryActions({ inquiryId, status }: { inquiryId: string; statu if (status === "converted" || status === "declined") return null; return ( -
- {status === "new" ? ( - - ) : ( - - )} - - +
+ + +
+ {status === "signing" && ( + + )} + +
{error &&

{error}

}
); diff --git a/src/app/(admin)/inquiries/[id]/inquiry-edit-form.tsx b/src/app/(admin)/inquiries/[id]/inquiry-edit-form.tsx index 06994d6..d745bcd 100644 --- a/src/app/(admin)/inquiries/[id]/inquiry-edit-form.tsx +++ b/src/app/(admin)/inquiries/[id]/inquiry-edit-form.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { AddressAutocomplete } from "@/components/address-autocomplete"; +import { SearchableSelect, type SearchableOption } from "@/components/searchable-select"; interface Inquiry { id: string; @@ -14,9 +15,41 @@ interface Inquiry { transportType: "consolidated" | "tent" | "trawl" | "backhaul" | "container" | null; comment: string | null; deadline: string | null; + incoterm: string | null; + paymentTerms: string | null; + prepaymentPercent: number | null; + carrierPayTerms: string | null; + receivablesDeadline: Date | string | null; + carrier1Id: string | null; + carrier2Id: string | null; + carrier3Id: string | null; + insuranceCompanyId: string | null; + customsBrokerId: string | null; + recipientContactName: string | null; } -export function InquiryEditForm({ inquiry }: { inquiry: Inquiry }) { +const INCOTERMS = ["EXW", "FCA", "FAS", "FOB", "CFR", "CIF", "CIP", "DAP", "DPU", "DDP"]; +const PAYMENT_TERMS_LABEL: Record = { + prepayment: "Предоплата", + postpayment: "Постоплата", + partial_prepayment: "Частичная предоплата", +}; + +function toDateInputValue(date: Date | string | null): string { + if (!date) return ""; + const d = new Date(date); + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; +} + +export function InquiryEditForm({ + inquiry, + carriers, + counterparties, +}: { + inquiry: Inquiry; + carriers: SearchableOption[]; + counterparties: SearchableOption[]; +}) { const router = useRouter(); const [cargoDescription, setCargoDescription] = useState(inquiry.cargoDescription); const [routeFrom, setRouteFrom] = useState(inquiry.routeFrom); @@ -26,6 +59,17 @@ export function InquiryEditForm({ inquiry }: { inquiry: Inquiry }) { const [transportType, setTransportType] = useState(inquiry.transportType ?? ""); const [comment, setComment] = useState(inquiry.comment ?? ""); const [deadline, setDeadline] = useState(inquiry.deadline ?? ""); + const [incoterm, setIncoterm] = useState(inquiry.incoterm ?? ""); + const [paymentTerms, setPaymentTerms] = useState(inquiry.paymentTerms ?? ""); + const [prepaymentPercent, setPrepaymentPercent] = useState(inquiry.prepaymentPercent?.toString() ?? ""); + const [carrierPayTerms, setCarrierPayTerms] = useState(inquiry.carrierPayTerms ?? ""); + const [receivablesDeadline, setReceivablesDeadline] = useState(toDateInputValue(inquiry.receivablesDeadline)); + const [carrier1Id, setCarrier1Id] = useState(inquiry.carrier1Id ?? ""); + const [carrier2Id, setCarrier2Id] = useState(inquiry.carrier2Id ?? ""); + const [carrier3Id, setCarrier3Id] = useState(inquiry.carrier3Id ?? ""); + const [insuranceCompanyId, setInsuranceCompanyId] = useState(inquiry.insuranceCompanyId ?? ""); + const [customsBrokerId, setCustomsBrokerId] = useState(inquiry.customsBrokerId ?? ""); + const [recipientContactName, setRecipientContactName] = useState(inquiry.recipientContactName ?? ""); const [loading, setLoading] = useState(false); const [saved, setSaved] = useState(false); const [error, setError] = useState(null); @@ -48,6 +92,17 @@ export function InquiryEditForm({ inquiry }: { inquiry: Inquiry }) { transportType: transportType || undefined, comment: comment || undefined, deadline: deadline || undefined, + incoterm: incoterm || undefined, + paymentTerms: paymentTerms || undefined, + prepaymentPercent: prepaymentPercent ? Number(prepaymentPercent) : undefined, + carrierPayTerms: carrierPayTerms || undefined, + receivablesDeadline: receivablesDeadline || undefined, + carrier1Id: carrier1Id || undefined, + carrier2Id: carrier2Id || undefined, + carrier3Id: carrier3Id || undefined, + insuranceCompanyId: insuranceCompanyId || undefined, + customsBrokerId: customsBrokerId || undefined, + recipientContactName: recipientContactName || undefined, }), }); @@ -141,6 +196,110 @@ export function InquiryEditForm({ inquiry }: { inquiry: Inquiry }) { /> +
+

Условия сделки

+
+ + +
+ {paymentTerms === "partial_prepayment" && ( + + )} +