Buckets:
| import React, { useState, useEffect } from 'react'; | |
| import { | |
| Box, | |
| Typography, | |
| Card, | |
| CardContent, | |
| CardActions, | |
| Button, | |
| TextField, | |
| Grid, | |
| Alert, | |
| CircularProgress, | |
| IconButton, | |
| Dialog, | |
| DialogTitle, | |
| DialogContent, | |
| DialogActions, | |
| } from '@mui/material'; | |
| import { Add as AddIcon, Delete as DeleteIcon, FolderOpen as OpenIcon } from '@mui/icons-material'; | |
| import { api } from '../services/api'; | |
| import { Project } from '../types'; | |
| interface ProjectListProps { | |
| onSelectProject: (project: Project) => void; | |
| } | |
| const ProjectList: React.FC<ProjectListProps> = ({ onSelectProject }) => { | |
| const [projects, setProjects] = useState<Project[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const [dialogOpen, setDialogOpen] = useState(false); | |
| const [newProjectName, setNewProjectName] = useState(''); | |
| const [newProjectDescription, setNewProjectDescription] = useState(''); | |
| const [creating, setCreating] = useState(false); | |
| useEffect(() => { | |
| loadProjects(); | |
| }, []); | |
| const loadProjects = async () => { | |
| setLoading(true); | |
| setError(null); | |
| try { | |
| const data = await api.getProjects(); | |
| setProjects(data); | |
| } catch (err: any) { | |
| setError(err.message || 'Erreur lors du chargement des projets'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleCreateProject = async () => { | |
| if (!newProjectName.trim()) return; | |
| setCreating(true); | |
| try { | |
| const project = await api.createProject(newProjectName, newProjectDescription); | |
| setProjects([...projects, project]); | |
| setDialogOpen(false); | |
| setNewProjectName(''); | |
| setNewProjectDescription(''); | |
| } catch (err: any) { | |
| setError(err.message || 'Erreur lors de la création'); | |
| } finally { | |
| setCreating(false); | |
| } | |
| }; | |
| const handleDeleteProject = async (projectId: string) => { | |
| try { | |
| await api.deleteProject(projectId); | |
| setProjects(projects.filter((p) => p.id !== projectId)); | |
| } catch (err: any) { | |
| setError(err.message || 'Erreur lors de la suppression'); | |
| } | |
| }; | |
| if (loading) { | |
| return ( | |
| <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexGrow: 1 }}> | |
| <CircularProgress /> | |
| </Box> | |
| ); | |
| } | |
| return ( | |
| <Box sx={{ flexGrow: 1, p: 3, overflow: 'auto' }}> | |
| <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}> | |
| <Typography variant="h5">Mes Projets</Typography> | |
| <Button | |
| variant="contained" | |
| startIcon={<AddIcon />} | |
| onClick={() => setDialogOpen(true)} | |
| > | |
| Nouveau Projet | |
| </Button> | |
| </Box> | |
| {error && ( | |
| <Alert severity="error" sx={{ mb: 2 }}> | |
| {error} | |
| </Alert> | |
| )} | |
| {projects.length === 0 ? ( | |
| <Box sx={{ textAlign: 'center', py: 8 }}> | |
| <Typography color="textSecondary" sx={{ mb: 2 }}> | |
| Aucun projet. Créez votre premier projet ! | |
| </Typography> | |
| <Button | |
| variant="outlined" | |
| startIcon={<AddIcon />} | |
| onClick={() => setDialogOpen(true)} | |
| > | |
| Créer un projet | |
| </Button> | |
| </Box> | |
| ) : ( | |
| <Grid container spacing={2}> | |
| {projects.map((project) => ( | |
| <Grid item xs={12} sm={6} md={4} key={project.id}> | |
| <Card> | |
| <CardContent> | |
| <Typography variant="h6" noWrap> | |
| {project.name} | |
| </Typography> | |
| <Typography variant="body2" color="textSecondary"> | |
| {project.description || 'Aucune description'} | |
| </Typography> | |
| <Typography variant="caption" color="textSecondary" sx={{ display: 'block', mt: 1 }}> | |
| Créé le {new Date(project.created_at).toLocaleDateString('fr-FR')} | |
| </Typography> | |
| </CardContent> | |
| <CardActions> | |
| <Button | |
| size="small" | |
| startIcon={<OpenIcon />} | |
| onClick={() => onSelectProject(project)} | |
| > | |
| Ouvrir | |
| </Button> | |
| <IconButton | |
| size="small" | |
| color="error" | |
| onClick={() => handleDeleteProject(project.id)} | |
| > | |
| <DeleteIcon /> | |
| </IconButton> | |
| </CardActions> | |
| </Card> | |
| </Grid> | |
| ))} | |
| </Grid> | |
| )} | |
| <Dialog open={dialogOpen} onClose={() => setDialogOpen(false)}> | |
| <DialogTitle>Nouveau Projet</DialogTitle> | |
| <DialogContent> | |
| <TextField | |
| autoFocus | |
| fullWidth | |
| label="Nom du projet" | |
| value={newProjectName} | |
| onChange={(e) => setNewProjectName(e.target.value)} | |
| sx={{ mt: 1 }} | |
| /> | |
| <TextField | |
| fullWidth | |
| multiline | |
| rows={3} | |
| label="Description (optionnelle)" | |
| value={newProjectDescription} | |
| onChange={(e) => setNewProjectDescription(e.target.value)} | |
| sx={{ mt: 2 }} | |
| /> | |
| </DialogContent> | |
| <DialogActions> | |
| <Button onClick={() => setDialogOpen(false)}>Annuler</Button> | |
| <Button | |
| variant="contained" | |
| onClick={handleCreateProject} | |
| disabled={creating || !newProjectName.trim()} | |
| > | |
| {creating ? <CircularProgress size={20} /> : 'Créer'} | |
| </Button> | |
| </DialogActions> | |
| </Dialog> | |
| </Box> | |
| ); | |
| }; | |
| export default ProjectList; | |
Xet Storage Details
- Size:
- 5.78 kB
- Xet hash:
- 81795fdf51328f2ccabb68a9a7ab811c92be49eb7b2755f21dc7b17fa9988ca7
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.