mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-23 07:08:17 +02:00
feat: add Kanban view to mobile interface
- Add view mode toggle (List/Kanban) in mobile notes page - Integrate KanbanBoard component with drag & drop support - Support moving notes between columns via tags - Auto-refresh notes list after moving to new column - Maintain search and filter functionality in Kanban view - Add mobile-optimized horizontal scrolling for Kanban columns
This commit is contained in:
1 file changed
+85
-25
@@ -5,12 +5,14 @@ import { Input } from "@/components/ui/input";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { MobileFAB } from "@/components/mobile/MobileFAB";
|
import { MobileFAB } from "@/components/mobile/MobileFAB";
|
||||||
import { MobileCard } from "@/components/mobile/MobileCard";
|
import { MobileCard } from "@/components/mobile/MobileCard";
|
||||||
import { Plus, Search, CheckSquare, Image as ImageIcon, Paperclip, Tag as TagIcon, FileText } from "lucide-react";
|
import { Plus, Search, CheckSquare, Image as ImageIcon, Paperclip, Tag as TagIcon, FileText, LayoutGrid, List } from "lucide-react";
|
||||||
import NotesService, { Note } from "@/services/NotesService";
|
import NotesService, { Note } from "@/services/NotesService";
|
||||||
import { showError, showSuccess } from "@/utils/toast";
|
import { showError, showSuccess } from "@/utils/toast";
|
||||||
import { TemplateSelector } from "@/components/TemplateSelector";
|
import { TemplateSelector } from "@/components/TemplateSelector";
|
||||||
import type { NoteTemplate } from "@/utils/noteTemplates";
|
import type { NoteTemplate } from "@/utils/noteTemplates";
|
||||||
import { AdvancedSearch, type SearchFilters, type TagOption } from "@/components/AdvancedSearch";
|
import { AdvancedSearch, type SearchFilters, type TagOption } from "@/components/AdvancedSearch";
|
||||||
|
import { KanbanBoard } from "@/components/KanbanBoard";
|
||||||
|
import TagsService from "@/services/TagsService";
|
||||||
|
|
||||||
const NOTES_PER_PAGE = 10;
|
const NOTES_PER_PAGE = 10;
|
||||||
|
|
||||||
@@ -29,6 +31,7 @@ export default function NotesPage() {
|
|||||||
const [showArchived, setShowArchived] = useState(false);
|
const [showArchived, setShowArchived] = useState(false);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
const [templateSelectorOpen, setTemplateSelectorOpen] = useState(false);
|
const [templateSelectorOpen, setTemplateSelectorOpen] = useState(false);
|
||||||
|
const [viewMode, setViewMode] = useState<'list' | 'kanban'>('list');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const loadNotes = async () => {
|
const loadNotes = async () => {
|
||||||
@@ -209,9 +212,32 @@ export default function NotesPage() {
|
|||||||
Archivées ({notes.filter(n => n.archived).length})
|
Archivées ({notes.filter(n => n.archived).length})
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* View Mode Toggle */}
|
||||||
|
<div className="flex gap-1 border rounded-md p-1">
|
||||||
|
<Button
|
||||||
|
variant={viewMode === 'list' ? 'default' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setViewMode('list')}
|
||||||
|
className="flex-1 gap-2"
|
||||||
|
>
|
||||||
|
<List className="h-4 w-4" />
|
||||||
|
Liste
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={viewMode === 'kanban' ? 'default' : 'ghost'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setViewMode('kanban')}
|
||||||
|
className="flex-1 gap-2"
|
||||||
|
>
|
||||||
|
<LayoutGrid className="h-4 w-4" />
|
||||||
|
Kanban
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Notes List */}
|
{/* Notes View - List or Kanban */}
|
||||||
|
{viewMode === 'list' ? (
|
||||||
<div className="p-4 space-y-3">
|
<div className="p-4 space-y-3">
|
||||||
{paginatedNotes.length > 0 ? (
|
{paginatedNotes.length > 0 ? (
|
||||||
paginatedNotes.map(note => (
|
paginatedNotes.map(note => (
|
||||||
@@ -304,30 +330,64 @@ export default function NotesPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex items-center justify-center gap-4 px-4 pb-4">
|
<div className="flex items-center justify-center gap-4 px-4 pb-4">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setPage(p => Math.max(0, p - 1))}
|
onClick={() => setPage(p => Math.max(0, p - 1))}
|
||||||
disabled={page === 0}
|
disabled={page === 0}
|
||||||
>
|
>
|
||||||
‹ Précédent
|
‹ Précédent
|
||||||
</Button>
|
</Button>
|
||||||
<span className="text-sm text-muted-foreground">
|
<span className="text-sm text-muted-foreground">
|
||||||
{page + 1} / {totalPages}
|
{page + 1} / {totalPages}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))}
|
onClick={() => setPage(p => Math.min(totalPages - 1, p + 1))}
|
||||||
disabled={page === totalPages - 1}
|
disabled={page === totalPages - 1}
|
||||||
>
|
>
|
||||||
Suivant ›
|
Suivant ›
|
||||||
</Button>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
/* Kanban View */
|
||||||
|
<div className="p-2 overflow-x-auto">
|
||||||
|
<KanbanBoard
|
||||||
|
notes={filteredNotes}
|
||||||
|
onNoteClick={(note) => navigate(`/mobile/notes/${note.id}`)}
|
||||||
|
onCreateNote={(columnId) => {
|
||||||
|
setTemplateSelectorOpen(true);
|
||||||
|
}}
|
||||||
|
onMoveNote={async (noteId, newStatus) => {
|
||||||
|
const note = notes.find(n => n.id === noteId);
|
||||||
|
if (!note) return;
|
||||||
|
|
||||||
|
// Remove old status tags
|
||||||
|
const oldStatusTags = note.tags?.filter(tag =>
|
||||||
|
['backlog', 'todo', 'in_progress', 'review', 'done'].includes(tag.name.toLowerCase())
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
for (const tag of oldStatusTags) {
|
||||||
|
if (tag.id && note.id) {
|
||||||
|
await TagsService.deleteTag(note.id, tag.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new status tag
|
||||||
|
if (note.id) {
|
||||||
|
await TagsService.addTag(note.id, newStatus);
|
||||||
|
await loadNotes();
|
||||||
|
showSuccess(`Note déplacée vers ${newStatus}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user