Added CSV paste dialog UI

X-Lovable-Edit-ID: edt-2674aec4-f9b1-4a5d-94f6-67fac9221c93
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-19 15:49:14 +00:00
co-authored by renee-png
+65 -8
View File
@@ -8,7 +8,9 @@ import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Upload, CheckCircle2, AlertCircle, Loader2, FileSpreadsheet } from "lucide-react";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Textarea } from "@/components/ui/textarea";
import { Upload, CheckCircle2, AlertCircle, Loader2, FileSpreadsheet, ClipboardPaste } from "lucide-react";
import { toast } from "sonner";
import { CONTACT_TYPES } from "@/components/contacts/contact-form-dialog";
@@ -572,6 +574,8 @@ function ImportPage() {
const [clientList, setClientList] = useState<{ id: string; name: string }[]>([]);
const [defaultClientId, setDefaultClientId] = useState<string>("");
const [contactGroup, setContactGroup] = useState<string>("client");
const [pasteFor, setPasteFor] = useState<ImporterConfig | null>(null);
const [pasteText, setPasteText] = useState("");
const ctx = useMemo<ImportCtx>(() => ({
userId: user?.id ?? "",
clientByExt: new Map(),
@@ -676,7 +680,7 @@ function ImportPage() {
console.info(`[import] lookups loaded: clients=${ctx.clientByExt.size}/${ctx.clientByName.size} cases=${ctx.caseByExt.size}/${ctx.caseByNumber.size}/${ctx.caseByTitle.size} users=${ctx.userByName.size}`);
};
const handleFile = async (cfg: ImporterConfig, file: File) => {
const handleFile = async (cfg: ImporterConfig, source: File | string) => {
if (!user?.id) return;
setResults((r) => ({ ...r, [cfg.key]: "running" }));
ctx.defaultClientId = cfg.key === "locations" ? (defaultClientId || null) : null;
@@ -684,12 +688,20 @@ function ImportPage() {
await ensureLookupsLoaded();
const parsed = await new Promise<Papa.ParseResult<Record<string, string>>>((resolve, reject) => {
Papa.parse<Record<string, string>>(file, {
header: true,
skipEmptyLines: true,
complete: resolve,
error: reject,
});
if (typeof source === "string") {
const result = Papa.parse<Record<string, string>>(source, {
header: true,
skipEmptyLines: true,
});
resolve(result);
} else {
Papa.parse<Record<string, string>>(source, {
header: true,
skipEmptyLines: true,
complete: resolve,
error: reject,
});
}
});
const rawRows = parsed.data;
@@ -1042,6 +1054,18 @@ function ImportPage() {
{running ? "Importing…" : "Upload CSV"}
</label>
</Button>
<Button
size="sm"
variant="outline"
disabled={running || (cfg.key === "locations" && !defaultClientId)}
onClick={() => {
setPasteText("");
setPasteFor(cfg);
}}
>
<ClipboardPaste className="h-3.5 w-3.5 mr-1.5" />
Paste
</Button>
</div>
</div>
</CardHeader>
@@ -1081,6 +1105,39 @@ function ImportPage() {
);
})}
</div>
<Dialog open={!!pasteFor} onOpenChange={(o) => !o && setPasteFor(null)}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Paste CSV — {pasteFor?.label}</DialogTitle>
<DialogDescription>
Paste CSV (or tab-separated) data including the header row. Same column rules as file upload apply.
</DialogDescription>
</DialogHeader>
<Textarea
value={pasteText}
onChange={(e) => setPasteText(e.target.value)}
placeholder={"name,email,phone\nJane Doe,jane@example.com,555-1234"}
className="min-h-[260px] font-mono text-xs"
autoFocus
/>
<DialogFooter>
<Button variant="outline" onClick={() => setPasteFor(null)}>Cancel</Button>
<Button
disabled={!pasteText.trim()}
onClick={() => {
const cfg = pasteFor;
const text = pasteText;
setPasteFor(null);
setPasteText("");
if (cfg && text.trim()) handleFile(cfg, text);
}}
>
Import
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}