From 042e0820251ce2ca15ac285fb5b24ddde5f15185 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Fri, 17 Jul 2026 15:23:47 +0000 Subject: [PATCH] fix(tags): preserve leading zeros in bulk tag CSV uploads and wire up drag-and-drop landlord_property_id values like "07510027001" were being read via SheetJS without raw:false, so CSV parsing coerced numeric-looking cells to numbers and dropped leading zeros. Also wires up drag-and-drop on the bulk tag upload dropzone, which was styled as one but only supported click-to-browse. Co-Authored-By: Claude Sonnet 5 --- .../[slug]/components/BulkTagUploadModal.tsx | 50 ++++++++++++++----- src/lib/tags/bulkAssign.test.ts | 25 +++++++++- src/lib/tags/bulkAssign.ts | 18 +++++++ 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/app/portfolio/[slug]/components/BulkTagUploadModal.tsx b/src/app/portfolio/[slug]/components/BulkTagUploadModal.tsx index 21e4ffed..0e457470 100644 --- a/src/app/portfolio/[slug]/components/BulkTagUploadModal.tsx +++ b/src/app/portfolio/[slug]/components/BulkTagUploadModal.tsx @@ -1,8 +1,7 @@ "use client"; -import { useState } from "react"; +import { useRef, useState, DragEvent } from "react"; import { useMutation } from "@tanstack/react-query"; -import * as XLSX from "xlsx"; import { Dialog, DialogContent, @@ -12,7 +11,11 @@ import { DialogFooter, } from "@/app/shadcn_components/ui/dialog"; import { Button } from "@/app/shadcn_components/ui/button"; -import { parseTagIdentifierRows, IdentifierKey } from "@/lib/tags/bulkAssign"; +import { + parseTagIdentifierRows, + readIdentifierRows, + IdentifierKey, +} from "@/lib/tags/bulkAssign"; import { Tag, useInvalidateTagSurfaces, usePortfolioTags } from "./useTags"; import { TagChip } from "./TagChip"; @@ -69,6 +72,8 @@ export function BulkTagUploadModal({ const { data: tags = [] } = usePortfolioTags(portfolioId); const invalidate = useInvalidateTagSurfaces(portfolioId); + const fileInputRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); const [fileName, setFileName] = useState(null); const [parsed, setParsed] = useState(null); const [parseError, setParseError] = useState(null); @@ -76,6 +81,7 @@ export function BulkTagUploadModal({ const [summary, setSummary] = useState(null); function reset() { + setIsDragging(false); setFileName(null); setParsed(null); setParseError(null); @@ -91,13 +97,7 @@ export function BulkTagUploadModal({ setFileName(file.name); try { const buf = await file.arrayBuffer(); - const wb = XLSX.read(buf, { type: "array" }); - const sheet = wb.Sheets[wb.SheetNames[0]]; - const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 }) as ( - | string - | number - | null - )[][]; + const rows = readIdentifierRows(buf); const result = parseTagIdentifierRows(rows); if (!result.ok) { setParseError(result.error); @@ -109,6 +109,22 @@ export function BulkTagUploadModal({ } } + function handleDragOver(e: DragEvent) { + e.preventDefault(); + setIsDragging(true); + } + + function handleDragLeave() { + setIsDragging(false); + } + + function handleDrop(e: DragEvent) { + e.preventDefault(); + setIsDragging(false); + const f = e.dataTransfer.files?.[0]; + if (f) onFile(f); + } + const assign = useMutation({ mutationFn: async () => { if (!parsed || !targetTag) throw new Error("Pick a file and a tag first"); @@ -198,14 +214,24 @@ export function BulkTagUploadModal({ ) : (
-