espace-paie-odentas/app/(app)/vos-documents/page.tsx
2025-10-12 17:05:46 +02:00

449 lines
15 KiB
TypeScript

'use client'
import React from 'react'
import { useQuery } from '@tanstack/react-query'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Download, FileText, Upload, Folder, Building2, ChevronDown, ChevronRight } from 'lucide-react'
import { Textarea } from '@/components/ui/textarea'
import { usePageTitle } from '@/hooks/usePageTitle'
import { Tooltip } from '@/components/ui/tooltip'
type DocumentItem = {
id: string
title: string
url?: string
updatedAt?: string
sizeBytes?: number
meta?: Record<string, any>
period_label?: string | null
}
function formatBytes(bytes?: number) {
if (!bytes && bytes !== 0) return ''
const sizes = ['o', 'Ko', 'Mo', 'Go', 'To']
const i = Math.floor(Math.log(bytes) / Math.log(1024))
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`
}
function formatDateLast(dateStr?: string) {
if (!dateStr) return ''
const date = new Date(dateStr)
return date.toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
})
}
function UploadPanel() {
const [description, setDescription] = React.useState('')
const handleSubmit = () => {
alert('Fonctionnalité de transmission de document en cours de développement')
}
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="h-4 w-4" />
Transmettre un document
</CardTitle>
<CardDescription>
Envoyez-nous directement vos documents
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Input type="file" />
<Textarea
placeholder="Description du document (optionnel)"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<Button onClick={handleSubmit} className="w-full">
Envoyer
</Button>
</CardContent>
</Card>
)
}
function SectionGeneraux() {
const { data: documentsGeneraux, isLoading, error } = useQuery<DocumentItem[]>({
queryKey: ['documents', 'generaux'],
queryFn: async () => {
const res = await fetch('/api/documents?category=generaux')
const data = await res.json()
console.log('📄 Documents Généraux - Response:', data)
console.log('📄 Documents Généraux - Is Array:', Array.isArray(data))
// Si la réponse est un objet avec une propriété documents
if (data && typeof data === 'object' && !Array.isArray(data)) {
console.log('📄 Documents Généraux - Keys:', Object.keys(data))
// Essayer différentes propriétés possibles
if (data.documents) return data.documents
if (data.data) return data.data
if (data.items) return data.items
}
return Array.isArray(data) ? data : []
}
})
console.log('📄 Documents Généraux - Final Data:', documentsGeneraux)
console.log('📄 Documents Généraux - Loading:', isLoading)
console.log('📄 Documents Généraux - Error:', error)
const handleDownload = (item: DocumentItem) => {
if (item.url) {
window.open(item.url, '_blank')
} else {
alert('Document non disponible')
}
}
if (isLoading) {
return <p className="text-center text-muted-foreground py-8">Chargement...</p>
}
if (error) {
return <p className="text-center text-red-500 py-8">Erreur: {String(error)}</p>
}
return (
<div className="space-y-2">
{documentsGeneraux && documentsGeneraux.length > 0 ? (
documentsGeneraux.map((item) => (
<div key={item.id} className="flex items-center justify-between p-3 border rounded-lg">
<div>
<h4 className="font-medium">{item.title}</h4>
<p className="text-sm text-muted-foreground">
{formatDateLast(item.updatedAt)} {formatBytes(item.sizeBytes)}
</p>
</div>
<Button
size="sm"
onClick={() => handleDownload(item)}
disabled={!item.url}
>
<Download className="h-4 w-4 mr-2" />
Télécharger
</Button>
</div>
))
) : (
<p className="text-center text-muted-foreground py-8">
Aucun document disponible
</p>
)}
</div>
)
}
function SectionCaisses() {
const { data: documentsOrganismes, isLoading, error } = useQuery<DocumentItem[]>({
queryKey: ['documents', 'caisses'],
queryFn: async () => {
const res = await fetch('/api/documents?category=caisses')
const data = await res.json()
console.log('📄 Documents Caisses - Response:', data)
if (data && typeof data === 'object' && !Array.isArray(data)) {
if (data.documents) return data.documents
if (data.data) return data.data
if (data.items) return data.items
}
return Array.isArray(data) ? data : []
}
})
const handleDownload = (item: DocumentItem) => {
if (item.url) {
window.open(item.url, '_blank')
} else {
alert('Document non disponible')
}
}
if (isLoading) {
return <p className="text-center text-muted-foreground py-8">Chargement...</p>
}
if (error) {
return <p className="text-center text-red-500 py-8">Erreur: {String(error)}</p>
}
return (
<div className="space-y-2">
{documentsOrganismes && documentsOrganismes.length > 0 ? (
documentsOrganismes.map((item) => (
<div key={item.id} className="flex items-center justify-between p-3 border rounded-lg">
<div>
<h4 className="font-medium">{item.title}</h4>
<p className="text-sm text-muted-foreground">
{formatDateLast(item.updatedAt)} {formatBytes(item.sizeBytes)}
</p>
</div>
<Button
size="sm"
onClick={() => handleDownload(item)}
disabled={!item.url}
>
<Download className="h-4 w-4 mr-2" />
Télécharger
</Button>
</div>
))
) : (
<p className="text-center text-muted-foreground py-8">
Aucun document disponible
</p>
)}
</div>
)
}
function SectionComptables() {
const [expandedPeriods, setExpandedPeriods] = React.useState<Set<string>>(new Set())
const { data: documentsCompta, isLoading, error } = useQuery<DocumentItem[]>({
queryKey: ['documents', 'comptables'],
queryFn: async () => {
console.log('📄 Fetching comptables with category=docs_comptables')
const res = await fetch('/api/documents?category=docs_comptables')
const data = await res.json()
console.log('📄 Documents Comptables - Raw Response:', data)
console.log('📄 Documents Comptables - Is Array?', Array.isArray(data))
if (data && typeof data === 'object' && !Array.isArray(data)) {
console.log('📄 Documents Comptables - Object keys:', Object.keys(data))
if (data.documents) {
console.log('📄 Using data.documents:', data.documents)
return data.documents
}
if (data.data) {
console.log('📄 Using data.data:', data.data)
return data.data
}
if (data.items) {
console.log('📄 Using data.items:', data.items)
return data.items
}
}
const result = Array.isArray(data) ? data : []
console.log('📄 Final result:', result)
return result
}
})
// Grouper les documents par période
const documentsByPeriod = React.useMemo(() => {
if (!documentsCompta || documentsCompta.length === 0) return new Map()
const grouped = new Map<string, DocumentItem[]>()
documentsCompta.forEach(doc => {
const period = doc.period_label || 'Sans période'
if (!grouped.has(period)) {
grouped.set(period, [])
}
grouped.get(period)!.push(doc)
})
// Trier les périodes par ordre décroissant (plus récent en premier)
const sortedEntries = Array.from(grouped.entries()).sort((a, b) => {
// Si "Sans période", mettre à la fin
if (a[0] === 'Sans période') return 1
if (b[0] === 'Sans période') return -1
// Sinon, tri décroissant (plus récent en premier)
return b[0].localeCompare(a[0])
})
return new Map(sortedEntries)
}, [documentsCompta])
const togglePeriod = (period: string) => {
setExpandedPeriods(prev => {
const next = new Set(prev)
if (next.has(period)) {
next.delete(period)
} else {
next.add(period)
}
return next
})
}
const handleDownload = (item: DocumentItem) => {
if (item.url) {
window.open(item.url, '_blank')
} else {
alert('Document non disponible')
}
}
if (isLoading) {
return <p className="text-center text-muted-foreground py-8">Chargement...</p>
}
if (error) {
return <p className="text-center text-red-500 py-8">Erreur: {String(error)}</p>
}
if (!documentsCompta || documentsCompta.length === 0) {
return (
<p className="text-center text-muted-foreground py-8">
Aucun document comptable disponible
</p>
)
}
return (
<div className="space-y-3">
{Array.from(documentsByPeriod.entries()).map(([period, docs]) => {
const isExpanded = expandedPeriods.has(period)
return (
<div key={period} className="border rounded-lg overflow-hidden">
{/* Header de la période - cliquable */}
<button
onClick={() => togglePeriod(period)}
className="w-full flex items-center justify-between p-4 bg-muted/30 hover:bg-muted/50 transition-colors"
>
<div className="flex items-center gap-3">
{isExpanded ? (
<ChevronDown className="h-5 w-5 text-muted-foreground" />
) : (
<ChevronRight className="h-5 w-5 text-muted-foreground" />
)}
<div className="text-left">
<h3 className="font-semibold text-base">{period}</h3>
<p className="text-sm text-muted-foreground">
{docs.length} document{docs.length > 1 ? 's' : ''}
</p>
</div>
</div>
</button>
{/* Liste des documents - affichée si expanded */}
{isExpanded && (
<div className="p-2 space-y-2 bg-background">
{docs.map((item: DocumentItem) => (
<div
key={item.id}
className="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/30 transition-colors"
>
<div className="flex-1 min-w-0">
<h4 className="font-medium truncate">{item.title}</h4>
<p className="text-sm text-muted-foreground">
{formatDateLast(item.updatedAt)} {formatBytes(item.sizeBytes)}
</p>
</div>
<Button
size="sm"
onClick={() => handleDownload(item)}
disabled={!item.url}
className="ml-4"
>
<Download className="h-4 w-4 mr-2" />
Télécharger
</Button>
</div>
))}
</div>
)}
</div>
)
})}
</div>
)
}
export default function VosDocumentsPage() {
usePageTitle("Vos documents");
const [activeTab, setActiveTab] = React.useState('generaux');
return (
<div className="space-y-6">
<header className="flex items-center justify-between">
<h2 className="text-2xl font-semibold tracking-tight">Vos documents</h2>
</header>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Colonne gauche : Documents disponibles */}
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle>Documents disponibles</CardTitle>
<CardDescription>
{activeTab === 'generaux' && 'Téléchargez vos documents généraux'}
{activeTab === 'comptables' && 'Téléchargez vos documents comptables et sociaux'}
</CardDescription>
</CardHeader>
<CardContent>
{activeTab === 'generaux' && <SectionGeneraux />}
{activeTab === 'comptables' && <SectionComptables />}
</CardContent>
</Card>
</div>
{/* Colonne droite : Onglets + Transmettre un document */}
<div className="lg:col-span-1 space-y-4">
<Card>
<CardHeader>
<CardTitle className="text-base">Catégories</CardTitle>
<CardDescription>Sélectionnez une catégorie</CardDescription>
</CardHeader>
<CardContent className="space-y-2">
<Button
variant={activeTab === 'generaux' ? 'default' : 'outline'}
className="w-full justify-start"
onClick={() => setActiveTab('generaux')}
>
<FileText className="h-4 w-4 mr-2" />
Documents généraux
</Button>
<Tooltip
content="Les documents des caisses seront de nouveau disponibles dans quelques jours"
side="left"
>
<Button
variant="outline"
className="w-full justify-start opacity-50 cursor-not-allowed"
disabled
>
<Building2 className="h-4 w-4 mr-2" />
Caisses & organismes
</Button>
</Tooltip>
<Button
variant={activeTab === 'comptables' ? 'default' : 'outline'}
className="w-full justify-start"
onClick={() => setActiveTab('comptables')}
>
<Folder className="h-4 w-4 mr-2" />
Documents comptables
</Button>
</CardContent>
</Card>
<UploadPanel />
<Card>
<CardHeader>
<CardTitle className="text-base">Besoin d'aide ?</CardTitle>
<CardDescription>
N'hésitez pas à nous contacter si vous avez besoin d'une attestation spécifique.
</CardDescription>
</CardHeader>
</Card>
</div>
</div>
</div>
)
}