diff --git a/server/index.js b/server/index.js index 7af1062..6d8ef26 100644 --- a/server/index.js +++ b/server/index.js @@ -75,7 +75,20 @@ function readDeskState() { try { if (fs.existsSync(DESK_FILE)) { const raw = fs.readFileSync(DESK_FILE, 'utf-8'); - return JSON.parse(raw); + const desk = JSON.parse(raw); + if (desk && (!desk.boards || typeof desk.boards !== 'object')) { + const currentBoard = desk.activeBoard || (desk.settings && desk.settings.deskTexture) || 'oak'; + desk.boards = { + [currentBoard]: { + cards: desk.cards || [], + props: desk.props || [], + strokes: desk.strokes || [], + settings: desk.settings || {} + } + }; + desk.activeBoard = currentBoard; + } + return desk; } } catch (err) { console.error('Error reading desk state:', err); @@ -142,17 +155,28 @@ app.get('/api/desk', (_req, res) => { // Save desk state app.post('/api/desk', (req, res) => { - const { cards, props, strokes, settings, originClientId } = req.body; + const { cards, props, strokes, settings, activeBoard, boards, originClientId } = req.body; if (!Array.isArray(cards)) { return res.status(400).json({ success: false, error: 'Invalid desk state: cards required' }); } + const currentBoard = activeBoard || (settings && settings.deskTexture) || 'oak'; + const updatedBoards = boards && typeof boards === 'object' ? { ...boards } : {}; + updatedBoards[currentBoard] = { + cards, + props: Array.isArray(props) ? props : [], + strokes: Array.isArray(strokes) ? strokes : [], + settings: settings || {} + }; + const newState = { cards, props: Array.isArray(props) ? props : [], strokes: Array.isArray(strokes) ? strokes : [], settings: settings || {}, + activeBoard: currentBoard, + boards: updatedBoards, updatedAt: Date.now() }; diff --git a/src/App.tsx b/src/App.tsx index 290af1d..967f89f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { CardItem, DeskProp, DeskSettings, InkStroke, SyncStatus } from './types'; +import { CardItem, DeskProp, DeskSettings, InkStroke, SyncStatus, DeskTexture, BoardsMap } from './types'; import { generateProceduralCards, SAMPLE_PRESETS } from './utils/proceduralCards'; import { soundEngine } from './utils/sound'; import { TopBar } from './components/TopBar'; @@ -8,19 +8,10 @@ import { DeskBackground } from './components/DeskTextures'; import { PaperCard } from './components/PaperCard'; import { DeskPropsLayer } from './components/DeskProps'; import { PenLayer } from './components/PenLayer'; +import { EvidenceStringsLayer } from './components/EvidenceStringsLayer'; import { AddCardModal, EditCardModal } from './components/AddCardModal'; import { PortfolioModal } from './components/PortfolioModal'; - -const INITIAL_PROPS: DeskProp[] = [ - { id: 'prop-pencil-1', type: 'pencil', x: 120, y: 160, rotation: 38, zIndex: 12 }, - { id: 'prop-pen-1', type: 'pen', x: 890, y: 170, rotation: -28, zIndex: 15 }, - { id: 'prop-pencil-2', type: 'pencil', x: 230, y: 720, rotation: -42, zIndex: 10 }, - { id: 'prop-pen-2', type: 'pen', x: 440, y: 880, rotation: 92, zIndex: 14 }, - { id: 'prop-tape-1', type: 'tape-roll', x: 410, y: 120, rotation: 12, zIndex: 9 }, - { id: 'prop-clip-1', type: 'paperclip', x: 640, y: 760, rotation: 55, zIndex: 8 }, - { id: 'prop-clip-2', type: 'paperclip', x: 1190, y: 920, rotation: -35, zIndex: 8 }, - { id: 'prop-pin-1', type: 'pin', x: 1210, y: 130, rotation: 0, zIndex: 16 } -]; +import { getDefaultBoardData, INITIAL_PROPS } from './utils/boardPresets'; export const App: React.FC = () => { // Desk Settings matching the original Paper Desk UI @@ -37,6 +28,7 @@ export const App: React.FC = () => { penMode: false, soundEnabled: true, isLocked: false, + showEvidenceStrings: false, activeFilter: 'all', zoom: typeof window !== 'undefined' && window.innerWidth < 768 ? 0.65 : 1.0 }); @@ -45,6 +37,7 @@ export const App: React.FC = () => { const [propsList, setPropsList] = useState(INITIAL_PROPS); const [strokes, setStrokes] = useState([]); const [maxZIndex, setMaxZIndex] = useState(50); + const boardsRef = useRef({}); // Modals state const [isAddOpen, setIsAddOpen] = useState(false); @@ -71,7 +64,9 @@ export const App: React.FC = () => { currentProps: DeskProp[], currentStrokes: InkStroke[], currentSettings: DeskSettings, - immediate = false + immediate = false, + targetBoard?: DeskTexture, + targetBoards?: BoardsMap ) => { if (!isLoadedRef.current) return; @@ -79,6 +74,18 @@ export const App: React.FC = () => { clearTimeout(saveTimeoutRef.current); } + const currentBoard = targetBoard || currentSettings.deskTexture || 'oak'; + const updatedBoards = targetBoards || { + ...boardsRef.current, + [currentBoard]: { + cards: currentCards, + props: currentProps, + strokes: currentStrokes, + settings: currentSettings + } + }; + boardsRef.current = updatedBoards; + const doSave = () => { setSyncStatus('syncing'); fetch('/api/desk', { @@ -88,11 +95,14 @@ export const App: React.FC = () => { cards: currentCards, props: currentProps, strokes: currentStrokes, + activeBoard: currentBoard, + boards: updatedBoards, settings: { - deskTexture: currentSettings.deskTexture, + deskTexture: currentBoard, lightAngle: currentSettings.lightAngle, grain: currentSettings.grain, - layoutMode: currentSettings.layoutMode + layoutMode: currentSettings.layoutMode, + showEvidenceStrings: currentSettings.showEvidenceStrings }, originClientId: clientIdRef.current }) @@ -122,24 +132,44 @@ export const App: React.FC = () => { fetch('/api/desk') .then((res) => res.json()) .then((data) => { - if (data.success && data.desk && Array.isArray(data.desk.cards) && data.desk.cards.length > 0) { - setCards(data.desk.cards); - if (Array.isArray(data.desk.props) && data.desk.props.length > 0) { - setPropsList(data.desk.props); + if (data.success && data.desk) { + const desk = data.desk; + const loadedBoards: BoardsMap = desk.boards && typeof desk.boards === 'object' ? desk.boards : {}; + const activeBoard: DeskTexture = desk.activeBoard || desk.settings?.deskTexture || 'oak'; + + if (!loadedBoards[activeBoard] && Array.isArray(desk.cards) && desk.cards.length > 0) { + loadedBoards[activeBoard] = { + cards: desk.cards, + props: desk.props || INITIAL_PROPS, + strokes: desk.strokes || [], + settings: desk.settings || {} + }; } - if (Array.isArray(data.desk.strokes)) { - setStrokes(data.desk.strokes); + + boardsRef.current = loadedBoards; + + let currentBoardData = loadedBoards[activeBoard]; + if (!currentBoardData || !Array.isArray(currentBoardData.cards) || currentBoardData.cards.length === 0) { + currentBoardData = activeBoard === 'oak' + ? { cards: generateProceduralCards(window.innerWidth < 768 ? 9 : 20, window.innerWidth, window.innerHeight, 0.55, 'mixed'), props: INITIAL_PROPS, strokes: [] } + : getDefaultBoardData(activeBoard, window.innerWidth, window.innerHeight); + loadedBoards[activeBoard] = currentBoardData; } - if (data.desk.settings) { - setSettings((s) => ({ - ...s, - deskTexture: data.desk.settings.deskTexture ?? s.deskTexture, - lightAngle: data.desk.settings.lightAngle ?? s.lightAngle, - grain: data.desk.settings.grain ?? s.grain, - layoutMode: data.desk.settings.layoutMode ?? s.layoutMode - })); - } - const maxZ = Math.max(15, ...data.desk.cards.map((c: CardItem) => c.zIndex || 1)); + + setCards(currentBoardData.cards || []); + if (currentBoardData.props) setPropsList(currentBoardData.props); + if (currentBoardData.strokes) setStrokes(currentBoardData.strokes); + + setSettings((s) => ({ + ...s, + deskTexture: activeBoard, + lightAngle: desk.settings?.lightAngle ?? s.lightAngle, + grain: desk.settings?.grain ?? s.grain, + layoutMode: desk.settings?.layoutMode ?? s.layoutMode, + showEvidenceStrings: activeBoard === 'evidence-board' ? true : (desk.settings?.showEvidenceStrings ?? false) + })); + + const maxZ = Math.max(15, ...(currentBoardData.cards || []).map((c: CardItem) => c.zIndex || 1)); setMaxZIndex(maxZ + 5); setSyncStatus('synced'); isLoadedRef.current = true; @@ -148,7 +178,7 @@ export const App: React.FC = () => { const w = typeof window !== 'undefined' ? window.innerWidth : 1400; const h = typeof window !== 'undefined' ? window.innerHeight : 900; const isMobile = w < 768; - const initialCount = isMobile ? 9 : 22; + const initialCount = isMobile ? 9 : 20; const initialCards = generateProceduralCards( initialCount, @@ -158,11 +188,15 @@ export const App: React.FC = () => { settings.fontMode ); setCards(initialCards); + const initialBoards: BoardsMap = { + oak: { cards: initialCards, props: INITIAL_PROPS, strokes: [] } + }; + boardsRef.current = initialBoards; setMaxZIndex(initialCards.length + 20); setSyncStatus('synced'); isLoadedRef.current = true; - saveToServer(initialCards, INITIAL_PROPS, [], settings, true); + saveToServer(initialCards, INITIAL_PROPS, [], settings, true, 'oak', initialBoards); } }) .catch((err) => { @@ -186,6 +220,10 @@ export const App: React.FC = () => { const payload = JSON.parse(e.data); if (payload.type === 'desk_updated' && payload.originClientId !== clientIdRef.current) { if (payload.desk) { + if (payload.desk.boards && typeof payload.desk.boards === 'object') { + boardsRef.current = payload.desk.boards; + } + const activeBoard = payload.desk.activeBoard || (payload.desk.settings && payload.desk.settings.deskTexture) || 'oak'; if (Array.isArray(payload.desk.cards)) { setCards(payload.desk.cards); const maxZ = Math.max(15, ...payload.desk.cards.map((c: CardItem) => c.zIndex || 1)); @@ -196,10 +234,11 @@ export const App: React.FC = () => { if (payload.desk.settings) { setSettings((prev) => ({ ...prev, - deskTexture: payload.desk.settings.deskTexture ?? prev.deskTexture, + deskTexture: activeBoard, lightAngle: payload.desk.settings.lightAngle ?? prev.lightAngle, grain: payload.desk.settings.grain ?? prev.grain, - layoutMode: payload.desk.settings.layoutMode ?? prev.layoutMode + layoutMode: payload.desk.settings.layoutMode ?? prev.layoutMode, + showEvidenceStrings: payload.desk.settings.showEvidenceStrings ?? prev.showEvidenceStrings })); } setSyncStatus('synced'); @@ -221,11 +260,83 @@ export const App: React.FC = () => { }; }, []); - // Update card count when slider changes + // Update desk settings or switch unique workspace board const updateSettings = (newSettings: Partial) => { setSettings((prev) => { const updated = { ...prev, ...newSettings }; + // 1. Switch between 8 unique workspace boards + if (newSettings.deskTexture !== undefined && newSettings.deskTexture !== prev.deskTexture) { + const prevDesk = prev.deskTexture; + const nextDesk = newSettings.deskTexture; + + // Snapshot current board state + const updatedBoards = { + ...boardsRef.current, + [prevDesk]: { + cards, + props: propsList, + strokes, + settings: prev + } + }; + + // Load or initialize target board state + let nextBoardData = updatedBoards[nextDesk]; + if (!nextBoardData || !Array.isArray(nextBoardData.cards) || nextBoardData.cards.length === 0) { + if (nextDesk === 'oak') { + const w = window.innerWidth; + const h = window.innerHeight; + const seed = generateProceduralCards(w < 768 ? 9 : 20, w, h, updated.attachChance, updated.fontMode); + nextBoardData = { cards: seed, props: INITIAL_PROPS, strokes: [] }; + } else { + nextBoardData = getDefaultBoardData(nextDesk, window.innerWidth, window.innerHeight); + } + updatedBoards[nextDesk] = nextBoardData; + } + + boardsRef.current = updatedBoards; + setCards(nextBoardData.cards || []); + setPropsList(nextBoardData.props || INITIAL_PROPS); + setStrokes(nextBoardData.strokes || []); + + const nextEvidenceStrings = nextDesk === 'evidence-board' ? true : (newSettings.showEvidenceStrings ?? false); + + updated.deskTexture = nextDesk; + updated.showEvidenceStrings = nextEvidenceStrings; + + saveToServer( + nextBoardData.cards || [], + nextBoardData.props || INITIAL_PROPS, + nextBoardData.strokes || [], + updated, + true, + nextDesk, + updatedBoards + ); + + soundEngine.rustle(updated.soundEnabled); + } + + // 2. Real-time typography mode update across desk cards + if (newSettings.fontMode !== undefined && newSettings.fontMode !== prev.fontMode) { + const mode = newSettings.fontMode; + setCards((prevCards) => { + const next = prevCards.map((c) => { + const fonts = mode === 'handwritten' + ? ['font-caveat', 'font-reenie', 'font-indie', 'font-shadows'] + : mode === 'typed' + ? ['font-courier', 'font-special', 'font-mono'] + : ['font-caveat', 'font-courier', 'font-reenie', 'font-special', 'font-indie', 'font-mono']; + const chosen = fonts[Math.floor(Math.random() * fonts.length)]; + return { ...c, fontFamily: chosen }; + }); + saveToServer(next, propsList, strokes, updated, false); + return next; + }); + } + + // 3. Card density adjustment if (newSettings.cardCount !== undefined && newSettings.cardCount !== prev.cardCount) { const w = window.innerWidth; const h = window.innerHeight; @@ -238,6 +349,7 @@ export const App: React.FC = () => { } } + // 4. Attach chance adjustment if (newSettings.attachChance !== undefined && newSettings.attachChance !== prev.attachChance) { setCards((c) => c.map((card) => { @@ -761,6 +873,12 @@ export const App: React.FC = () => { /> ))} + {/* Evidence Board Red Yarn Connections Layer */} + + {/* 4. Stationery Props Layer */} {settings.showProps && ( { id: `card-custom-${Date.now()}`, type: newCard.type || 'sticky', title: newCard.title, + subtitle: newCard.subtitle, content: newCard.content || '', + date: newCard.date, + tagNumber: newCard.tagNumber, imageUrl: newCard.imageUrl, url: newCard.url, faviconUrl: newCard.faviconUrl, photoCaption: newCard.photoCaption, + stamp: newCard.stamp, + stampAngle: newCard.stampAngle, + graphType: newCard.graphType, + todoItems: newCard.todoItems, + receiptItems: newCard.receiptItems, color: newCard.color || '#FFF9A6', - x: w / 2 - 90 + (Math.random() - 0.5) * 60, - y: h / 2 - 90 + (Math.random() - 0.5) * 60, + x: w / 2 - (newCard.width || 180) / 2 + (Math.random() - 0.5) * 60, + y: h / 2 - (newCard.height || 180) / 2 + (Math.random() - 0.5) * 60, vx: 0, vy: 0, rotation: (Math.random() - 0.5) * 8, diff --git a/src/components/AddCardModal.tsx b/src/components/AddCardModal.tsx index 3b39445..f4298bf 100644 --- a/src/components/AddCardModal.tsx +++ b/src/components/AddCardModal.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { CardItem, CardType, AttachType } from '../types'; -import { X } from 'lucide-react'; +import { CardItem, CardType, AttachType, StampType } from '../types'; +import { X, Plus, Trash2 } from 'lucide-react'; interface AddCardModalProps { isOpen: boolean; @@ -8,9 +8,24 @@ interface AddCardModalProps { onAdd: (card: Partial) => void; } +const CARD_TYPES: { id: CardType; label: string; icon: string }[] = [ + { id: 'sticky', label: 'Sticky Note', icon: '📝' }, + { id: 'polaroid', label: 'Photo', icon: '📷' }, + { id: 'bookmark', label: 'Bookmark', icon: '🌐' }, + { id: 'receipt', label: 'Receipt', icon: '🧾' }, + { id: 'graph', label: 'Graph / Sketch', icon: '📐' }, + { id: 'checklist', label: 'Checklist', icon: '☑️' }, + { id: 'ticket', label: 'Ticket / Pass', icon: '🎟️' }, + { id: 'tag', label: 'Luggage Tag', icon: '🏷️' }, + { id: 'torn', label: 'Torn Memo', icon: '📄' }, + { id: 'business', label: 'Cardstock', icon: '📇' }, + { id: 'moodboard', label: 'Moodboard', icon: '🎨' } +]; + export const AddCardModal: React.FC = ({ isOpen, onClose, onAdd }) => { const [type, setType] = useState('sticky'); const [title, setTitle] = useState(''); + const [subtitle, setSubtitle] = useState(''); const [content, setContent] = useState(''); const [imageUrl, setImageUrl] = useState(''); const [bookmarkUrl, setBookmarkUrl] = useState(''); @@ -18,6 +33,29 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA const [color, setColor] = useState('#FFF9A6'); const [attachType, setAttachType] = useState('washi'); const [fontFamily, setFontFamily] = useState('font-caveat'); + const [stamp, setStamp] = useState('none'); + const [tagNumber, setTagNumber] = useState(''); + const [dateStr, setDateStr] = useState(''); + + // Graph state + const [graphType, setGraphType] = useState<'barchart' | 'flowchart' | 'curve'>('curve'); + + // Checklist state + const [todoItems, setTodoItems] = useState<{ text: string; done: boolean }[]>([ + { text: 'Review project layout', done: true }, + { text: 'Add tactile stickers', done: false } + ]); + const [newTodoText, setNewTodoText] = useState(''); + + // Receipt state + const [receiptItems, setReceiptItems] = useState<{ name: string; price: string }[]>([ + { name: 'FLAT WHITE', price: '4.75' }, + { name: 'CROISSANT', price: '3.50' }, + { name: 'SUBTOTAL', price: '8.25' }, + { name: 'TOTAL', price: '$8.99' } + ]); + const [newReceiptName, setNewReceiptName] = useState(''); + const [newReceiptPrice, setNewReceiptPrice] = useState(''); const [isUploading, setIsUploading] = useState(false); const [isFetchingMeta, setIsFetchingMeta] = useState(false); @@ -50,6 +88,21 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA } }; + const handleAddTodo = () => { + if (newTodoText.trim()) { + setTodoItems([...todoItems, { text: newTodoText.trim(), done: false }]); + setNewTodoText(''); + } + }; + + const handleAddReceiptItem = () => { + if (newReceiptName.trim() && newReceiptPrice.trim()) { + setReceiptItems([...receiptItems, { name: newReceiptName.trim().toUpperCase(), price: newReceiptPrice.trim() }]); + setNewReceiptName(''); + setNewReceiptPrice(''); + } + }; + const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (isUploading || isFetchingMeta) return; @@ -64,22 +117,43 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA ? (imageUrl.trim() || (formattedBookmarkUrl ? `https://s0.wp.com/mshots/v1/${encodeURIComponent(formattedBookmarkUrl)}?w=600` : undefined)) : undefined; + let defaultWidth = 180; + let defaultHeight = 180; + let defaultColor = color; + + if (type === 'polaroid') { defaultWidth = 175; defaultHeight = 220; defaultColor = '#FFFFFF'; } + else if (type === 'bookmark') { defaultWidth = 210; defaultHeight = 250; defaultColor = '#FAF8F5'; } + else if (type === 'tag') { defaultWidth = 140; defaultHeight = 220; defaultColor = '#CBB296'; } + else if (type === 'receipt') { defaultWidth = 160; defaultHeight = 310; defaultColor = '#F9F8F4'; } + else if (type === 'graph') { defaultWidth = 220; defaultHeight = 195; defaultColor = '#F4F6F9'; } + else if (type === 'checklist') { defaultWidth = 200; defaultHeight = 240; defaultColor = '#FCFAF2'; } + else if (type === 'ticket') { defaultWidth = 200; defaultHeight = 165; defaultColor = '#F4EDE2'; } + onAdd({ type, title: title.trim() || (type === 'bookmark' && formattedBookmarkUrl ? new URL(formattedBookmarkUrl).hostname : undefined), + subtitle: subtitle.trim() || undefined, content: content.trim(), + date: dateStr.trim() || undefined, + tagNumber: tagNumber.trim() || undefined, imageUrl: effectiveImageUrl, url: type === 'bookmark' ? formattedBookmarkUrl : undefined, faviconUrl: type === 'bookmark' ? (faviconUrl || (formattedBookmarkUrl ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(new URL(formattedBookmarkUrl).hostname)}&sz=128` : undefined)) : undefined, photoCaption: type === 'polaroid' ? (title.trim() || content.trim()) : undefined, - color: type === 'bookmark' ? '#FAF8F5' : color, + color: defaultColor, attachType: type === 'bookmark' ? 'washi' : attachType, fontFamily, - width: type === 'polaroid' ? 175 : type === 'tag' ? 140 : type === 'bookmark' ? 210 : 180, - height: type === 'polaroid' ? 220 : type === 'tag' ? 220 : type === 'bookmark' ? 250 : 180 + stamp: stamp !== 'none' ? stamp : undefined, + stampAngle: stamp !== 'none' ? Math.floor(Math.random() * 20 - 10) : undefined, + graphType: type === 'graph' ? graphType : undefined, + todoItems: type === 'checklist' ? todoItems : undefined, + receiptItems: type === 'receipt' ? receiptItems : undefined, + width: defaultWidth, + height: defaultHeight }); onClose(); setTitle(''); + setSubtitle(''); setContent(''); setImageUrl(''); setBookmarkUrl(''); @@ -90,7 +164,6 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA const file = e.target.files?.[0]; if (!file) return; - // Immediate preview const reader = new FileReader(); reader.onload = (ev) => { if (typeof ev.target?.result === 'string') { @@ -99,7 +172,6 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA }; reader.readAsDataURL(file); - // Upload to server setIsUploading(true); const formData = new FormData(); formData.append('file', file); @@ -122,40 +194,41 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA }; return ( -
-
+
+

Add New Paper Card

-
- {/* Card Type */} + {/* Card Type Grid */}
- -
- {(['sticky', 'polaroid', 'bookmark', 'tag', 'torn', 'business', 'moodboard'] as CardType[]).map((t) => ( + +
+ {CARD_TYPES.map((t) => ( ))}
- {/* Bookmark URL input & preview fetcher */} + {/* Bookmark URL input */} {type === 'bookmark' && (
@@ -171,135 +244,211 @@ export const AddCardModal: React.FC = ({ isOpen, onClose, onA type="text" value={bookmarkUrl} onChange={(e) => setBookmarkUrl(e.target.value)} - onBlur={() => { - if (bookmarkUrl.trim()) handleFetchBookmarkMeta(bookmarkUrl.trim()); - }} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.preventDefault(); - if (bookmarkUrl.trim()) handleFetchBookmarkMeta(bookmarkUrl.trim()); - } - }} + onBlur={() => { if (bookmarkUrl.trim()) handleFetchBookmarkMeta(bookmarkUrl.trim()); }} placeholder="e.g. https://news.ycombinator.com" - className="flex-1 bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500 font-mono text-[11px]" + className="flex-1 bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 font-mono text-[11px]" />
- - {/* Bookmark thumbnail */} - {imageUrl && ( -
-
- Thumbnail -
-
- ✓ Snapshot Ready - {imageUrl} -
-
- )}
)} - {/* Title */} -
- - setTitle(e.target.value)} - placeholder="e.g. Brainstorm note, Project Retro" - className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500" - /> + {/* Graph Type Selector */} + {type === 'graph' && ( +
+ +
+ {[ + { id: 'curve', label: 'Wave / Vector' }, + { id: 'barchart', label: 'Bar Chart' }, + { id: 'flowchart', label: 'Flowchart' } + ].map((g) => ( + + ))} +
+
+ )} + + {/* Checklist Task Builder */} + {type === 'checklist' && ( +
+ +
+ {todoItems.map((item, idx) => ( +
+ {item.text} + +
+ ))} +
+
+ setNewTodoText(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddTodo(); } }} + placeholder="Add a new task..." + className="flex-1 bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1 text-neutral-100" + /> + +
+
+ )} + + {/* Receipt Items Builder */} + {type === 'receipt' && ( +
+ +
+ {receiptItems.map((it, idx) => ( +
+ {it.name} +
+ {it.price} + +
+
+ ))} +
+
+ setNewReceiptName(e.target.value)} + placeholder="ITEM" + className="flex-1 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 uppercase text-[11px]" + /> + setNewReceiptPrice(e.target.value)} + placeholder="$0.00" + className="w-20 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-[11px]" + /> + +
+
+ )} + + {/* Title & Subtitle */} +
+
+ + setTitle(e.target.value)} + placeholder={type === 'receipt' ? 'STORE NAME' : type === 'ticket' ? 'EVENT / PASS' : 'Note Title'} + className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500" + /> +
+
+ + setSubtitle(e.target.value)} + placeholder={type === 'ticket' ? 'SEAT 14A · VIP' : type === 'receipt' ? 'LOCATION' : 'Subtitle or Date'} + className="w-full bg-neutral-800 border border-neutral-700 rounded px-2.5 py-1.5 text-neutral-100 focus:outline-none focus:border-amber-500" + /> +
- {/* Content */} -
- -