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
// when the existing payments were originally recorded.
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))
.filter(d => !isNaN(d.getTime()))
.sort((a, b) => a - b);
@ -1522,6 +1522,9 @@ export function ImportSpreadsheetSection({ onHistoryRefresh }) {
setDecisions({});
setSelectedRows(new Set());
setApplyState({ status: 'idle', result: null, error: null });
setViewMode('rows');
setImportingBillId(null);
setBillImportResults(new Map());
if (fileRef.current) fileRef.current.value = '';
};

View File

@ -283,16 +283,19 @@ function useSortable(items, setItems, setDirty) {
const onPointerDown = useCallback((e, index) => {
// 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
if (e.button !== undefined && e.button !== 0) return;
e.currentTarget.setPointerCapture(e.pointerId);
const card = e.currentTarget.closest('[data-card]');
const card = e.target.closest('[data-card]');
const list = card?.parentElement;
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 = {
fromIdx: index,
currentIdx: index,
@ -593,12 +596,14 @@ export default function SnowballPage() {
data-card
data-card-index={index}
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',
// 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
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">

View File

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