'use client' import { useState, useEffect } from 'react' import dynamic from 'next/dynamic' import { useRouter } from 'next/navigation' import { useTranslations } from 'next-intl' import { createClient } from '@/lib/supabase/client' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog' import { EmptyState } from '@/components/ui/empty-state' import { Skeleton } from '@/components/ui/skeleton' import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table' import { ReportExportMenu } from '@/components/reports/ReportExportMenu' import { useToast } from '@/components/ui/use-toast' import { Plus, Search, Building2, Lock } from 'lucide-react' import Link from 'next/link' import { cn } from '@/lib/utils' import { useCompany } from '@/contexts/CompanyContext' import { useCanWrite } from '@/lib/hooks/use-can-write' import type { Supplier, SupplierType, CreateSupplierInput } from '@/types' const SupplierForm = dynamic( () => import('@/components/suppliers/SupplierForm'), { loading: () => (
), }, ) const SUPPLIER_TYPE_KEYS: Record = { swedish_business: 'type_swedish_business', eu_business: 'type_eu_business', non_eu_business: 'type_non_eu_business', } const INITIAL_VISIBLE_ROWS = 100 // "Betalsätt" cell (concept scene 26): the supplier's primary payment route, // e.g. "BG 5050-1055". First match wins, mirroring the detail page's order. function getPaymentInfo(supplier: Supplier, t: (key: string) => string): { label: string; value: string } | null { if (supplier.bankgiro) return { label: t('label_bg'), value: supplier.bankgiro } if (supplier.plusgiro) return { label: t('label_pg'), value: supplier.plusgiro } if (supplier.iban) return { label: t('label_iban'), value: supplier.iban } if (supplier.bank_account) return { label: t('label_bank_account'), value: supplier.bank_account } return null } export default function SuppliersPage() { const { company } = useCompany() const { canWrite } = useCanWrite() const t = useTranslations('suppliers') const [suppliers, setSuppliers] = useState([]) const [isLoading, setIsLoading] = useState(true) const [searchTerm, setSearchTerm] = useState('') const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_ROWS) const [isDialogOpen, setIsDialogOpen] = useState(false) const [isCreating, setIsCreating] = useState(false) const { toast } = useToast() const supabase = createClient() const tCommon = useTranslations('common') const router = useRouter() async function fetchSuppliers() { if (!company) return setIsLoading(true) const { data, error } = await supabase .from('suppliers') .select('*') .eq('company_id', company.id) .order('name', { ascending: true }) if (error) { toast({ title: t('load_failed_title'), description: t('load_failed_description'), variant: 'destructive', }) } else { setSuppliers(data || []) } setIsLoading(false) } useEffect(() => { fetchSuppliers() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) async function handleCreateSupplier(data: CreateSupplierInput) { setIsCreating(true) const response = await fetch('/api/suppliers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) const result = await response.json() if (!response.ok) { const fieldErrors = result.errors?.map((e: { field: string; message: string }) => `${e.field}: ${e.message}`).join(', ') toast({ title: t('create_failed_title'), description: fieldErrors || result.error || t('create_failed_retry'), variant: 'destructive', }) } else { toast({ title: t('created_title'), description: t('created_description', { name: data.name }), }) setSuppliers([...suppliers, result.data]) setIsDialogOpen(false) } setIsCreating(false) } const filteredSuppliers = suppliers.filter((s) => s.name.toLowerCase().includes(searchTerm.toLowerCase()) || s.email?.toLowerCase().includes(searchTerm.toLowerCase()) || s.org_number?.includes(searchTerm) ) const visibleSuppliers = filteredSuppliers.slice(0, visibleCount) return (
{/* Page header (concept scene 26): title + export + Ny leverantör */}

{t('title')}

{t('add_supplier')}
{/* Toolbar: search (concept) */}
{ setSearchTerm(e.target.value) setVisibleCount(INITIAL_VISIBLE_ROWS) }} className="h-9 pl-10" />
{isLoading ? (
{[1, 2, 3, 4].map((i) => ( ))}
) : filteredSuppliers.length === 0 ? ( searchTerm ? ( ) : ( setIsDialogOpen(true) : undefined} /> ) ) : ( <>
{visibleSuppliers.map((supplier) => { const payment = getPaymentInfo(supplier, t) return ( router.push(`/suppliers/${supplier.id}`)} > ) })}
{t('th_name')} {t('th_type')} {t('th_payment')} {t('th_email')} {t('th_org_number')}
e.stopPropagation()} > {supplier.name} {t(SUPPLIER_TYPE_KEYS[supplier.supplier_type])} {payment ? `${payment.label} ${payment.value}` : ''} {supplier.email || ''} {supplier.org_number || ''}
{/* Footer note (concept pgnote) */}

{t('count_summary', { count: suppliers.length })}

{visibleCount < filteredSuppliers.length && (
)} )}
) }