All files / src/components/InvoiceCard InvoiceCard.tsx

100% Statements 32/32
100% Branches 29/29
100% Functions 4/4
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151                                  15x   4x   3x   3x   2x   3x                 10x 10x   10x 10x     10x 10x     10x 10x     10x     10x 10x 6x 2x 4x 2x   2x         10x 4x   1x     3x 1x 2x 1x   1x         10x                                                                                                                                        
import Box from '@mui/material/Box'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import Chip from '@mui/material/Chip'
import { useTranslation } from 'react-i18next'
import { formatDate, formatCurrency } from '../../utils/format'
import type { Invoice } from '../../types/invoices'
import { useLanguageStore } from '../../stores/useLanguageStore'
import ReceiptIcon from '@mui/icons-material/Receipt'
import { useDownloadInvoice } from '../../hooks/useDownloadInvoice'
import Tooltip from '@mui/material/Tooltip'
import CircularProgress from '@mui/material/CircularProgress'
import { useCustomSnackbar } from '../../hooks/useCustomSnackbar'
 
// Définir le mapping statut → couleur du Chip MUI
export function getStatusChipColor(status: Invoice['status']): 'success' | 'warning' | 'error' | 'default' | 'info' {
  switch (status) {
    case 'paid':
      return 'success'
    case 'pending':
      return 'warning'
    case 'failed':
      return 'error'
    case 'refunded':
      return 'info'
    default:
      return 'default'
  }
}
 
interface InvoiceCardProps {
  invoice: Invoice
}
 
export function InvoiceCard({ invoice }: InvoiceCardProps) {
  const { t } = useTranslation('invoices')
  const lang = useLanguageStore(s => s.lang)
 
  const { download, downloading } = useDownloadInvoice()
  const { notify } = useCustomSnackbar()
  
  // Formattage date et montant
  const dateStr = formatDate(invoice.created_at, lang)
  const amountStr = formatCurrency(invoice.amount, lang, 'EUR')
 
  // Texte pour le statut, on peut utiliser i18n ou fallback brut
  const statusLabel = t(`card.status.${invoice.status}`, invoice.status)
  const chipColor = getStatusChipColor(invoice.status)
 
  // Déterminer si on autorise le téléchargement
  const canDownload = invoice.status === 'paid' || invoice.status === 'refunded'
 
  // Tooltip explicatif
  let downloadTooltip = t('card.download_invoice')
  if (!canDownload) {
    if (invoice.status === 'pending') {
      downloadTooltip = t('card.download_not_ready')
    } else if (invoice.status === 'failed') {
      downloadTooltip = t('card.download_not_available')
    } else {
      downloadTooltip = t('card.download_not_available_generic')
    }
  }
 
  // Handler clic sur l’icône
  const handleIconClick = () => {
    if (canDownload) {
      // Lance le téléchargement ; useDownloadInvoice appellera notify pour succès/erreur
      download(invoice.invoice_link)
    } else {
      // Affiche un message via notify, apparaîtra en bas grâce au provider global
      if (invoice.status === 'pending') {
        notify(t('snackbar.pending_message'), 'warning')
      } else if (invoice.status === 'failed') {
        notify(t('snackbar.failed_message'), 'error')
      } else {
        notify(t('snackbar.unavailable_message'), 'info')
      }
    }
  }
 
  return (
    <>
      <Box
        sx={{
          flex: { xs: '1 1 calc(33% - 32px)', md: '1 1 100%' },
          minWidth: { xs: 280, md: 'auto' },
          maxWidth: { xs: 320, md: '100%' },
        }}
      >
        <Card
          sx={{
            display: 'flex',
            flexDirection: { xs: 'column', md: 'row' },
            alignItems: { xs: 'stretch', md: 'center' },
            p: 2,
            gap: 1,
          }}
        >
          {/* Zone icône / téléchargement */}
          <Tooltip title={downloadTooltip}>
            <Box
              onClick={handleIconClick}
              sx={{
                cursor: canDownload ? 'pointer' : 'default',
                width: { xs: '100%', md: 120 },
                height: 80,
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                bgcolor: 'background.default',
                borderRadius: 1,
                position: 'relative',
                '&:hover': canDownload ? { bgcolor: 'action.hover' } : {},
              }}
            >
              {downloading && canDownload
                ? <CircularProgress size={24} />
                : <ReceiptIcon fontSize="large" color={canDownload ? 'action' : 'disabled'} />
              }
            </Box>
          </Tooltip>
 
          <CardContent sx={{ flexGrow: 1 }}>
            {/* Référence / UUID */}
            <Typography variant="h6">
              {t('card.reference')}: {invoice.uuid}
            </Typography>
            {/* Date de création */}
            <Typography variant="body2" sx={{ mt: 0.5 }}>
              {t('card.date')}: {dateStr}
            </Typography>
            {/* Montant */}
            <Typography variant="body2" sx={{ mt: 0.5 }}>
              {t('card.amount')}: {amountStr}
            </Typography>
            {/* Statut avec Chip */}
            <Box sx={{ mt: 1 }}>
              <Chip
                label={statusLabel}
                color={chipColor}
                size="small"
              />
            </Box>
          </CardContent>
        </Card>
      </Box>
    </>
  )
}