'use client' import { useState, useCallback } from 'react' import { motion, useMotionValue, useTransform, AnimatePresence, type PanInfo } from 'framer-motion' import { Card, CardContent, CardHeader } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { formatCurrency, formatDate } from '@/lib/utils' import { checkExpenseWarnings } from '@/lib/tax/expense-warnings' import { X, ArrowLeft, ArrowRight, Building, AlertTriangle, Check, FileText, Link2, Receipt as ReceiptIcon, SkipForward } from 'lucide-react' import type { Transaction, TransactionCategory, Invoice, Customer } from '@/types' import type { SuggestedCategory } from '@/lib/transactions/category-suggestions' interface TransactionWithInvoice extends Transaction { potential_invoice?: Invoice & { customer?: Customer } } interface SwipeCategorizationViewProps { transactions: TransactionWithInvoice[] suggestions?: Record onCategorize: (id: string, isBusiness: boolean, category?: TransactionCategory) => Promise onMatchInvoice?: (transactionId: string, invoiceId: string) => Promise onClose: () => void } const expenseCategories: { value: TransactionCategory; label: string }[] = [ { value: 'expense_equipment', label: 'Utrustning' }, { value: 'expense_software', label: 'Programvara' }, { value: 'expense_travel', label: 'Resor' }, { value: 'expense_office', label: 'Kontor' }, { value: 'expense_marketing', label: 'Marknadsföring' }, { value: 'expense_professional_services', label: 'Konsulter' }, { value: 'expense_education', label: 'Utbildning' }, { value: 'expense_bank_fees', label: 'Bankavgift' }, { value: 'expense_card_fees', label: 'Kortavgift' }, { value: 'expense_currency_exchange', label: 'Valutaväxling' }, { value: 'expense_other', label: 'Övrigt' }, ] const incomeCategories: { value: TransactionCategory; label: string }[] = [ { value: 'income_services', label: 'Tjänster' }, { value: 'income_products', label: 'Produkter' }, { value: 'income_other', label: 'Övrigt' }, ] export default function SwipeCategorizationView({ transactions, suggestions, onCategorize, onMatchInvoice, onClose, }: SwipeCategorizationViewProps) { const [showAllCategories, setShowAllCategories] = useState(false) const [currentIndex, setCurrentIndex] = useState(0) const [showCategorySelect, setShowCategorySelect] = useState(false) const [isProcessing, setIsProcessing] = useState(false) const [error, setError] = useState(null) const currentTransaction = transactions[currentIndex] const warnings = currentTransaction ? checkExpenseWarnings(currentTransaction.description) : [] const x = useMotionValue(0) const rotate = useTransform(x, [-200, 0, 200], [-15, 0, 15]) const opacity = useTransform(x, [-200, -100, 0, 100, 200], [0.5, 1, 1, 1, 0.5]) const businessIndicatorOpacity = useTransform(x, [0, 100, 200], [0, 0.5, 1]) const skipIndicatorOpacity = useTransform(x, [-200, -100, 0], [1, 0.5, 0]) const moveToNext = useCallback(() => { x.set(0) if (currentIndex < transactions.length - 1) { setCurrentIndex(currentIndex + 1) } else { onClose() } }, [x, currentIndex, transactions.length, onClose]) const handleDrag = useCallback( (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { if (isProcessing) return x.set(info.offset.x) }, [isProcessing, x] ) const handleDragEnd = useCallback( async (_event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => { if (isProcessing || !currentTransaction) return const mx = info.offset.x const vx = Math.abs(info.velocity.x) const shouldSwipe = Math.abs(mx) > 100 || vx > 500 if (shouldSwipe) { if (mx > 0) { // Swipe right = categorize as business if (currentTransaction.amount < 0) { // Show category selector for business expenses setShowCategorySelect(true) x.set(0) } else { setIsProcessing(true) setError(null) try { const success = await onCategorize( currentTransaction.id, true, 'income_other' ) if (success) { moveToNext() } else { setError('Kunde inte kategorisera. Tryck "Hoppa över" för att gå vidare.') } } catch { setError('Ett fel uppstod. Tryck "Hoppa över" för att gå vidare.') } finally { setIsProcessing(false) } } } else { // Swipe left = skip moveToNext() } } else { x.set(0) } }, [isProcessing, currentTransaction, onCategorize, x, moveToNext] ) const handleCategorySelect = async (category: TransactionCategory) => { setIsProcessing(true) setError(null) try { const success = await onCategorize(currentTransaction.id, true, category) if (success) { setShowCategorySelect(false) moveToNext() } else { setError('Kunde inte kategorisera. Tryck "Hoppa över" för att gå vidare.') } } catch { setError('Ett fel uppstod. Tryck "Hoppa över" för att gå vidare.') } finally { setIsProcessing(false) } } const handleMatchInvoice = async () => { if (!currentTransaction.potential_invoice || !onMatchInvoice) return setIsProcessing(true) setError(null) try { const success = await onMatchInvoice( currentTransaction.id, currentTransaction.potential_invoice.id ) if (success) { moveToNext() } else { setError('Kunde inte matcha faktura. Tryck "Hoppa över" för att gå vidare.') } } catch { setError('Ett fel uppstod. Tryck "Hoppa över" för att gå vidare.') } finally { setIsProcessing(false) } } const handleSkip = useCallback(() => { setError(null) setShowCategorySelect(false) moveToNext() }, [moveToNext]) if (!currentTransaction) { return (

Klart!

Alla transaktioner är nu kategoriserade

) } if (showCategorySelect) { const categories = currentTransaction.amount > 0 ? incomeCategories : expenseCategories return (

Välj kategori

{currentTransaction.description}

{formatCurrency(Math.abs(currentTransaction.amount), currentTransaction.currency)}

{error && (
{error}
)}
{categories.map((cat) => ( ))}
) } return (
{/* Header */}

{currentIndex + 1} av {transactions.length}

{/* Instructions */}
Hoppa över
Kategorisera
{/* Card stack */}
{/* Swipe indicators */} Hoppa över Företag
{formatDate(currentTransaction.date)}
{currentTransaction.receipt_id && ( Kvitto )} {currentTransaction.currency}

{currentTransaction.description}

0 ? 'text-success' : '' }`} > {currentTransaction.amount > 0 ? '+' : ''} {formatCurrency(currentTransaction.amount, currentTransaction.currency)}

{/* Potential Invoice Match */} {currentTransaction.potential_invoice && (
Fakturamatchning hittad
Match

Faktura {currentTransaction.potential_invoice.invoice_number}

{currentTransaction.potential_invoice.customer?.name || 'Okänd kund'}

{formatCurrency( currentTransaction.potential_invoice.total, currentTransaction.potential_invoice.currency )}

)} {/* Warnings */} {warnings.length > 0 && (
{warnings.map((warning, idx) => (

{warning.category}

{warning.message}

))}
)}
{/* Action buttons */}
{/* Error message */} {error && (
{error}
)} {/* Invoice match button - primary action when there's a match */} {currentTransaction.potential_invoice && onMatchInvoice && ( )} {/* Suggested categories - shown as quick-select buttons */} {suggestions && suggestions[currentTransaction.id] && suggestions[currentTransaction.id].length > 0 && (

Föreslagna kategorier

{suggestions[currentTransaction.id].map((suggestion) => ( ))}
)} {/* Categorization button */} {/* Skip button - always visible */}
) }