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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user