Some checks failed
PR checks / checks (pull_request) Failing after 3s
The company form outgrew the modal once the branding (logo/stamp) section was added. Add/edit now live on their own routes: - /admin/companies/new - /admin/companies/[id]/edit - createCompany returns the new id and the create flow lands on the edit page so logo/stamp can be uploaded immediately - list "+ Add Company" is a link; row "Edit" navigates to the edit page - branding is its own card on the edit page (independent uploads) - list page no longer mints a presigned URL per company (moved to edit) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
153 lines
6.8 KiB
TypeScript
153 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import Link from "next/link";
|
|
import { ArrowLeft } from "lucide-react";
|
|
import { createCompany, updateCompany } from "./actions";
|
|
import { CompanyBrandingUploader } from "./company-branding-uploader";
|
|
|
|
export type CompanyFormData = {
|
|
id: string;
|
|
name: string;
|
|
code: string | null;
|
|
gstNumber: string | null;
|
|
address: string | null;
|
|
telephone: string | null;
|
|
mobile: string | null;
|
|
email: string | null;
|
|
invoiceEmail: string | null;
|
|
invoiceAddress: string | null;
|
|
logoUrl: string | null;
|
|
stampUrl: string | null;
|
|
isActive: boolean;
|
|
};
|
|
|
|
const INPUT = "w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20";
|
|
const LABEL = "block text-xs font-medium text-neutral-700 mb-1";
|
|
|
|
function CompanyFormFields({ company }: { company?: CompanyFormData }) {
|
|
return (
|
|
<div className="space-y-3">
|
|
<div className="grid grid-cols-3 gap-3">
|
|
<div className="col-span-2">
|
|
<label className={LABEL}>Company Name *</label>
|
|
<input name="name" defaultValue={company?.name} required className={INPUT} placeholder="e.g. Pelagia Marine Services Pvt. Ltd." />
|
|
</div>
|
|
<div>
|
|
<label className={LABEL}>Code * <span className="font-normal text-neutral-400">(used in PO numbers)</span></label>
|
|
<input name="code" defaultValue={company?.code ?? ""} required maxLength={10}
|
|
className={`${INPUT} uppercase`} placeholder="e.g. PMS"
|
|
style={{ textTransform: "uppercase" }} />
|
|
</div>
|
|
</div>
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div>
|
|
<label className={LABEL}>GST Number</label>
|
|
<input name="gstNumber" defaultValue={company?.gstNumber ?? ""} className={INPUT} placeholder="e.g. 27AAHCP5787B1Z6" />
|
|
</div>
|
|
<div className="col-span-2">
|
|
<label className={LABEL}>Contact Email</label>
|
|
<input name="email" type="email" defaultValue={company?.email ?? ""} className={INPUT} placeholder="contact@company.com" />
|
|
</div>
|
|
<div>
|
|
<label className={LABEL}>Invoice Email <span className="font-normal text-neutral-400">(shown on POs)</span></label>
|
|
<input name="invoiceEmail" type="email" defaultValue={company?.invoiceEmail ?? ""} className={INPUT} placeholder="accounts@company.com" />
|
|
</div>
|
|
<div>
|
|
<label className={LABEL}>Telephone</label>
|
|
<input name="telephone" defaultValue={company?.telephone ?? ""} className={INPUT} placeholder="+91-22-1234 5678" />
|
|
</div>
|
|
<div>
|
|
<label className={LABEL}>Mobile</label>
|
|
<input name="mobile" defaultValue={company?.mobile ?? ""} className={INPUT} placeholder="+91 98765 43210" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className={LABEL}>Address</label>
|
|
<textarea name="address" defaultValue={company?.address ?? ""} rows={2} className={INPUT} placeholder="Office address" />
|
|
</div>
|
|
<div>
|
|
<label className={LABEL}>Invoice Address <span className="font-normal text-neutral-400">(shown on exported POs)</span></label>
|
|
<textarea name="invoiceAddress" defaultValue={company?.invoiceAddress ?? ""} rows={2} className={INPUT} placeholder="Full address as it should appear on invoices/POs" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function CompanyForm({ company }: { company?: CompanyFormData }) {
|
|
const router = useRouter();
|
|
const isEdit = !!company?.id;
|
|
const [pending, setPending] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
e.preventDefault();
|
|
setPending(true);
|
|
setError("");
|
|
const fd = new FormData(e.currentTarget);
|
|
|
|
if (isEdit) {
|
|
fd.set("id", company!.id);
|
|
const result = await updateCompany(fd);
|
|
if ("error" in result) { setError(result.error); setPending(false); return; }
|
|
router.push("/admin/companies");
|
|
router.refresh();
|
|
} else {
|
|
const result = await createCompany(fd);
|
|
if ("error" in result) { setError(result.error); setPending(false); return; }
|
|
// Land on the edit page so the logo/stamp can be uploaded against the new company.
|
|
router.push(`/admin/companies/${result.id}/edit`);
|
|
router.refresh();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-3xl">
|
|
<Link href="/admin/companies" className="inline-flex items-center gap-1.5 text-sm text-neutral-500 hover:text-neutral-700 mb-3">
|
|
<ArrowLeft className="h-3.5 w-3.5" /> Back to Companies
|
|
</Link>
|
|
<h1 className="text-2xl font-semibold text-neutral-900">{isEdit ? `Edit — ${company!.name}` : "Add Company"}</h1>
|
|
<p className="text-sm text-neutral-500 mt-0.5 mb-6">Sister company used for invoicing and purchase orders</p>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="rounded-lg border border-neutral-200 bg-white p-5">
|
|
<CompanyFormFields company={company} />
|
|
</div>
|
|
{error && <p className="text-sm text-danger-700 bg-danger-50 rounded-lg px-3 py-2">{error}</p>}
|
|
<div className="flex justify-end gap-3">
|
|
<Link href="/admin/companies"
|
|
className="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50">
|
|
Cancel
|
|
</Link>
|
|
<button type="submit" disabled={pending}
|
|
className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-700 disabled:opacity-60">
|
|
{pending ? (isEdit ? "Saving…" : "Creating…") : (isEdit ? "Save Changes" : "Create Company")}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
{/* ── Branding (independent uploads; available once the company exists) ── */}
|
|
<div className="rounded-lg border border-neutral-200 bg-white p-5 mt-6">
|
|
<h2 className="text-sm font-semibold text-neutral-800">Branding</h2>
|
|
<p className="text-xs text-neutral-400 mb-3">Logo and stamp shown on exported POs</p>
|
|
{isEdit ? (
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<CompanyBrandingUploader
|
|
companyId={company!.id} type="logo" label="Logo"
|
|
hint="PNG, JPG or WebP — shown top-left. Max 4 MB"
|
|
currentUrl={company!.logoUrl}
|
|
/>
|
|
<CompanyBrandingUploader
|
|
companyId={company!.id} type="stamp" label="Stamp / Seal"
|
|
hint="PNG, JPG or WebP — shown in signatory block. Max 4 MB"
|
|
currentUrl={company!.stampUrl}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<p className="text-xs text-neutral-400">Create the company first — you'll be taken to the edit page where you can upload a logo and stamp.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|