Spaces:
Sleeping
Sleeping
| import { useEffect, useState } from "react"; | |
| import { Download, FolderOpen, Pencil, Trash2, Upload } from "lucide-react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| AlertDialog, | |
| AlertDialogAction, | |
| AlertDialogCancel, | |
| AlertDialogContent, | |
| AlertDialogDescription, | |
| AlertDialogFooter, | |
| AlertDialogHeader, | |
| AlertDialogTitle, | |
| } from "@/components/ui/alert-dialog"; | |
| import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; | |
| import { | |
| formatStamp, | |
| listProjects, | |
| removeProject, | |
| renameProject, | |
| type StoredProject, | |
| } from "@/lib/studio-storage"; | |
| type Props = { | |
| open: boolean; | |
| onOpenChange: (v: boolean) => void; | |
| onOpenProject: (p: StoredProject) => void; | |
| onImportFile: () => void; | |
| onSaveCurrent: () => void; | |
| ownerId: string; | |
| }; | |
| export default function ProjectsDialog({ open, onOpenChange, onOpenProject, onImportFile, onSaveCurrent, ownerId }: Props) { | |
| const [items, setItems] = useState<StoredProject[]>([]); | |
| const [toDelete, setToDelete] = useState<StoredProject | null>(null); | |
| useEffect(() => { | |
| if (open) setItems(listProjects(ownerId)); | |
| }, [open, ownerId]); | |
| const download = (p: StoredProject) => { | |
| const blob = new Blob([JSON.stringify(p.data, null, 2)], { type: "application/json" }); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = `${p.title || "proekt"}.json`; | |
| a.click(); | |
| URL.revokeObjectURL(a.href); | |
| }; | |
| const rename = (p: StoredProject) => { | |
| const title = window.prompt("Новое название проекта", p.title); | |
| if (title && title.trim()) setItems(renameProject(p.id, title.trim(), ownerId)); | |
| }; | |
| return ( | |
| <> | |
| <Dialog open={open} onOpenChange={onOpenChange}> | |
| <DialogContent className="max-h-[85vh] max-w-4xl overflow-y-auto"> | |
| <DialogHeader> | |
| <DialogTitle className="font-display text-2xl">Мои проекты</DialogTitle> | |
| <DialogDescription>Схемы, сохранённые в памяти этого браузера.</DialogDescription> | |
| </DialogHeader> | |
| <div className="flex flex-wrap gap-2"> | |
| <Button size="lg" className="h-12 gap-2" onClick={onSaveCurrent} title="Сохранить текущую схему в проекты"> | |
| <FolderOpen className="h-5 w-5" /> Сохранить текущую схему | |
| </Button> | |
| <Button | |
| variant="outline" | |
| size="lg" | |
| className="h-12 gap-2" | |
| onClick={onImportFile} | |
| title="Загрузить проект из файла .json" | |
| > | |
| <Upload className="h-5 w-5" /> Загрузить .json | |
| </Button> | |
| </div> | |
| {items.length === 0 ? ( | |
| <p className="py-8 text-center text-sm text-muted-foreground"> | |
| Пока нет сохранённых проектов. Создайте схему и нажмите «Сохранить текущую схему». | |
| </p> | |
| ) : ( | |
| <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> | |
| {items.map((p) => ( | |
| <article key={p.id} className="overflow-hidden rounded-2xl border border-border bg-card"> | |
| <img | |
| src={p.thumb} | |
| alt={`Превью схемы «${p.title}»`} | |
| className="aspect-[4/3] w-full bg-secondary object-cover" | |
| /> | |
| <div className="space-y-2 p-3"> | |
| <h3 className="truncate font-medium">{p.title}</h3> | |
| <p className="text-xs text-muted-foreground"> | |
| {formatStamp(p.updatedAt)} · элементов: {p.elements} | |
| </p> | |
| <div className="grid grid-cols-2 gap-2"> | |
| <Button size="sm" className="h-10 gap-1" onClick={() => onOpenProject(p)} title="Открыть проект"> | |
| <FolderOpen className="h-4 w-4" /> Открыть | |
| </Button> | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| className="h-10 gap-1" | |
| onClick={() => rename(p)} | |
| title="Переименовать проект" | |
| > | |
| <Pencil className="h-4 w-4" /> Переименовать | |
| </Button> | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| className="h-10 gap-1" | |
| onClick={() => download(p)} | |
| title="Скачать проект файлом .json" | |
| > | |
| <Download className="h-4 w-4" /> Скачать | |
| </Button> | |
| <Button | |
| variant="destructive" | |
| size="sm" | |
| className="h-10 gap-1" | |
| onClick={() => setToDelete(p)} | |
| title="Удалить проект" | |
| > | |
| <Trash2 className="h-4 w-4" /> Удалить | |
| </Button> | |
| </div> | |
| </div> | |
| </article> | |
| ))} | |
| </div> | |
| )} | |
| </DialogContent> | |
| </Dialog> | |
| <AlertDialog open={!!toDelete} onOpenChange={(v) => !v && setToDelete(null)}> | |
| <AlertDialogContent> | |
| <AlertDialogHeader> | |
| <AlertDialogTitle>Удалить проект?</AlertDialogTitle> | |
| <AlertDialogDescription> | |
| Вы уверены, что хотите удалить проект «{toDelete?.title}»? Действие нельзя отменить. | |
| </AlertDialogDescription> | |
| </AlertDialogHeader> | |
| <AlertDialogFooter> | |
| <AlertDialogCancel>Отмена</AlertDialogCancel> | |
| <AlertDialogAction | |
| onClick={() => { | |
| if (toDelete) setItems(removeProject(toDelete.id, ownerId)); | |
| setToDelete(null); | |
| }} | |
| > | |
| Удалить | |
| </AlertDialogAction> | |
| </AlertDialogFooter> | |
| </AlertDialogContent> | |
| </AlertDialog> | |
| </> | |
| ); | |
| } | |