This commit is contained in:
alma 2025-04-20 11:18:55 +02:00
parent bcbd4866df
commit 56c3f5a0ba
6 changed files with 399 additions and 7 deletions

View File

@ -3,10 +3,45 @@
import { useEffect, useState } from "react";
import { useSession } from "next-auth/react";
import { redirect } from "next/navigation";
import { Navigation } from "@/components/carnet/navigation";
import { NotesView } from "@/components/carnet/notes-view";
import { Editor } from "@/components/carnet/editor";
import { PanelResizer } from "@/components/carnet/panel-resizer";
import { useMediaQuery } from "@/hooks/use-media-query";
// Layout modes
export enum PaneLayout {
TagSelection = "tag-selection",
ItemSelection = "item-selection",
TableView = "table-view",
Editing = "editing"
}
interface Note {
id: string;
title: string;
content: string;
lastEdited: Date;
}
export default function CarnetPage() {
const { data: session, status } = useSession();
const [isLoading, setIsLoading] = useState(true);
const [layoutMode, setLayoutMode] = useState<PaneLayout>(PaneLayout.ItemSelection);
const [selectedNote, setSelectedNote] = useState<Note | null>(null);
const [isMobile, setIsMobile] = useState(false);
const [showNav, setShowNav] = useState(true);
const [showNotes, setShowNotes] = useState(true);
// Panel widths state
const [navWidth, setNavWidth] = useState(220);
const [notesWidth, setNotesWidth] = useState(400);
const [isDraggingNav, setIsDraggingNav] = useState(false);
const [isDraggingNotes, setIsDraggingNotes] = useState(false);
// Check screen size
const isSmallScreen = useMediaQuery("(max-width: 768px)");
const isMediumScreen = useMediaQuery("(max-width: 1024px)");
useEffect(() => {
if (status === "unauthenticated") {
@ -17,6 +52,51 @@ export default function CarnetPage() {
}
}, [status]);
useEffect(() => {
if (isSmallScreen) {
setIsMobile(true);
setShowNav(false);
setShowNotes(false);
} else if (isMediumScreen) {
setIsMobile(false);
setShowNav(true);
setShowNotes(false);
} else {
setIsMobile(false);
setShowNav(true);
setShowNotes(true);
}
}, [isSmallScreen, isMediumScreen]);
// Handle panel resizing
const handleNavResize = (e: MouseEvent) => {
if (!isDraggingNav) return;
const newWidth = e.clientX;
if (newWidth >= 48 && newWidth <= 400) {
setNavWidth(newWidth);
}
};
const handleNotesResize = (e: MouseEvent) => {
if (!isDraggingNotes) return;
const newWidth = e.clientX - navWidth - 2; // 2px for the resizer
if (newWidth >= 200) {
setNotesWidth(newWidth);
}
};
const handleNoteSelect = (note: Note) => {
setSelectedNote(note);
if (isMobile) {
setShowNotes(false);
}
};
const handleNoteSave = (note: Note) => {
// TODO: Implement note saving logic
console.log('Saving note:', note);
};
if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
@ -26,14 +106,69 @@ export default function CarnetPage() {
}
return (
<main className="w-full h-screen bg-black">
<div className="w-full h-full px-4 pt-12 pb-4">
<iframe
src={process.env.NEXT_PUBLIC_IFRAME_CARNET_URL}
className="w-full h-full border-0"
title="Carnet"
/>
<main className="flex h-screen bg-background">
{/* Navigation Panel */}
{showNav && (
<>
<div
className="flex flex-col h-full bg-sidebar"
style={{ width: `${navWidth}px` }}
>
<Navigation onLayoutChange={setLayoutMode} />
</div>
{/* Navigation Resizer */}
<PanelResizer
isDragging={isDraggingNav}
onDragStart={() => setIsDraggingNav(true)}
onDragEnd={() => setIsDraggingNav(false)}
onDrag={handleNavResize}
/>
</>
)}
{/* Notes Panel */}
{showNotes && (
<>
<div
className="flex flex-col h-full bg-panel"
style={{ width: `${notesWidth}px` }}
>
<NotesView onNoteSelect={handleNoteSelect} />
</div>
{/* Notes Resizer */}
<PanelResizer
isDragging={isDraggingNotes}
onDragStart={() => setIsDraggingNotes(true)}
onDragEnd={() => setIsDraggingNotes(false)}
onDrag={handleNotesResize}
/>
</>
)}
{/* Editor Panel */}
<div className="flex-1 flex flex-col h-full bg-background">
<Editor note={selectedNote} onSave={handleNoteSave} />
</div>
{/* Mobile Navigation Toggle */}
{isMobile && (
<div className="fixed bottom-4 right-4 flex space-x-2">
<button
className="p-2 rounded-full bg-primary text-white"
onClick={() => setShowNav(!showNav)}
>
{showNav ? 'Hide Nav' : 'Show Nav'}
</button>
<button
className="p-2 rounded-full bg-primary text-white"
onClick={() => setShowNotes(!showNotes)}
>
{showNotes ? 'Hide Notes' : 'Show Notes'}
</button>
</div>
)}
</main>
);
}

View File

@ -0,0 +1,83 @@
"use client";
import React, { useState, useEffect } from 'react';
import { Image, FileText } from 'lucide-react';
interface EditorProps {
note?: {
id: string;
title: string;
content: string;
};
onSave?: (note: { id: string; title: string; content: string }) => void;
}
export const Editor: React.FC<EditorProps> = ({ note, onSave }) => {
const [title, setTitle] = useState(note?.title || '');
const [content, setContent] = useState(note?.content || '');
useEffect(() => {
if (note) {
setTitle(note.title);
setContent(note.content);
}
}, [note]);
const handleTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTitle(e.target.value);
};
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setContent(e.target.value);
};
const handleSave = () => {
if (note?.id) {
onSave?.({
id: note.id,
title,
content
});
}
};
return (
<div className="flex flex-col h-full">
{/* Title Bar */}
<div className="p-4 border-b">
<input
type="text"
value={title}
onChange={handleTitleChange}
placeholder="Note title"
className="w-full text-xl font-semibold focus:outline-none bg-transparent"
/>
</div>
{/* Editor Area */}
<div className="flex-1 p-4">
<textarea
value={content}
onChange={handleContentChange}
placeholder="Start writing..."
className="w-full h-full resize-none focus:outline-none bg-transparent"
/>
</div>
{/* Toolbar */}
<div className="p-4 border-t">
<div className="flex space-x-2">
<button
className="p-2 text-gray-500 hover:text-gray-700"
onClick={handleSave}
>
<FileText className="h-5 w-5" />
</button>
<button className="p-2 text-gray-500 hover:text-gray-700">
<Image className="h-5 w-5" />
</button>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,47 @@
"use client";
import React from 'react';
import { BookOpen, Tag, Trash2 } from 'lucide-react';
import { PaneLayout } from '@/app/carnet/page';
interface NavigationProps {
onLayoutChange?: (layout: PaneLayout) => void;
}
export const Navigation: React.FC<NavigationProps> = ({ onLayoutChange }) => {
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-border">
<h2 className="text-menu-item font-medium text-text">Navigation</h2>
</div>
{/* Navigation Items */}
<div className="flex-1 overflow-y-auto">
<div
className="flex items-center p-3 hover:bg-contrast cursor-pointer"
onClick={() => onLayoutChange?.(PaneLayout.ItemSelection)}
>
<BookOpen className="w-5 h-5 mr-3 text-passive-1" />
<h3 className="text-menu-item font-medium text-text">All Notes</h3>
</div>
<div
className="flex items-center p-3 hover:bg-contrast cursor-pointer"
onClick={() => onLayoutChange?.(PaneLayout.TagSelection)}
>
<Tag className="w-5 h-5 mr-3 text-passive-1" />
<h3 className="text-menu-item font-medium text-text">Tags</h3>
</div>
<div
className="flex items-center p-3 hover:bg-contrast cursor-pointer"
onClick={() => onLayoutChange?.(PaneLayout.TableView)}
>
<Trash2 className="w-5 h-5 mr-3 text-passive-1" />
<h3 className="text-menu-item font-medium text-text">Trash</h3>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,69 @@
"use client";
import React, { useState } from 'react';
import { Plus } from 'lucide-react';
interface Note {
id: string;
title: string;
lastEdited: Date;
}
interface NotesViewProps {
onNoteSelect?: (note: Note) => void;
}
export const NotesView: React.FC<NotesViewProps> = ({ onNoteSelect }) => {
const [notes, setNotes] = useState<Note[]>([
{
id: '1',
title: 'Sample Note',
lastEdited: new Date(Date.now() - 2 * 60 * 60 * 1000) // 2 hours ago
}
]);
const handleNewNote = () => {
const newNote: Note = {
id: Date.now().toString(),
title: 'New Note',
lastEdited: new Date()
};
setNotes([newNote, ...notes]);
onNoteSelect?.(newNote);
};
return (
<div className="flex flex-col h-full">
{/* Header */}
<div className="flex items-center justify-between px-4 py-2 border-b border-border">
<h2 className="text-lg font-medium text-foreground">Notes</h2>
<button
className="p-2 rounded-full hover:bg-contrast"
onClick={handleNewNote}
>
<Plus className="w-5 h-5 text-passive-1" />
</button>
</div>
{/* Notes List */}
<div className="flex-1 overflow-y-auto">
{notes.map((note) => (
<div
key={note.id}
className="p-4 hover:bg-contrast cursor-pointer"
onClick={() => onNoteSelect?.(note)}
>
<div className="flex flex-col">
<span className="text-menu-item font-medium text-foreground">
{note.title}
</span>
<span className="text-xs text-passive-1">
Last edited {note.lastEdited.toLocaleTimeString()}
</span>
</div>
</div>
))}
</div>
</div>
);
};

View File

@ -0,0 +1,33 @@
"use client";
import React from 'react';
interface PanelResizerProps {
isDragging: boolean;
onDragStart: () => void;
onDragEnd: () => void;
onDrag: (e: MouseEvent) => void;
}
export const PanelResizer: React.FC<PanelResizerProps> = ({
isDragging,
onDragStart,
onDragEnd,
onDrag
}) => {
const handleMouseDown = (e: React.MouseEvent) => {
e.preventDefault();
onDragStart();
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', onDragEnd);
};
return (
<div
className={`w-1 bg-border cursor-col-resize hover:bg-info transition-colors ${
isDragging ? 'bg-info' : ''
}`}
onMouseDown={handleMouseDown}
/>
);
};

25
hooks/use-media-query.ts Normal file
View File

@ -0,0 +1,25 @@
"use client";
import { useState, useEffect } from 'react';
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
// Set initial value
setMatches(media.matches);
// Create event listener
const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
// Add the listener
media.addEventListener('change', listener);
// Clean up
return () => media.removeEventListener('change', listener);
}, [query]);
return matches;
}