cpns / apps /web /src /components /bank /BundleSidebar.tsx
pramudya-yb
Rename project from Labas to Pram
2b62b0c
Raw
History Blame Contribute Delete
9.04 kB
import { Button } from "@pram/ui/components/button";
import { Input } from "@pram/ui/components/input";
import { MaterialIcon } from "@/components/ui/MaterialIcon";
import { formatLabel } from "@/lib/format";
interface BundleItem {
id: string;
questionText?: string;
title?: string;
examTypeName?: string | null;
format?: string;
sectionTypeName?: string | null;
}
interface BundleSidebarProps {
mode: "soal" | "section";
bundleQuestions: BundleItem[];
bundleSections: BundleItem[];
bundleTitle: string;
bundleDescription: string;
bundleIsPublic: boolean;
isCreating: boolean;
autoBundleExamType: string | null;
lockedExamType?: string | null;
onSetTitle: (v: string) => void;
onSetDescription: (v: string) => void;
onSetIsPublic: (v: boolean) => void;
onRemoveFromBundle: (id: string, type: "question" | "section") => void;
onCreateFromQuestions: () => void;
onCreateFromSections: () => void;
onOpenAutoBundle: () => void;
}
export function BundleSidebar({
mode,
bundleQuestions,
bundleSections,
bundleTitle,
bundleDescription,
bundleIsPublic,
isCreating,
autoBundleExamType,
lockedExamType,
onSetTitle,
onSetDescription,
onSetIsPublic,
onRemoveFromBundle,
onCreateFromQuestions,
onCreateFromSections,
onOpenAutoBundle,
}: BundleSidebarProps) {
const activeBundle = mode === "soal" ? bundleQuestions : bundleSections;
const bundleCount = activeBundle.length;
return (
<div data-tour="bank-sidebar" className="lg:col-span-4">
<div className="sticky top-32 bg-[var(--pure-white)] rounded-[var(--radius-xl)] border-2 border-[var(--oat-border)] clay-shadow overflow-hidden flex flex-col max-h-[calc(100vh-8rem)]">
{/* Header */}
<div className="p-5 bg-[var(--clay-black)] text-[var(--pure-white)]">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-bold font-headline">Paket Saat Ini</h3>
<span className="text-xs font-medium bg-[var(--matcha-600)]/30 px-2 py-1 rounded">DRAFT</span>
</div>
<div className="flex items-center gap-2">
<p className="text-sm text-[var(--warm-silver)]">
{bundleCount} {mode === "soal" ? "soal" : "section"} dipilih
</p>
{lockedExamType && (
<span className="text-[10px] font-medium bg-[var(--matcha-600)]/40 text-[var(--matcha-300)] px-2 py-0.5 rounded-full flex items-center gap-1">
<MaterialIcon name="lock" className="text-[10px]" />
{lockedExamType}
</span>
)}
</div>
</div>
{/* Items List */}
<div className="flex-1 overflow-y-auto p-4 space-y-3 min-h-[120px]">
{bundleCount === 0 ? (
<div className="text-center py-8 text-sm text-[var(--warm-silver)]">
<MaterialIcon name="inventory_2" className="text-3xl mx-auto mb-2" />
Belum ada yang dipilih
</div>
) : (
activeBundle.map((item, idx: number) => {
const isQ = mode === "soal";
return (
<div
key={isQ ? item.id : `${item.id}-${idx}`}
className="p-3 bg-[var(--oat-light)] rounded-[var(--radius-lg)] flex items-center gap-3 group"
>
<div className="h-8 w-8 bg-[var(--pure-white)] rounded-lg flex items-center justify-center text-[var(--matcha-600)] shrink-0">
<MaterialIcon name={isQ ? "quiz" : "folder_open"} className="text-sm" />
</div>
<div className="flex-1 min-w-0">
<h4 className="text-sm font-bold text-[var(--clay-black)] truncate">
{isQ ? item.questionText : item.title}
</h4>
<p className="text-xs text-[var(--warm-charcoal)]">
{isQ
? `${item.examTypeName} \u00B7 ${formatLabel(item.format ?? "")}`
: `${item.examTypeName} \u00B7 ${item.sectionTypeName}`}
</p>
</div>
<button
onClick={() => onRemoveFromBundle(item.id, isQ ? "question" : "section")}
aria-label={`Hapus ${isQ ? "soal" : "section"} dari bundle`}
className="text-[var(--warm-silver)] hover:text-[var(--pomegranate-400)] opacity-0 group-hover:opacity-100 transition-opacity shrink-0 cursor-pointer"
>
<MaterialIcon name="delete" className="text-sm" />
</button>
</div>
);
})
)}
</div>
{/* Auto Bundle (only in soal mode) */}
{mode === "soal" && (
<div className="p-4 border-t border-[var(--oat-border)] bg-[var(--oat-light)]">
<div className="bg-[var(--pure-white)] rounded-[var(--radius-lg)] p-4 border-2 border-dashed border-[var(--oat-border)] flex items-center gap-3">
<div className="bg-[var(--matcha-300)]/20 p-2 rounded-full shrink-0">
<MaterialIcon name="auto_awesome" className="text-lg text-[var(--matcha-600)]" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-bold text-[var(--clay-black)]">Auto Bundle</p>
<p className="text-xs text-[var(--warm-charcoal)]">AI pilihkan soal otomatis</p>
</div>
<Button
size="sm"
onClick={onOpenAutoBundle}
disabled={!autoBundleExamType}
className="bg-[var(--matcha-600)] text-[var(--pure-white)] hover:bg-[var(--matcha-800)] rounded-[var(--radius-lg)] text-xs px-3 py-1.5 h-auto shrink-0 cursor-pointer"
>
<MaterialIcon name="auto_fix_high" className="mr-1 text-xs" />
Buat
</Button>
</div>
</div>
)}
{/* Form & Actions */}
<div className="p-5 border-t border-[var(--oat-border)] bg-[var(--oat-light)]">
<div className="space-y-4 mb-5">
<div>
<label className="block text-xs font-bold text-[var(--warm-charcoal)] mb-1.5 uppercase">
Judul Paket
</label>
<Input
value={bundleTitle}
onChange={(e) => onSetTitle(e.target.value)}
placeholder="Judul paket..."
className="w-full bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[var(--radius-lg)] text-sm"
/>
</div>
<div>
<label className="block text-xs font-bold text-[var(--warm-charcoal)] mb-1.5 uppercase">
Deskripsi
</label>
<Input
value={bundleDescription}
onChange={(e) => onSetDescription(e.target.value)}
placeholder="Deskripsi singkat..."
className="w-full bg-[var(--pure-white)] border-2 border-[var(--oat-border)] rounded-[var(--radius-lg)] text-sm"
/>
</div>
<div>
<label className="block text-xs font-bold text-[var(--warm-charcoal)] mb-1.5 uppercase">
Visibilitas
</label>
<div className="flex gap-2">
<VisButton active={bundleIsPublic} onClick={() => onSetIsPublic(true)}>
Publik
</VisButton>
<VisButton active={!bundleIsPublic} onClick={() => onSetIsPublic(false)}>
Privat
</VisButton>
</div>
</div>
</div>
<Button
onClick={mode === "soal" ? onCreateFromQuestions : onCreateFromSections}
disabled={!bundleTitle || bundleCount === 0 || isCreating}
className="w-full py-3 rounded-[var(--radius-lg)] bg-[var(--clay-black)] text-[var(--pure-white)] font-bold text-sm clay-shadow clay-hover hover:bg-[var(--warm-charcoal)] h-auto cursor-pointer"
>
<MaterialIcon name={mode === "soal" ? "folder" : "construction"} />
<span className="ml-2">
{isCreating
? "Membuat..."
: mode === "soal"
? "Buat Paket"
: "Buat Combo Paket"}
</span>
</Button>
</div>
</div>
</div>
);
}
function VisButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {
return (
<button
onClick={onClick}
className={`flex-1 text-xs font-bold py-2 rounded-[var(--radius-lg)] transition-colors cursor-pointer ${
active
? "bg-[var(--matcha-600)] text-[var(--pure-white)]"
: "bg-[var(--pure-white)] text-[var(--warm-charcoal)] border-2 border-[var(--oat-border)]"
}`}
>
{children}
</button>
);
}