chore: initial project import
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
Some checks failed
CI - Production Readiness / Verify (push) Has been cancelled
This commit is contained in:
95
app/contacts/import/page.tsx
Normal file
95
app/contacts/import/page.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user