This commit is contained in:
null 2026-05-15 04:22:33 -05:00
parent 153ed7ab79
commit 74603ff2d5
3 changed files with 26 additions and 14 deletions

View File

@ -1356,7 +1356,7 @@ export function ImportSpreadsheetSection({ onHistoryRefresh }) {
// Collect created_at dates from duplicate detail entries so we can show // Collect created_at dates from duplicate detail entries so we can show
// when the existing payments were originally recorded. // when the existing payments were originally recorded.
const dupDates = (result.details ?? []) const dupDates = (result.details ?? [])
.filter(d => d.result === 'skipped_duplicate' && d.existing_created_at) .filter(d => (d.result === 'skipped_duplicate' || d.payment === 'skipped_duplicate') && d.existing_created_at)
.map(d => new Date(d.existing_created_at)) .map(d => new Date(d.existing_created_at))
.filter(d => !isNaN(d.getTime())) .filter(d => !isNaN(d.getTime()))
.sort((a, b) => a - b); .sort((a, b) => a - b);
@ -1522,6 +1522,9 @@ export function ImportSpreadsheetSection({ onHistoryRefresh }) {
setDecisions({}); setDecisions({});
setSelectedRows(new Set()); setSelectedRows(new Set());
setApplyState({ status: 'idle', result: null, error: null }); setApplyState({ status: 'idle', result: null, error: null });
setViewMode('rows');
setImportingBillId(null);
setBillImportResults(new Map());
if (fileRef.current) fileRef.current.value = ''; if (fileRef.current) fileRef.current.value = '';
}; };

View File

@ -283,16 +283,19 @@ function useSortable(items, setItems, setDirty) {
const onPointerDown = useCallback((e, index) => { const onPointerDown = useCallback((e, index) => {
// Only trigger on the grip handle (data-grip attr) // Only trigger on the grip handle (data-grip attr)
if (!e.currentTarget.hasAttribute('data-grip')) return; if (!e.target.closest('[data-grip]')) return;
// Ignore right-click // Ignore right-click
if (e.button !== undefined && e.button !== 0) return; if (e.button !== undefined && e.button !== 0) return;
e.currentTarget.setPointerCapture(e.pointerId); const card = e.target.closest('[data-card]');
const card = e.currentTarget.closest('[data-card]');
const list = card?.parentElement; const list = card?.parentElement;
const rect = card?.getBoundingClientRect(); const rect = card?.getBoundingClientRect();
// Capture on the container so pointermove/pointerup are dispatched
// directly to the element that owns those React handlers avoids
// relying on bubbling from the grip through React's delegation chain.
list?.setPointerCapture(e.pointerId);
state.current = { state.current = {
fromIdx: index, fromIdx: index,
currentIdx: index, currentIdx: index,
@ -593,12 +596,14 @@ export default function SnowballPage() {
data-card data-card
data-card-index={index} data-card-index={index}
className={cn( className={cn(
'surface-elevated rounded-xl border transition-all duration-150 select-none touch-none', 'surface-elevated rounded-xl border select-none touch-none',
// Only animate when not in a drag gesture instant feedback on grab
!isDragging && 'transition-all duration-150',
isAttack ? 'border-emerald-500/30 bg-emerald-950/5' : 'border-border/40', isAttack ? 'border-emerald-500/30 bg-emerald-950/5' : 'border-border/40',
// Card being actively dragged lifted look // Card being actively dragged lifted look
isDragSource && 'scale-[1.03] shadow-2xl ring-2 ring-primary/40 opacity-80 relative z-10', isDragSource && 'scale-105 shadow-2xl ring-2 ring-primary/60 opacity-75 relative z-10',
// Where the card will land slot highlight // Where the card will land slot highlight
isLandTarget && 'ring-2 ring-primary/60 scale-[0.98] opacity-60', isLandTarget && 'ring-2 ring-primary/40 scale-[0.97] opacity-50',
)} )}
> >
<div className="flex items-stretch"> <div className="flex items-stretch">

View File

@ -1401,18 +1401,18 @@ function createPaymentFromImport(db, billId, amount, paidDate, notes, allowOverw
if (!paidDate || amount == null || amount <= 0) return null; if (!paidDate || amount == null || amount <= 0) return null;
const dup = db.prepare(` const dup = db.prepare(`
SELECT id FROM payments SELECT id, created_at FROM payments
WHERE bill_id = ? AND paid_date = ? AND amount = ? AND deleted_at IS NULL WHERE bill_id = ? AND paid_date = ? AND amount = ? AND deleted_at IS NULL
`).get(billId, paidDate, amount); `).get(billId, paidDate, amount);
if (dup && !allowOverwrite) return 'skipped_duplicate'; if (dup && !allowOverwrite) return { result: 'skipped_duplicate', existing_created_at: dup.created_at ?? null };
db.prepare(` db.prepare(`
INSERT INTO payments (bill_id, amount, paid_date, method, notes) INSERT INTO payments (bill_id, amount, paid_date, method, notes)
VALUES (?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
`).run(billId, amount, paidDate, null, notes); `).run(billId, amount, paidDate, null, notes);
return 'created'; return { result: 'created', existing_created_at: null };
} }
function resolvePaymentInfo(decision, previewRow, amount) { function resolvePaymentInfo(decision, previewRow, amount) {
@ -1448,7 +1448,7 @@ function importRelatedPaidMonthsForNewBill(db, newBillId, billName, sourceRowId,
const paymentResult = createPaymentFromImport(db, newBillId, paymentAmount, paymentDate, notes, allowOverwrite); const paymentResult = createPaymentFromImport(db, newBillId, paymentAmount, paymentDate, notes, allowOverwrite);
if (paymentResult) { if (paymentResult) {
detail.payment = paymentResult; detail.payment = paymentResult.result;
detail.paid_date = paymentDate; detail.paid_date = paymentDate;
detail.payment_amount = paymentAmount; detail.payment_amount = paymentAmount;
} }
@ -1531,7 +1531,7 @@ function applyOneDecision(db, userId, decision, previewRow, sessionData, allowOv
allowOverwrite, allowOverwrite,
); );
if (paymentResult) { if (paymentResult) {
detail.payment = paymentResult; detail.payment = paymentResult.result;
detail.paid_date = paymentDate; detail.paid_date = paymentDate;
detail.payment_amount = paymentAmount; detail.payment_amount = paymentAmount;
} }
@ -1588,9 +1588,13 @@ function applyOneDecision(db, userId, decision, previewRow, sessionData, allowOv
allowOverwrite, allowOverwrite,
); );
if (paymentResult) { if (paymentResult) {
detail.payment = paymentResult; detail.payment = paymentResult.result;
detail.paid_date = paymentDate; detail.paid_date = paymentDate;
detail.payment_amount = paymentAmount; detail.payment_amount = paymentAmount;
if (paymentResult.result === 'skipped_duplicate') {
summary.duplicates++;
detail.existing_created_at = paymentResult.existing_created_at;
}
} }
} }
summary.details.push(detail); summary.details.push(detail);