/
DME
/
Notes
Обзор
Документация
Войти
/
DME
/
Notes
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/components/note-content.tsx
254 строки
8 KB
Karasique333
Commit
19 июн 2025, 15:09
19 июн 2025, 15:09
d96c829
Код
Авторство
О чём код?
'use client' import { Archive, ChevronLeft, Clock, Loader, RotateCcw, Tag, Trash2, } from 'lucide-react' import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import React, { useState } from 'react' import toast from 'react-hot-toast' import { Button } from './ui/button' import { Modal } from './ui/modal' import { Separator } from './ui/separator' import { Textarea } from './ui/textarea' import { type Note } from '@/server/db/schema' import { api } from '@/trpc/react' import ImageUploader from '@/components/ImageUploader' import Checklist from '@/components/Checklist' export default function NoteContent({ note }: { note: Note }) { const pathname = usePathname() const [content, setContent] = useState(note.content ?? '') const [originalContent, setOriginalContent] = useState(note.content ?? '') const [isEditing, setIsEditing] = useState(false) const [checklistItems, setChecklistItems] = useState( note.checklistItems?.map((item) => ({ content: item.content, completed: item.completed, })) || [] ) const [images, setImages] = useState( note.images?.map((img) => img.url) || [] ) const isArchivedPage = pathname.split('/')[1] === 'archived-notes' const utils = api.useUtils() const handleSave = () => { updateNote.mutate({ id: note.id, content, checklist: checklistItems, images, }) setOriginalContent(content) setIsEditing(false) } const handleCancel = () => { setContent(originalContent) setChecklistItems( note.checklistItems?.map((item) => ({ content: item.content, completed: item.completed, })) || [] ) setImages(note.images?.map((img) => img.url) || []) setIsEditing(false) } const updateNote = api.note.update.useMutation({ onSuccess: async () => { await utils.note.invalidate() toast.success('Заметка обновлена!') }, }) return ( <div className="flex h-full w-full flex-col"> <NoteTopHeader id={note.id} onCancel={handleCancel} onSave={handleSave} /> <Separator className="my-4 lg:hidden" /> <div className="flex flex-1 flex-col gap-4 pb-24"> <h2 className="text-2xl font-bold tracking-[-0.5px] text-neutral-950 dark:text-white"> {note.title} </h2> <div className="flex flex-col gap-3 text-neutral-700 dark:text-neutral-300"> <div className="flex items-center"> <div className="flex items-center gap-1.5"> <Tag className="h-4 w-4" /> <h6 className="min-w-[115px] text-xs font-normal">Теги</h6> </div> <p className="flex-1 text-xs font-normal capitalize"> {note?.tags.join(', ')} </p> </div> {isArchivedPage && ( <div className="flex"> <div className="flex gap-1.5"> <Loader className="h-4 w-4" /> <h6 className="w-[115px] text-xs font-normal">Статус</h6> </div> <p className="text-xs font-normal"> {note.status === 'archived' && 'Archived'} </p> </div> )} <div className="flex"> <div className="flex gap-1.5"> <Clock className="h-4 w-4" /> <h6 className="w-[115px] text-xs font-normal">Редактировано</h6> </div> <p className="text-xs font-normal"> {note.createdAt.toLocaleDateString()} </p> </div> </div> <Separator className="my-4 text-neutral-200 dark:text-neutral-800" /> <div className="h-full text-neutral-800 dark:text-neutral-100"> <Textarea value={content} onChange={(e) => { setContent(e.target.value) setIsEditing(true) }} placeholder="Start typing your note here..." className={`h-full resize-none text-sm font-normal border ${ isEditing ? 'border-blue-500' : 'border-transparent' }`} /> </div> <Checklist checklist={checklistItems} setChecklist={(updatedItems) => { setChecklistItems(updatedItems) setIsEditing(true) }} /> <ImageUploader images={images} setImages={(updatedImages) => { setImages(updatedImages) setIsEditing(true) }} /> <div className="mt-4 flex gap-4"> <Button onClick={handleSave} disabled={!isEditing || content.trim() === originalContent.trim()} > Сохранить </Button> <Button variant="secondary" onClick={handleCancel} disabled={!isEditing} > Отмена </Button> </div> </div> </div> ) } function NoteTopHeader({ id, onSave, onCancel, }: { id: string onSave: () => void onCancel: () => void }) { const pathname = usePathname() const router = useRouter() const rootPath = pathname.split('/')[1] const [showDeleteModal, setDeleteModal] = useState(false) const [showArchiveModal, setArchiveModal] = useState(false) const [showRestoreModal, setRestoreModal] = useState(false) const utils = api.useUtils() const updateNote = api.note.update.useMutation({ onSuccess: async () => { await utils.note.invalidate() toast.success('Заметка обновлена!') }, }) const deleteNote = api.note.delete.useMutation({ onSuccess: async () => { await utils.note.invalidate() router.push(`/${rootPath}`) }, }) const handleDeleteNote = () => { deleteNote.mutate({ id }) toast.success('Заметка удалена') setDeleteModal(false) } const handleArchiveNote = () => { updateNote.mutate({ id, status: 'archived' }) toast.success('Заметка архивирована') setArchiveModal(false) } const handleRestoreNote = () => { updateNote.mutate({ id, status: 'active' }) toast.success('Заметка восстановлена') setRestoreModal(false) } return ( <div className="flex items-center justify-between border-b border-neutral-200 bg-white pb-3 text-neutral-600 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-300 lg:hidden"> <Link className="inline-flex items-center gap-1" href={`/${rootPath}`}> <ChevronLeft className="h-4 w-4" /> Go Back </Link> <div className="flex items-center gap-4"> <button onClick={() => setDeleteModal(true)}> <Trash2 className="h-[18px] w-[18px]" /> </button> {rootPath === 'archived-notes' ? ( <button onClick={() => setRestoreModal(true)}> <RotateCcw className="h-[18px] w-[18px]" /> </button> ) : ( <button onClick={() => setArchiveModal(true)}> <Archive className="h-[18px] w-[18px]" /> </button> )} <button className="text-sm font-normal" onClick={onCancel}> Отмена </button> <button className="text-sm font-normal text-blue-500" onClick={onSave}> Сохранить </button> </div> {/* Модальные окна ниже... */} {/* ...можно оставить без изменений */} </div> ) }