pelagia-portal/App/app/(portal)/admin/companies/company-form.tsx
Hardik bad67f66c4 feat(companies): move add/edit from dialog to dedicated pages
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>
2026-06-21 01:45:59 +05:30

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&apos;ll be taken to the edit page where you can upload a logo and stamp.</p>
)}
</div>
</div>
);
}