96 lines
3.6 KiB
TypeScript
96 lines
3.6 KiB
TypeScript
import { ShellPage } from "@/components/page-templates";
|
|
import { SectionCard } from "@/components/ui";
|
|
import { getSession } from "@/lib/auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { redirect } from "next/navigation";
|
|
|
|
function formatDate(value: Date | null | undefined) {
|
|
if (!value) {
|
|
return "-";
|
|
}
|
|
|
|
return new Intl.DateTimeFormat("id-ID", {
|
|
day: "2-digit",
|
|
month: "short",
|
|
year: "numeric"
|
|
}).format(value);
|
|
}
|
|
|
|
export default async function ImportContactsPage() {
|
|
const session = await getSession();
|
|
if (!session) {
|
|
redirect("/login");
|
|
}
|
|
|
|
if (session.role === "agent") {
|
|
redirect("/unauthorized");
|
|
}
|
|
|
|
const [channels, tags, sampleContacts] = await Promise.all([
|
|
prisma.channel.findMany({
|
|
where: { tenantId: session.tenantId },
|
|
select: { id: true, channelName: true, provider: true }
|
|
}),
|
|
prisma.tag.findMany({
|
|
where: { tenantId: session.tenantId },
|
|
orderBy: { name: "asc" }
|
|
}),
|
|
prisma.contact.findMany({
|
|
where: { tenantId: session.tenantId },
|
|
orderBy: { createdAt: "desc" },
|
|
take: 5,
|
|
select: { id: true, fullName: true, phoneNumber: true, createdAt: true }
|
|
})
|
|
]);
|
|
|
|
return (
|
|
<ShellPage shell="admin" title="Import Contacts" description="Upload CSV dan mapping ke field contact sesuai channel tenant.">
|
|
<div className="grid gap-6 xl:grid-cols-3">
|
|
<SectionCard title="Step 1" description="Upload CSV">
|
|
<p className="text-sm text-on-surface-variant">
|
|
Pilih file CSV dari browser dan pastikan header minimal: nama, no_telepon, email.
|
|
</p>
|
|
<div className="mt-3 rounded-xl border border-line bg-surface-container p-3">
|
|
<input type="file" className="w-full text-sm" />
|
|
</div>
|
|
</SectionCard>
|
|
<SectionCard title="Step 2" description="Field mapping">
|
|
<div className="space-y-2 text-sm text-on-surface-variant">
|
|
<p>Gunakan channel yang aktif:</p>
|
|
{channels.length === 0 ? (
|
|
<p className="text-sm text-warning">Tenant belum memiliki channel. Tambahkan channel dulu.</p>
|
|
) : (
|
|
<ul className="space-y-1">
|
|
{channels.map((channel) => (
|
|
<li key={channel.id} className="rounded-xl border border-line bg-surface-container p-3">
|
|
{channel.channelName} • {channel.provider}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
</SectionCard>
|
|
<SectionCard title="Step 3" description="Validation preview">
|
|
<div className="space-y-2 text-sm">
|
|
<p className="text-on-surface-variant">Baris terakhir di tenant: {sampleContacts.length} contoh terbaru.</p>
|
|
{sampleContacts.length === 0 ? (
|
|
<p className="text-warning text-sm">Belum ada contact sebelumnya.</p>
|
|
) : (
|
|
<ul className="space-y-2">
|
|
{sampleContacts.map((contact) => (
|
|
<li key={contact.id} className="rounded-xl border border-line bg-surface-container p-3">
|
|
<p className="font-medium text-ink">{contact.fullName}</p>
|
|
<p className="text-xs text-outline">{contact.phoneNumber}</p>
|
|
<p className="text-xs text-outline">Created: {formatDate(contact.createdAt)}</p>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
<p className="text-xs text-on-surface-variant">Available tags: {tags.length > 0 ? tags.map((tag) => tag.name).join(", ") : "Belum ada tag."}</p>
|
|
</div>
|
|
</SectionCard>
|
|
</div>
|
|
</ShellPage>
|
|
);
|
|
}
|