feat: Ajouter possibilité de renommer les périodes dans documents comptables (staff)

This commit is contained in:
odentas 2025-12-10 16:16:32 +01:00
parent 39fea18d9e
commit c0142d167e
2 changed files with 178 additions and 20 deletions

View file

@ -415,6 +415,8 @@ export default function StaffDocumentsPage() {
const [expandedPeriods, setExpandedPeriods] = React.useState<Set<string>>(new Set())
const [viewDocument, setViewDocument] = React.useState<{ name: string; downloadUrl: string; size?: number } | null>(null)
const [deleteConfirm, setDeleteConfirm] = React.useState<{ type: 'general' | 'comptable'; data: any } | null>(null)
const [editingPeriod, setEditingPeriod] = React.useState<string | null>(null)
const [newPeriodName, setNewPeriodName] = React.useState('')
const queryClient = useQueryClient()
@ -515,6 +517,49 @@ export default function StaffDocumentsPage() {
onError: () => toast.error('Erreur lors de la suppression')
})
// Renommer une période
const renamePeriod = useMutation({
mutationFn: async ({ oldName, newName }: { oldName: string; newName: string }) => {
const response = await fetch('/api/staff/documents/update-period', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
org_id: selectedOrgId,
old_period_label: oldName,
new_period_label: newName
})
})
if (!response.ok) throw new Error('Erreur mise à jour')
return response.json()
},
onSuccess: (data) => {
toast.success(`${data.updated_count} document(s) mis à jour`)
queryClient.invalidateQueries({ queryKey: ['documents', 'comptables'] })
setEditingPeriod(null)
setNewPeriodName('')
},
onError: () => toast.error('Erreur lors de la mise à jour')
})
const handleStartEditPeriod = (period: string) => {
setEditingPeriod(period)
setNewPeriodName(period)
}
const handleSavePeriod = (oldName: string) => {
if (newPeriodName.trim() && newPeriodName !== oldName) {
renamePeriod.mutate({ oldName, newName: newPeriodName.trim() })
} else {
setEditingPeriod(null)
setNewPeriodName('')
}
}
const handleCancelEditPeriod = () => {
setEditingPeriod(null)
setNewPeriodName('')
}
return (
<div className="space-y-6">
<header>
@ -739,31 +784,78 @@ export default function StaffDocumentsPage() {
<div className="space-y-2">
{Array.from(documentsByPeriod.entries()).map(([period, docs]) => {
const isExpanded = expandedPeriods.has(period)
const isEditing = editingPeriod === period
return (
<Card key={period}>
<button
onClick={() => {
const next = new Set(expandedPeriods)
if (next.has(period)) next.delete(period)
else next.add(period)
setExpandedPeriods(next)
}}
className="w-full p-4 flex items-center justify-between hover:bg-gray-50 transition-colors"
>
<div className="flex items-center gap-3">
<div className="p-4 flex items-center justify-between">
<button
onClick={() => {
if (!isEditing) {
const next = new Set(expandedPeriods)
if (next.has(period)) next.delete(period)
else next.add(period)
setExpandedPeriods(next)
}
}}
className="flex items-center gap-3 flex-1 hover:bg-gray-50 transition-colors rounded p-2 -m-2"
disabled={isEditing}
>
{isExpanded ? (
<ChevronDown className="h-4 w-4" />
<ChevronDown className="h-4 w-4 flex-shrink-0" />
) : (
<ChevronRight className="h-4 w-4" />
<ChevronRight className="h-4 w-4 flex-shrink-0" />
)}
<div className="text-left">
<h3 className="font-semibold">{period}</h3>
<p className="text-sm text-gray-500">
{docs.length} document{docs.length > 1 ? 's' : ''}
</p>
</div>
</div>
</button>
{isEditing ? (
<div className="flex items-center gap-2 flex-1" onClick={(e) => e.stopPropagation()}>
<Input
value={newPeriodName}
onChange={(e) => setNewPeriodName(e.target.value)}
className="max-w-sm"
autoFocus
onKeyDown={(e) => {
if (e.key === 'Enter') handleSavePeriod(period)
if (e.key === 'Escape') handleCancelEditPeriod()
}}
/>
<Button
size="sm"
onClick={() => handleSavePeriod(period)}
disabled={renamePeriod.isPending}
>
<Check className="h-3 w-3" />
</Button>
<Button
size="sm"
variant="outline"
onClick={handleCancelEditPeriod}
disabled={renamePeriod.isPending}
>
<X className="h-3 w-3" />
</Button>
</div>
) : (
<div className="text-left">
<h3 className="font-semibold">{period}</h3>
<p className="text-sm text-gray-500">
{docs.length} document{docs.length > 1 ? 's' : ''}
</p>
</div>
)}
</button>
{!isEditing && (
<Button
size="sm"
variant="ghost"
onClick={() => handleStartEditPeriod(period)}
className="ml-2"
>
<Edit3 className="h-3 w-3" />
</Button>
)}
</div>
{isExpanded && (
<div className="border-t p-4 space-y-2">

View file

@ -0,0 +1,66 @@
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'
export const dynamic = 'force-dynamic'
export async function PUT(request: Request) {
try {
const supabase = createRouteHandlerClient({ cookies })
// Vérifier l'authentification et les droits staff
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const { data: staffUser } = await supabase
.from('staff_users')
.select('is_staff')
.eq('user_id', session.user.id)
.maybeSingle()
if (!staffUser?.is_staff) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
}
const body = await request.json()
const { org_id, old_period_label, new_period_label } = body
if (!org_id || !old_period_label || !new_period_label) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
)
}
console.log('📝 Update Period - Org:', org_id, 'Old:', old_period_label, 'New:', new_period_label)
// Mettre à jour tous les documents de cette période
const { error, count } = await supabase
.from('documents')
.update({ period_label: new_period_label })
.eq('org_id', org_id)
.eq('period_label', old_period_label)
.eq('category', 'docs_comptables')
if (error) {
console.error('❌ Error updating period:', error)
return NextResponse.json({ error: error.message }, { status: 500 })
}
console.log('✅ Updated', count, 'documents')
return NextResponse.json({
success: true,
updated_count: count
})
} catch (error: any) {
console.error('❌ Update period error:', error)
return NextResponse.json(
{ error: error.message || 'Internal server error' },
{ status: 500 }
)
}
}