mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-24 15:48:16 +02:00
Merge pull request #30 from R0m1k3/claude/fix-npm-ci-build-error-011CV3c2K4ETHdBRQUs7tkJf
Feature: Redesign de l'interface sans sidebar pour écran 1920x1080
This commit is contained in:
1 file changed
+295
-255
+295
-255
@@ -8,13 +8,12 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { RichTextEditor } from "@/components/RichTextEditor";
|
import { RichTextEditor } from "@/components/RichTextEditor";
|
||||||
import { MadeWithDyad } from "@/components/made-with-dyad";
|
import { MadeWithDyad } from "@/components/made-with-dyad";
|
||||||
import {
|
import {
|
||||||
PlusCircle, Search, User, LogOut, Settings, ChevronDown, Plus, Archive, Trash2,
|
PlusCircle, Search, User, LogOut, Settings, ChevronDown, Plus, Archive, Trash2,
|
||||||
Image, CheckSquare, FileText, Rss, ExternalLink, RefreshCw, Key, Zap, Paperclip, X
|
Image, CheckSquare, FileText, Rss, ExternalLink, RefreshCw, Key, Zap, Paperclip, X, Edit
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import AuthService from "@/services/AuthService";
|
import AuthService from "@/services/AuthService";
|
||||||
import AdminService from "@/services/AdminService";
|
import AdminService from "@/services/AdminService";
|
||||||
@@ -34,16 +33,17 @@ interface UserType {
|
|||||||
const Index = () => {
|
const Index = () => {
|
||||||
const [user, setUser] = useState<UserType | null>(null);
|
const [user, setUser] = useState<UserType | null>(null);
|
||||||
const [showAdmin, setShowAdmin] = useState(false);
|
const [showAdmin, setShowAdmin] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState("all");
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [notes, setNotes] = useState<Note[]>([]);
|
const [notes, setNotes] = useState<Note[]>([]);
|
||||||
const [currentNote, setCurrentNote] = useState<Note | null>(null);
|
const [currentNote, setCurrentNote] = useState<Note | null>(null);
|
||||||
|
const [showNoteDialog, setShowNoteDialog] = useState(false);
|
||||||
const [users, setUsers] = useState<UserType[]>([]);
|
const [users, setUsers] = useState<UserType[]>([]);
|
||||||
const [todos, setTodos] = useState<Todo[]>([]);
|
const [todos, setTodos] = useState<Todo[]>([]);
|
||||||
const [rssFeeds, setRssFeeds] = useState<RssFeed[]>([]);
|
const [rssFeeds, setRssFeeds] = useState<RssFeed[]>([]);
|
||||||
const [rssArticles, setRssArticles] = useState<RssArticle[]>([]);
|
const [rssArticles, setRssArticles] = useState<RssArticle[]>([]);
|
||||||
const [settings, setSettings] = useState<AppSettings>({});
|
const [settings, setSettings] = useState<AppSettings>({});
|
||||||
const [adminTab, setAdminTab] = useState("users");
|
const [adminTab, setAdminTab] = useState("users");
|
||||||
|
const [showArchived, setShowArchived] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -64,9 +64,6 @@ const Index = () => {
|
|||||||
const fetchedNotes = await NotesService.getNotes();
|
const fetchedNotes = await NotesService.getNotes();
|
||||||
if (Array.isArray(fetchedNotes)) {
|
if (Array.isArray(fetchedNotes)) {
|
||||||
setNotes(fetchedNotes);
|
setNotes(fetchedNotes);
|
||||||
if (fetchedNotes.length > 0) {
|
|
||||||
setCurrentNote(fetchedNotes[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur lors du chargement des notes:", error);
|
console.error("Erreur lors du chargement des notes:", error);
|
||||||
@@ -97,7 +94,7 @@ const Index = () => {
|
|||||||
|
|
||||||
const loadRssArticles = async () => {
|
const loadRssArticles = async () => {
|
||||||
try {
|
try {
|
||||||
const articles = await RssService.getArticles(10);
|
const articles = await RssService.getArticles(15);
|
||||||
if (Array.isArray(articles)) {
|
if (Array.isArray(articles)) {
|
||||||
setRssArticles(articles);
|
setRssArticles(articles);
|
||||||
}
|
}
|
||||||
@@ -148,12 +145,18 @@ const Index = () => {
|
|||||||
if (newNote) {
|
if (newNote) {
|
||||||
setNotes([newNote, ...notes]);
|
setNotes([newNote, ...notes]);
|
||||||
setCurrentNote(newNote);
|
setCurrentNote(newNote);
|
||||||
|
setShowNoteDialog(true);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError("Erreur lors de la création de la note");
|
showError("Erreur lors de la création de la note");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOpenNote = (note: Note) => {
|
||||||
|
setCurrentNote(note);
|
||||||
|
setShowNoteDialog(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleUpdateNote = async (updatedFields: Partial<Note>) => {
|
const handleUpdateNote = async (updatedFields: Partial<Note>) => {
|
||||||
if (!currentNote) return;
|
if (!currentNote) return;
|
||||||
|
|
||||||
@@ -187,7 +190,8 @@ const Index = () => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
const updatedNotes = notes.filter(note => note.id !== currentNote.id);
|
const updatedNotes = notes.filter(note => note.id !== currentNote.id);
|
||||||
setNotes(updatedNotes);
|
setNotes(updatedNotes);
|
||||||
setCurrentNote(updatedNotes.length > 0 ? updatedNotes[0] : null);
|
setShowNoteDialog(false);
|
||||||
|
setCurrentNote(null);
|
||||||
showSuccess("Note supprimée");
|
showSuccess("Note supprimée");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -299,6 +303,20 @@ const Index = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleChangePassword = async (userId: number) => {
|
||||||
|
const newPassword = prompt("Nouveau mot de passe:");
|
||||||
|
if (newPassword) {
|
||||||
|
try {
|
||||||
|
const success = await AdminService.updateUserPassword(userId, newPassword);
|
||||||
|
if (success) {
|
||||||
|
showSuccess("Mot de passe modifié");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError("Erreur lors de la modification");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveSettings = async () => {
|
const handleSaveSettings = async () => {
|
||||||
try {
|
try {
|
||||||
await SettingsService.updateSettings(settings);
|
await SettingsService.updateSettings(settings);
|
||||||
@@ -337,7 +355,7 @@ const Index = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filteredNotes = notes.filter(note =>
|
const filteredNotes = notes.filter(note =>
|
||||||
(activeTab === "all" || (activeTab === "archived" && note.archived)) &&
|
(showArchived ? note.archived : !note.archived) &&
|
||||||
(searchQuery === "" ||
|
(searchQuery === "" ||
|
||||||
(note.title && note.title.toLowerCase().includes(searchQuery.toLowerCase())) ||
|
(note.title && note.title.toLowerCase().includes(searchQuery.toLowerCase())) ||
|
||||||
(note.content && note.content.toLowerCase().includes(searchQuery.toLowerCase())))
|
(note.content && note.content.toLowerCase().includes(searchQuery.toLowerCase())))
|
||||||
@@ -347,21 +365,26 @@ const Index = () => {
|
|||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||||
{/* Top Navigation */}
|
{/* Top Navigation */}
|
||||||
<header className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-30">
|
<header className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-30">
|
||||||
<div className="max-w-screen-2xl mx-auto px-4">
|
<div className="max-w-[1920px] mx-auto px-8">
|
||||||
<div className="flex h-16 items-center justify-between">
|
<div className="flex h-16 items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-3">
|
||||||
<div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center">
|
<div className="h-10 w-10 rounded-full bg-primary/10 flex items-center justify-center">
|
||||||
<FileText className="h-4 w-4 text-primary" />
|
<FileText className="h-5 w-5 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xl font-semibold">NoteFlow</span>
|
<span className="text-2xl font-semibold">NoteFlow</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-6">
|
||||||
|
<Button onClick={handleCreateNote} size="lg" className="gap-2">
|
||||||
|
<PlusCircle className="h-5 w-5" />
|
||||||
|
Nouvelle Note
|
||||||
|
</Button>
|
||||||
|
|
||||||
{user && (
|
{user && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="flex items-center gap-2">
|
<Button variant="ghost" className="flex items-center gap-2">
|
||||||
<Avatar className="h-8 w-8">
|
<Avatar className="h-9 w-9">
|
||||||
<AvatarFallback className="bg-primary/10 text-primary">
|
<AvatarFallback className="bg-primary/10 text-primary">
|
||||||
{user.username ? user.username.charAt(0).toUpperCase() : "U"}
|
{user.username ? user.username.charAt(0).toUpperCase() : "U"}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
@@ -392,67 +415,215 @@ const Index = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="max-w-screen-2xl mx-auto px-4 py-6">
|
<div className="max-w-[1920px] mx-auto px-8 py-8">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-[350px_1fr_300px] gap-6">
|
{/* Todos & RSS Boxes - Large side by side */}
|
||||||
{/* Left Sidebar - Notes */}
|
<div className="grid grid-cols-2 gap-6 mb-8">
|
||||||
|
{/* Todos Box */}
|
||||||
|
<Card className="shadow-lg">
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
|
<CheckSquare className="h-6 w-6" />
|
||||||
|
Tâches
|
||||||
|
</CardTitle>
|
||||||
|
<Button size="lg" variant="outline" onClick={handleAddTodo} className="gap-2">
|
||||||
|
<Plus className="h-5 w-5" />
|
||||||
|
Ajouter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<Tabs defaultValue="active" className="w-full">
|
||||||
|
<TabsList className="grid grid-cols-2 w-full">
|
||||||
|
<TabsTrigger value="active">Actives ({todos.filter(t => !t.completed).length})</TabsTrigger>
|
||||||
|
<TabsTrigger value="completed">Complétées ({todos.filter(t => t.completed).length})</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="active" className="mt-4">
|
||||||
|
<div className="space-y-3 max-h-[400px] overflow-y-auto">
|
||||||
|
{todos.filter(t => !t.completed).length > 0 ? (
|
||||||
|
todos.filter(t => !t.completed).map(todo => (
|
||||||
|
<div key={todo.id} className="flex items-center gap-3 p-3 border rounded-lg hover:bg-accent/50 transition-colors group">
|
||||||
|
<Checkbox
|
||||||
|
checked={false}
|
||||||
|
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
|
||||||
|
className="h-5 w-5"
|
||||||
|
/>
|
||||||
|
<span className="text-base flex-1">
|
||||||
|
{todo.text}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
|
onClick={() => todo.id && handleDeleteTodo(todo.id)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-center text-muted-foreground py-8">Aucune tâche active</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="completed" className="mt-4">
|
||||||
|
<div className="space-y-3 max-h-[400px] overflow-y-auto">
|
||||||
|
{todos.filter(t => t.completed).length > 0 ? (
|
||||||
|
todos.filter(t => t.completed).map(todo => (
|
||||||
|
<div key={todo.id} className="flex items-center gap-3 p-3 border rounded-lg hover:bg-accent/50 transition-colors group">
|
||||||
|
<Checkbox
|
||||||
|
checked={true}
|
||||||
|
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
|
||||||
|
className="h-5 w-5"
|
||||||
|
/>
|
||||||
|
<span className="text-base flex-1 line-through text-muted-foreground">
|
||||||
|
{todo.text}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
|
onClick={() => todo.id && handleDeleteTodo(todo.id)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-center text-muted-foreground py-8">Aucune tâche complétée</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* RSS Box */}
|
||||||
|
<Card className="shadow-lg">
|
||||||
|
<CardHeader className="pb-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<CardTitle className="text-xl flex items-center gap-2">
|
||||||
|
<Rss className="h-6 w-6" />
|
||||||
|
Flux RSS
|
||||||
|
</CardTitle>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="lg" variant="outline" onClick={handleRefreshRss} className="gap-2">
|
||||||
|
<RefreshCw className="h-5 w-5" />
|
||||||
|
Actualiser
|
||||||
|
</Button>
|
||||||
|
<Button size="lg" variant="outline" onClick={handleAddRssFeed} className="gap-2">
|
||||||
|
<Plus className="h-5 w-5" />
|
||||||
|
Ajouter
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-3 max-h-[480px] overflow-y-auto">
|
||||||
|
{rssArticles.length > 0 ? (
|
||||||
|
rssArticles.map(article => (
|
||||||
|
<div
|
||||||
|
key={article.id}
|
||||||
|
className="p-3 border rounded-lg hover:bg-accent/50 transition-colors cursor-pointer"
|
||||||
|
onClick={() => article.link && window.open(article.link, '_blank')}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-3">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<h4 className="font-medium line-clamp-2 mb-1">{article.title}</h4>
|
||||||
|
{article.description && (
|
||||||
|
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||||
|
{article.description.replace(/<[^>]*>/g, '')}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<ExternalLink className="h-4 w-4 text-muted-foreground flex-shrink-0 mt-1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-center text-muted-foreground py-8">
|
||||||
|
Aucun article
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
className="mt-2"
|
||||||
|
onClick={handleAddRssFeed}
|
||||||
|
>
|
||||||
|
Ajouter un flux RSS
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notes Section */}
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="relative">
|
<div className="flex items-center justify-between">
|
||||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
<div className="flex items-center gap-4">
|
||||||
|
<h2 className="text-2xl font-semibold">Mes Notes</h2>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant={!showArchived ? "default" : "outline"}
|
||||||
|
onClick={() => setShowArchived(false)}
|
||||||
|
>
|
||||||
|
Actives ({notes.filter(n => !n.archived).length})
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={showArchived ? "default" : "outline"}
|
||||||
|
onClick={() => setShowArchived(true)}
|
||||||
|
>
|
||||||
|
Archivées ({notes.filter(n => n.archived).length})
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative w-96">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
type="search"
|
type="search"
|
||||||
placeholder="Rechercher une note..."
|
placeholder="Rechercher dans les notes..."
|
||||||
className="pl-9"
|
className="pl-10 h-12 text-base"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
{/* Notes Grid */}
|
||||||
className="w-full flex items-center gap-2"
|
<div className="grid grid-cols-4 gap-6">
|
||||||
size="lg"
|
{filteredNotes.length > 0 ? (
|
||||||
onClick={handleCreateNote}
|
|
||||||
>
|
|
||||||
<PlusCircle className="h-4 w-4" />
|
|
||||||
Nouvelle Note
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Tabs defaultValue="all" value={activeTab} onValueChange={setActiveTab}>
|
|
||||||
<TabsList className="grid grid-cols-2 w-full">
|
|
||||||
<TabsTrigger value="all">Toutes</TabsTrigger>
|
|
||||||
<TabsTrigger value="archived">Archivées</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
<div className="space-y-2 max-h-[calc(100vh-340px)] overflow-y-auto">
|
|
||||||
{filteredNotes.length === 0 ? (
|
|
||||||
<p className="text-center text-muted-foreground py-4">Aucune note trouvée</p>
|
|
||||||
) : (
|
|
||||||
filteredNotes.map(note => (
|
filteredNotes.map(note => (
|
||||||
<Card
|
<Card
|
||||||
key={note.id}
|
key={note.id}
|
||||||
className={`cursor-pointer hover:shadow-md transition-shadow ${currentNote?.id === note.id ? 'border-primary' : ''}`}
|
className="cursor-pointer hover:shadow-xl transition-all hover:scale-105 group"
|
||||||
onClick={() => setCurrentNote(note)}
|
onClick={() => handleOpenNote(note)}
|
||||||
>
|
>
|
||||||
<CardContent className="p-4">
|
<CardContent className="p-6">
|
||||||
<h3 className="font-medium truncate">{note.title || "Sans titre"}</h3>
|
<h3 className="font-semibold text-lg mb-3 line-clamp-2 min-h-[56px]">
|
||||||
<p className="text-sm text-muted-foreground line-clamp-2 mt-1">
|
{note.title || "Sans titre"}
|
||||||
{note.content ? note.content.replace(/<[^>]*>/g, '') : ""}
|
</h3>
|
||||||
|
<p className="text-sm text-muted-foreground line-clamp-4 mb-4 min-h-[80px]">
|
||||||
|
{note.content ? note.content.replace(/<[^>]*>/g, '') : "Note vide"}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex gap-2 mt-2">
|
<div className="flex gap-2 flex-wrap">
|
||||||
{note.todos && note.todos.length > 0 && (
|
{note.todos && note.todos.length > 0 && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary">
|
||||||
<CheckSquare className="h-3 w-3 mr-1" />
|
<CheckSquare className="h-3 w-3 mr-1" />
|
||||||
{note.todos.length}
|
{note.todos.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{note.images && note.images.length > 0 && (
|
{note.images && note.images.length > 0 && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary">
|
||||||
<Image className="h-3 w-3 mr-1" />
|
<Image className="h-3 w-3 mr-1" />
|
||||||
{note.images.length}
|
{note.images.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{note.files && note.files.length > 0 && (
|
{note.files && note.files.length > 0 && (
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary">
|
||||||
<Paperclip className="h-3 w-3 mr-1" />
|
<Paperclip className="h-3 w-3 mr-1" />
|
||||||
{note.files.length}
|
{note.files.length}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -461,19 +632,34 @@ const Index = () => {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))
|
))
|
||||||
|
) : (
|
||||||
|
<div className="col-span-4 text-center py-16">
|
||||||
|
<FileText className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
|
||||||
|
<p className="text-muted-foreground text-lg mb-4">
|
||||||
|
{searchQuery ? "Aucune note trouvée" : "Aucune note"}
|
||||||
|
</p>
|
||||||
|
{!searchQuery && (
|
||||||
|
<Button onClick={handleCreateNote} size="lg">
|
||||||
|
<Plus className="h-5 w-5 mr-2" />
|
||||||
|
Créer votre première note
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Center - Note Editor */}
|
{/* Note Editor Dialog - Full Screen */}
|
||||||
{currentNote ? (
|
<Dialog open={showNoteDialog} onOpenChange={setShowNoteDialog}>
|
||||||
<div className="space-y-4">
|
<DialogContent className="max-w-[95vw] max-h-[95vh] overflow-y-auto">
|
||||||
<div className="flex items-center justify-between">
|
<DialogHeader>
|
||||||
|
<div className="flex items-center justify-between pr-8">
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Titre de la note"
|
placeholder="Titre de la note"
|
||||||
className="text-xl font-semibold border-none shadow-none text-2xl h-auto px-0 focus-visible:ring-0"
|
className="text-2xl font-semibold border-none shadow-none h-auto px-0 focus-visible:ring-0"
|
||||||
value={currentNote.title || ""}
|
value={currentNote?.title || ""}
|
||||||
onChange={(e) => handleUpdateNote({ title: e.target.value })}
|
onChange={(e) => handleUpdateNote({ title: e.target.value })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -481,7 +667,8 @@ const Index = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => handleUpdateNote({ archived: !currentNote.archived })}
|
onClick={() => currentNote && handleUpdateNote({ archived: !currentNote.archived })}
|
||||||
|
title={currentNote?.archived ? "Désarchiver" : "Archiver"}
|
||||||
>
|
>
|
||||||
<Archive className="h-4 w-4" />
|
<Archive className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -489,14 +676,17 @@ const Index = () => {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={handleDeleteNote}
|
onClick={handleDeleteNote}
|
||||||
|
title="Supprimer"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4" />
|
<Trash2 className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
<Tabs defaultValue="content">
|
{currentNote && (
|
||||||
<TabsList>
|
<Tabs defaultValue="content" className="mt-4">
|
||||||
|
<TabsList className="grid grid-cols-4 w-full max-w-md">
|
||||||
<TabsTrigger value="content">Contenu</TabsTrigger>
|
<TabsTrigger value="content">Contenu</TabsTrigger>
|
||||||
<TabsTrigger value="todos">
|
<TabsTrigger value="todos">
|
||||||
Tâches
|
Tâches
|
||||||
@@ -518,7 +708,7 @@ const Index = () => {
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="content" className="mt-4">
|
<TabsContent value="content" className="mt-6">
|
||||||
<RichTextEditor
|
<RichTextEditor
|
||||||
content={currentNote.content || ""}
|
content={currentNote.content || ""}
|
||||||
onChange={(content) => handleUpdateNote({ content })}
|
onChange={(content) => handleUpdateNote({ content })}
|
||||||
@@ -526,13 +716,12 @@ const Index = () => {
|
|||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="todos" className="mt-4">
|
<TabsContent value="todos" className="mt-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-4 space-y-4">
|
<CardContent className="p-6 space-y-4">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
className="w-full"
|
||||||
className="flex items-center gap-2"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const text = prompt("Nouvelle tâche:");
|
const text = prompt("Nouvelle tâche:");
|
||||||
if (text) {
|
if (text) {
|
||||||
@@ -541,14 +730,14 @@ const Index = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
Ajouter une tâche
|
Ajouter une tâche
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{currentNote.todos && currentNote.todos.length > 0 ? (
|
{currentNote.todos && currentNote.todos.length > 0 ? (
|
||||||
currentNote.todos.map((todo, index) => (
|
currentNote.todos.map((todo, index) => (
|
||||||
<div key={index} className="flex items-center gap-2 p-2 border rounded-md">
|
<div key={index} className="flex items-center gap-2 p-3 border rounded-md">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={todo.completed}
|
checked={todo.completed}
|
||||||
onCheckedChange={() => {
|
onCheckedChange={() => {
|
||||||
@@ -563,7 +752,6 @@ const Index = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-6 w-6"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedTodos = [...(currentNote.todos || [])];
|
const updatedTodos = [...(currentNote.todos || [])];
|
||||||
updatedTodos.splice(index, 1);
|
updatedTodos.splice(index, 1);
|
||||||
@@ -582,18 +770,17 @@ const Index = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="images" className="mt-4">
|
<TabsContent value="images" className="mt-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-4 space-y-4">
|
<CardContent className="p-6 space-y-4">
|
||||||
<label htmlFor="image-upload">
|
<label htmlFor="image-upload">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
className="w-full"
|
||||||
className="flex items-center gap-2"
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => document.getElementById('image-upload')?.click()}
|
onClick={() => document.getElementById('image-upload')?.click()}
|
||||||
>
|
>
|
||||||
<Image className="h-4 w-4" />
|
<Image className="h-4 w-4 mr-2" />
|
||||||
Ajouter une image
|
Ajouter une image
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
@@ -620,7 +807,7 @@ const Index = () => {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
<div className="grid grid-cols-4 gap-4">
|
||||||
{currentNote.images && currentNote.images.length > 0 ? (
|
{currentNote.images && currentNote.images.length > 0 ? (
|
||||||
currentNote.images.map((image, index) => (
|
currentNote.images.map((image, index) => (
|
||||||
<div key={index} className="relative aspect-square rounded-md overflow-hidden border group">
|
<div key={index} className="relative aspect-square rounded-md overflow-hidden border group">
|
||||||
@@ -632,7 +819,7 @@ const Index = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="absolute top-1 right-1 h-6 w-6 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
className="absolute top-2 right-2 h-8 w-8 rounded-full opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (image.id && currentNote.id) {
|
if (image.id && currentNote.id) {
|
||||||
try {
|
try {
|
||||||
@@ -648,30 +835,29 @@ const Index = () => {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X className="h-3 w-3" />
|
<X className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<p className="col-span-full text-center text-muted-foreground py-8">Aucune image</p>
|
<p className="col-span-4 text-center text-muted-foreground py-8">Aucune image</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="files" className="mt-4">
|
<TabsContent value="files" className="mt-6">
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="p-4 space-y-4">
|
<CardContent className="p-6 space-y-4">
|
||||||
<label htmlFor="file-upload">
|
<label htmlFor="file-upload">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
className="w-full"
|
||||||
className="flex items-center gap-2"
|
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => document.getElementById('file-upload')?.click()}
|
onClick={() => document.getElementById('file-upload')?.click()}
|
||||||
>
|
>
|
||||||
<Paperclip className="h-4 w-4" />
|
<Paperclip className="h-4 w-4 mr-2" />
|
||||||
Ajouter un fichier
|
Ajouter un fichier
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
@@ -686,18 +872,17 @@ const Index = () => {
|
|||||||
{currentNote.files && currentNote.files.length > 0 ? (
|
{currentNote.files && currentNote.files.length > 0 ? (
|
||||||
currentNote.files.map((file: any, index: number) => (
|
currentNote.files.map((file: any, index: number) => (
|
||||||
<div key={index} className="flex items-center justify-between p-3 border rounded-md hover:bg-accent/50 transition-colors group">
|
<div key={index} className="flex items-center justify-between p-3 border rounded-md hover:bg-accent/50 transition-colors group">
|
||||||
<div className="flex items-center gap-2 flex-1">
|
<div className="flex items-center gap-3 flex-1">
|
||||||
<Paperclip className="h-4 w-4 text-muted-foreground" />
|
<Paperclip className="h-5 w-5 text-muted-foreground" />
|
||||||
<span className="text-sm truncate">{file.filename || file.original_name}</span>
|
<span className="truncate">{file.filename || file.original_name}</span>
|
||||||
<Badge variant="secondary" className="text-xs">
|
<Badge variant="secondary">
|
||||||
{file.size ? `${(file.size / 1024).toFixed(1)} KB` : ''}
|
{file.size ? `${(file.size / 1024).toFixed(1)} KB` : ''}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8"
|
|
||||||
onClick={() => window.open(`/uploads/${file.filename}`, '_blank')}
|
onClick={() => window.open(`/uploads/${file.filename}`, '_blank')}
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-4 w-4" />
|
<ExternalLink className="h-4 w-4" />
|
||||||
@@ -705,7 +890,7 @@ const Index = () => {
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
|
className="opacity-0 group-hover:opacity-100 transition-opacity"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (file.id && currentNote.id) {
|
if (file.id && currentNote.id) {
|
||||||
try {
|
try {
|
||||||
@@ -739,164 +924,9 @@ const Index = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center h-[calc(100vh-200px)]">
|
|
||||||
<div className="text-center">
|
|
||||||
<FileText className="h-16 w-16 mx-auto text-muted-foreground mb-4" />
|
|
||||||
<p className="text-muted-foreground mb-4">Aucune note sélectionnée</p>
|
|
||||||
<Button onClick={handleCreateNote}>
|
|
||||||
<Plus className="h-4 w-4 mr-2" />
|
|
||||||
Créer une note
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
</DialogContent>
|
||||||
{/* Right Sidebar - Todos & RSS */}
|
</Dialog>
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Boxes côte à côte */}
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
{/* Todos Box */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="pb-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<CardTitle className="text-base flex items-center gap-2">
|
|
||||||
<CheckSquare className="h-4 w-4" />
|
|
||||||
Tâches
|
|
||||||
</CardTitle>
|
|
||||||
<Button size="sm" variant="ghost" onClick={handleAddTodo} className="h-7 w-7 p-0">
|
|
||||||
<Plus className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="pb-3">
|
|
||||||
<Tabs defaultValue="active" className="w-full">
|
|
||||||
<TabsList className="grid grid-cols-2 w-full h-8">
|
|
||||||
<TabsTrigger value="active" className="text-xs">Actives</TabsTrigger>
|
|
||||||
<TabsTrigger value="completed" className="text-xs">Complétées</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="active" className="mt-2">
|
|
||||||
<div className="space-y-2 max-h-[250px] overflow-y-auto">
|
|
||||||
{todos.filter(t => !t.completed).length > 0 ? (
|
|
||||||
todos.filter(t => !t.completed).map(todo => (
|
|
||||||
<div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
|
|
||||||
<Checkbox
|
|
||||||
checked={false}
|
|
||||||
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
|
|
||||||
/>
|
|
||||||
<span className="text-xs flex-1">
|
|
||||||
{todo.text}
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
onClick={() => todo.id && handleDeleteTodo(todo.id)}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className="text-xs text-center text-muted-foreground py-4">Aucune tâche active</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="completed" className="mt-2">
|
|
||||||
<div className="space-y-2 max-h-[250px] overflow-y-auto">
|
|
||||||
{todos.filter(t => t.completed).length > 0 ? (
|
|
||||||
todos.filter(t => t.completed).map(todo => (
|
|
||||||
<div key={todo.id} className="flex items-center gap-2 p-2 border rounded-md hover:bg-accent/50 transition-colors group">
|
|
||||||
<Checkbox
|
|
||||||
checked={true}
|
|
||||||
onCheckedChange={() => todo.id && handleToggleTodo(todo.id)}
|
|
||||||
/>
|
|
||||||
<span className="text-xs flex-1 line-through text-muted-foreground">
|
|
||||||
{todo.text}
|
|
||||||
</span>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
||||||
onClick={() => todo.id && handleDeleteTodo(todo.id)}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className="text-xs text-center text-muted-foreground py-4">Aucune tâche complétée</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* RSS Box */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader className="pb-3">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<CardTitle className="text-base flex items-center gap-2">
|
|
||||||
<Rss className="h-4 w-4" />
|
|
||||||
Flux RSS
|
|
||||||
</CardTitle>
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Button size="sm" variant="ghost" onClick={handleRefreshRss} className="h-7 w-7 p-0">
|
|
||||||
<RefreshCw className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
<Button size="sm" variant="ghost" onClick={handleAddRssFeed} className="h-7 w-7 p-0">
|
|
||||||
<Plus className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="pb-3">
|
|
||||||
<div className="space-y-2 max-h-[330px] overflow-y-auto">
|
|
||||||
{rssArticles.length > 0 ? (
|
|
||||||
rssArticles.map(article => (
|
|
||||||
<div
|
|
||||||
key={article.id}
|
|
||||||
className="p-2 border rounded-md hover:bg-accent/50 transition-colors cursor-pointer"
|
|
||||||
onClick={() => article.link && window.open(article.link, '_blank')}
|
|
||||||
>
|
|
||||||
<div className="flex items-start justify-between gap-2">
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<h4 className="text-xs font-medium line-clamp-2 mb-1">{article.title}</h4>
|
|
||||||
{article.description && (
|
|
||||||
<p className="text-xs text-muted-foreground line-clamp-2">
|
|
||||||
{article.description.replace(/<[^>]*>/g, '')}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<ExternalLink className="h-3 w-3 text-muted-foreground flex-shrink-0 mt-1" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className="text-xs text-center text-muted-foreground py-4">
|
|
||||||
Aucun article
|
|
||||||
<br />
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
size="sm"
|
|
||||||
className="mt-2 text-xs"
|
|
||||||
onClick={handleAddRssFeed}
|
|
||||||
>
|
|
||||||
Ajouter un flux RSS
|
|
||||||
</Button>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Admin Modal */}
|
{/* Admin Modal */}
|
||||||
<Dialog open={showAdmin} onOpenChange={setShowAdmin}>
|
<Dialog open={showAdmin} onOpenChange={setShowAdmin}>
|
||||||
@@ -926,29 +956,39 @@ const Index = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{users.map(user => (
|
{users.map(u => (
|
||||||
<Card key={user.id}>
|
<Card key={u.id}>
|
||||||
<CardContent className="p-4 flex items-center justify-between">
|
<CardContent className="p-4 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Avatar>
|
<Avatar>
|
||||||
<AvatarFallback>{user.username.charAt(0).toUpperCase()}</AvatarFallback>
|
<AvatarFallback>{u.username.charAt(0).toUpperCase()}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">{user.username}</p>
|
<p className="font-medium">{u.username}</p>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{user.is_admin ? "Administrateur" : "Utilisateur"}
|
{u.is_admin ? "Administrateur" : "Utilisateur"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{user.id !== 1 && (
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => handleChangePassword(u.id)}
|
||||||
|
>
|
||||||
|
<Key className="h-4 w-4 mr-2" />
|
||||||
|
Mot de passe
|
||||||
|
</Button>
|
||||||
|
{u.id !== 1 && (
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => handleDeleteUser(user.id)}
|
onClick={() => handleDeleteUser(u.id)}
|
||||||
>
|
>
|
||||||
Supprimer
|
Supprimer
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in new issue
Block a user