diff --git a/apps/backend/src/application/controllers/admin/csv-rates.controller.ts b/apps/backend/src/application/controllers/admin/csv-rates.controller.ts index dc2c069..67bda15 100644 --- a/apps/backend/src/application/controllers/admin/csv-rates.controller.ts +++ b/apps/backend/src/application/controllers/admin/csv-rates.controller.ts @@ -348,4 +348,137 @@ export class CsvRatesAdminController { this.logger.log(`Deleted CSV config for company: ${companyName}`); } + + /** + * List all CSV files (Frontend compatibility endpoint) + * Maps to GET /files for compatibility with frontend API client + */ + @Get('files') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'List all CSV files (ADMIN only)', + description: 'Returns list of all uploaded CSV files with metadata. Alias for /config endpoint.', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'List of CSV files', + schema: { + type: 'object', + properties: { + files: { + type: 'array', + items: { + type: 'object', + properties: { + filename: { type: 'string', example: 'ssc-consolidation.csv' }, + size: { type: 'number', example: 2048 }, + uploadedAt: { type: 'string', format: 'date-time' }, + rowCount: { type: 'number', example: 150 }, + }, + }, + }, + }, + }, + }) + async listFiles(): Promise<{ files: any[] }> { + this.logger.log('Fetching all CSV files (frontend compatibility)'); + + const configs = await this.csvConfigRepository.findAll(); + const fs = require('fs'); + const path = require('path'); + + // Map configs to file info format expected by frontend + const files = configs.map((config) => { + const filePath = path.join( + process.cwd(), + 'apps/backend/src/infrastructure/storage/csv-storage/rates', + config.csvFilePath + ); + + let fileSize = 0; + try { + const stats = fs.statSync(filePath); + fileSize = stats.size; + } catch (error) { + this.logger.warn(`Could not get file size for ${config.csvFilePath}`); + } + + return { + filename: config.csvFilePath, + size: fileSize, + uploadedAt: config.uploadedAt.toISOString(), + rowCount: config.rowCount, + }; + }); + + return { files }; + } + + /** + * Delete CSV file (Frontend compatibility endpoint) + * Maps to DELETE /files/:filename + */ + @Delete('files/:filename') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'Delete CSV file by filename (ADMIN only)', + description: 'Deletes a CSV file and its configuration from the system.', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'File deleted successfully', + schema: { + type: 'object', + properties: { + success: { type: 'boolean', example: true }, + message: { type: 'string', example: 'File deleted successfully' }, + }, + }, + }) + @ApiResponse({ + status: 404, + description: 'File not found', + }) + async deleteFile( + @Param('filename') filename: string, + @CurrentUser() user: UserPayload + ): Promise<{ success: boolean; message: string }> { + this.logger.warn(`[Admin: ${user.email}] Deleting CSV file: ${filename}`); + + // Find config by file path + const configs = await this.csvConfigRepository.findAll(); + const config = configs.find((c) => c.csvFilePath === filename); + + if (!config) { + throw new BadRequestException(`No configuration found for file: ${filename}`); + } + + // Delete the file from filesystem + const fs = require('fs'); + const path = require('path'); + const filePath = path.join( + process.cwd(), + 'apps/backend/src/infrastructure/storage/csv-storage/rates', + filename + ); + + try { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + this.logger.log(`Deleted file: ${filePath}`); + } + } catch (error: any) { + this.logger.error(`Failed to delete file ${filePath}: ${error.message}`); + } + + // Delete the configuration + await this.csvConfigRepository.delete(config.companyName); + + this.logger.log(`Deleted CSV config and file for: ${config.companyName}`); + + return { + success: true, + message: `File ${filename} deleted successfully`, + }; + } } diff --git a/apps/frontend/src/app/admin/csv-rates/page.tsx b/apps/frontend/app/dashboard/admin/csv-rates/page.tsx similarity index 100% rename from apps/frontend/src/app/admin/csv-rates/page.tsx rename to apps/frontend/app/dashboard/admin/csv-rates/page.tsx diff --git a/apps/frontend/app/dashboard/layout.tsx b/apps/frontend/app/dashboard/layout.tsx index c1119e9..5c35561 100644 --- a/apps/frontend/app/dashboard/layout.tsx +++ b/apps/frontend/app/dashboard/layout.tsx @@ -25,6 +25,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod { name: 'My Profile', href: '/dashboard/profile', icon: '👤' }, { name: 'Organization', href: '/dashboard/settings/organization', icon: '🏢' }, { name: 'Users', href: '/dashboard/settings/users', icon: '👥' }, + // ADMIN only navigation items + ...(user?.role === 'ADMIN' ? [ + { name: 'CSV Rates', href: '/dashboard/admin/csv-rates', icon: '📄' }, + ] : []), ]; const isActive = (href: string) => { diff --git a/apps/frontend/src/components/admin/CsvUpload.tsx b/apps/frontend/src/components/admin/CsvUpload.tsx index 662288d..6ef5c74 100644 --- a/apps/frontend/src/components/admin/CsvUpload.tsx +++ b/apps/frontend/src/components/admin/CsvUpload.tsx @@ -20,6 +20,7 @@ import { uploadCsvRates } from '@/lib/api/admin/csv-rates'; export function CsvUpload() { const router = useRouter(); const [companyName, setCompanyName] = useState(''); + const [companyEmail, setCompanyEmail] = useState(''); const [file, setFile] = useState(null); const [loading, setLoading] = useState(false); const [success, setSuccess] = useState(null); @@ -47,7 +48,7 @@ export function CsvUpload() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!file || !companyName) { + if (!file || !companyName || !companyEmail) { setError('Veuillez remplir tous les champs'); return; } @@ -60,11 +61,13 @@ export function CsvUpload() { const formData = new FormData(); formData.append('file', file); formData.append('companyName', companyName); + formData.append('companyEmail', companyEmail); const result = await uploadCsvRates(formData); setSuccess(`✅ Succès ! ${result.rowCount} tarifs uploadés pour ${result.companyName}`); setCompanyName(''); + setCompanyEmail(''); setFile(null); // Reset file input @@ -86,6 +89,7 @@ export function CsvUpload() { const handleReset = () => { setCompanyName(''); + setCompanyEmail(''); setFile(null); setError(null); setSuccess(null); @@ -129,6 +133,25 @@ export function CsvUpload() {

+ {/* Company Email */} +
+ + setCompanyEmail(e.target.value)} + placeholder="Ex: bookings@sscconsolidation.com" + required + disabled={loading} + /> +

+ Email pour les demandes de réservation auprès de cette compagnie +

+
+ {/* File Input */}