diff --git a/app/create-news/page.tsx b/app/create-news/page.tsx
index f194b09..cc14bd3 100644
--- a/app/create-news/page.tsx
+++ b/app/create-news/page.tsx
@@ -248,15 +248,15 @@ const CreateNewsPage = () => {
- {/* Resumo (Lead) */}
-
diff --git a/app/manage-category/components/CategoryTree.tsx b/app/manage-category/components/CategoryTree.tsx
new file mode 100644
index 0000000..d91d863
--- /dev/null
+++ b/app/manage-category/components/CategoryTree.tsx
@@ -0,0 +1,264 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import {
+ FolderTree,
+ Edit3,
+ Trash2,
+ Plus,
+ ChevronRight,
+ ChevronDown,
+} from "lucide-react";
+import { getTree, getFlat, updateCategory, createCategory, deleteCategory } from "@/lib/categories.api";
+
+/* ================= TYPES ================= */
+interface Category {
+ id: string;
+ name: string;
+ slug: string;
+ parentId?: string | null;
+ children?: Category[];
+}
+
+/* ================= API ================= */
+
+/* ================= UTIL ================= */
+function slugify(text: string) {
+ return text
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, "-")
+ .replace(/[^a-z0-9-]/g, "");
+}
+
+/* ================= PAGE ================= */
+export default function CategoriesPage() {
+ const [tree, setTree] = useState
([]);
+ const [flat, setFlat] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const [modalOpen, setModalOpen] = useState(false);
+
+ const [form, setForm] = useState({
+ id: null as string | null,
+ name: "",
+ slug: "",
+ parentId: null as string | null,
+ });
+
+ /* ================= LOAD ================= */
+ async function load() {
+ setLoading(true);
+ try {
+ const [t, f] = await Promise.all([getTree(), getFlat()]);
+ setTree(t);
+ setFlat(f);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ useEffect(() => {
+ load();
+ }, []);
+
+ /* ================= CRUD ================= */
+ async function save() {
+ const payload = {
+ name: form.name,
+ slug: form.slug || slugify(form.name),
+ parentId: form.parentId,
+ };
+
+ if (form.id) {
+ await updateCategory(form.id, payload);
+ } else {
+ await createCategory(payload);
+ }
+
+ closeModal();
+ load();
+ }
+
+ async function remove(id: string) {
+ if (!confirm("Delete this category?")) return;
+ await deleteCategory(id);
+ load();
+ }
+
+ /* ================= MODAL ================= */
+ function openCreate(parentId?: string) {
+ setForm({
+ id: null,
+ name: "",
+ slug: "",
+ parentId: parentId || null,
+ });
+ setModalOpen(true);
+ }
+
+ function openEdit(cat: Category) {
+ setForm({
+ id: cat.id,
+ name: cat.name,
+ slug: cat.slug,
+ parentId: cat.parentId || null,
+ });
+ setModalOpen(true);
+ }
+
+ function closeModal() {
+ setModalOpen(false);
+ setForm({ id: null, name: "", slug: "", parentId: null });
+ }
+
+ /* ================= TREE ================= */
+ function TreeNode({
+ node,
+ level = 0,
+ }: {
+ node: Category;
+ level?: number;
+ }) {
+ const [open, setOpen] = useState(true);
+
+ return (
+
+
+ {/* NODE */}
+
+
+
+
+
+
+
+
+ openEdit(node)}
+ className="text-sm font-medium cursor-pointer hover:text-blue-600"
+ >
+ {node.name}
+
+
+
+ {/* ACTIONS */}
+
+
+
+
+
+
+
+
+
+
+ {/* CHILDREN */}
+ {open &&
+ node.children?.map((child) => (
+
+ ))}
+
+ );
+ }
+
+ /* ================= UI ================= */
+ return (
+
+
+ {/* HEADER */}
+
+
+ Category Manager
+
+
+
+
+
+ {/* TREE */}
+
+ {loading ? (
+
Loading...
+ ) : (
+ tree.map((node) => (
+
+ ))
+ )}
+
+
+ {/* MODAL */}
+ {modalOpen && (
+
+
+
+
+
+ {form.id ? "Edit Category" : "Create Category"}
+
+
+
+ setForm({
+ ...form,
+ name: e.target.value,
+ slug: slugify(e.target.value),
+ })
+ }
+ />
+
+
+ setForm({ ...form, slug: e.target.value })
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/app/manage-category/page.tsx b/app/manage-category/page.tsx
new file mode 100644
index 0000000..6cab277
--- /dev/null
+++ b/app/manage-category/page.tsx
@@ -0,0 +1,343 @@
+"use client";
+
+import React, { useEffect, useState } from "react";
+import Image from "next/image";
+import {
+ LayoutDashboard,
+ Newspaper,
+ Users,
+ BarChart3,
+ Settings,
+ HelpCircle,
+ Tag,
+ FolderTree,
+ Edit3,
+ Trash2,
+ Plus,
+ ChevronRight,
+ ChevronDown,
+} from "lucide-react";
+import { createCategory, deleteCategory, getFlat, getTree, updateCategory } from "@/lib/categories.api";
+import { slugify } from "@/lib/slug";
+
+/* ================= TYPES ================= */
+interface Category {
+ id: string;
+ name: string;
+ slug: string;
+ parentId?: string | null;
+ children?: Category[];
+}
+
+/* ================= PAGE ================= */
+export default function CategoriesPage() {
+ const [tree, setTree] = useState([]);
+ const [flat, setFlat] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const [modalOpen, setModalOpen] = useState(false);
+
+ const [form, setForm] = useState({
+ id: null as string | null,
+ name: "",
+ slug: "",
+ parentId: null as string | null,
+ });
+
+ /* ================= LOAD ================= */
+ async function load() {
+ setLoading(true);
+ try {
+ const [t, f] = await Promise.all([getTree(), getFlat()]);
+ setTree(t);
+ setFlat(f);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ useEffect(() => {
+ load();
+ }, []);
+
+ /* ================= CRUD ================= */
+ async function save() {
+ const payload = {
+ name: form.name,
+ slug: form.slug || slugify(form.name),
+ parentId: form.parentId,
+ };
+
+ if (form.id) {
+ await updateCategory(form.id, payload);
+ } else {
+ await createCategory(payload);
+ }
+
+ closeModal();
+ load();
+ }
+
+ async function remove(id: string) {
+ if (!confirm("Delete this category?")) return;
+ await deleteCategory(id);
+ load();
+ }
+
+ /* ================= MODAL ================= */
+ function openCreate(parentId?: string) {
+ setForm({
+ id: null,
+ name: "",
+ slug: "",
+ parentId: parentId || null,
+ });
+ setModalOpen(true);
+ }
+
+ function openEdit(cat: Category) {
+ setForm({
+ id: cat.id,
+ name: cat.name,
+ slug: cat.slug,
+ parentId: cat.parentId || null,
+ });
+ setModalOpen(true);
+ }
+
+ function closeModal() {
+ setModalOpen(false);
+ setForm({ id: null, name: "", slug: "", parentId: null });
+ }
+
+ /* ================= TREE ================= */
+ function TreeNode({
+ node,
+ level = 0,
+ }: {
+ node: Category;
+ level?: number;
+ }) {
+ const [open, setOpen] = useState(true);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ openEdit(node)}
+ className="text-sm font-medium cursor-pointer hover:text-blue-600"
+ >
+ {node.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {open &&
+ node.children?.map((child) => (
+
+ ))}
+
+ );
+ }
+
+ /* ================= UI ================= */
+ return (
+
+
+ {/* ================= SIDEBAR ================= */}
+ {/* Sidebar Lateral */}
+
+
+ {/* ================= MAIN ================= */}
+
+
+ {/* HEADER */}
+
+
+
+
+
+
+
+ Admin
+
+
+

+
+
+
+ {/* CONTENT */}
+
+
+ {/* TOP BAR */}
+
+
+
+ Categories
+
+
+
+
+
+ {/* TREE */}
+
+ {loading ? (
+
Loading...
+ ) : (
+ tree.map((node) => (
+
+ ))
+ )}
+
+
+
+
+
+ {/* ================= MODAL ================= */}
+ {modalOpen && (
+
+
+
+
+
+ {form.id ? "Edit Category" : "Create Category"}
+
+
+
+ setForm({
+ ...form,
+ name: e.target.value,
+ slug: slugify(e.target.value),
+ })
+ }
+ />
+
+
+ setForm({ ...form, slug: e.target.value })
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ );
+}
+
+
+// Componentes Auxiliares para Limpeza de Código
+const NavItem = ({ icon, label, active = false }: { icon: any, label: string, active?: boolean }) => (
+
+ {icon}
+ {label}
+
+ );
+
+ const ActivityItem = ({ user, action }: { user: string, action: string }) => (
+
+
+
+ {user} {action}
+
+
+ );
\ No newline at end of file
diff --git a/components/CategoryModal.tsx b/components/CategoryModal.tsx
new file mode 100644
index 0000000..e85ab63
--- /dev/null
+++ b/components/CategoryModal.tsx
@@ -0,0 +1,50 @@
+// export function CategoryModal({
+// open,
+// onClose,
+// form,
+// setForm,
+// onSave,
+// }: any) {
+// if (!open) return null;
+
+// return (
+//
+
+//
+
+//
+// {form.id ? "Edit Category" : "New Category"}
+//
+
+//
+// setForm({ ...form, name: e.target.value })
+// }
+// />
+
+//
+// setForm({ ...form, slug: e.target.value })
+// }
+// />
+
+//
+//
+
+//
+//
+//
+//
+// );
+// }
\ No newline at end of file
diff --git a/components/CategoryTree.tsx b/components/CategoryTree.tsx
new file mode 100644
index 0000000..b3e57e4
--- /dev/null
+++ b/components/CategoryTree.tsx
@@ -0,0 +1,116 @@
+// "use client";
+
+// import React, { useState } from "react";
+// import {
+// FolderTree,
+// Edit3,
+// Trash2,
+// Plus,
+// ChevronRight,
+// ChevronDown,
+// } from "lucide-react";
+// import { Category } from "@/lib/categories.api";
+
+// export function CategoryTree({
+// nodes,
+// onEdit,
+// onDelete,
+// onAddChild,
+// }: {
+// nodes: Category[];
+// onEdit: (c: Category) => void;
+// onDelete: (id: string) => void;
+// onAddChild: (parentId: string) => void;
+// }) {
+// return (
+//
+// {nodes.map((node) => (
+//
+// ))}
+//
+// );
+// }
+
+// function TreeNode({
+// node,
+// onEdit,
+// onDelete,
+// onAddChild,
+// }: {
+// node: Category;
+// onEdit: (c: Category) => void;
+// onDelete: (id: string) => void;
+// onAddChild: (parentId: string) => void;
+// }) {
+// const [open, setOpen] = useState(true);
+
+// return (
+//
+
+// {/* NODE ROW */}
+//
+
+//
+
+//
+
+//
+
+// {/* INLINE EDIT TRIGGER */}
+// onEdit(node)}
+// className="text-sm font-medium cursor-pointer hover:text-blue-600"
+// >
+// {node.name}
+//
+//
+
+// {/* ACTIONS (SHOW ON HOVER) */}
+//
+
+//
+
+//
+
+//
+//
+//
+
+// {/* CHILDREN */}
+// {open && node.children?.length ? (
+//
+// {node.children.map((child) => (
+//
+// ))}
+//
+// ) : null}
+//
+// );
+// }
\ No newline at end of file
diff --git a/hooks/useCategories.ts b/hooks/useCategories.ts
new file mode 100644
index 0000000..f2b25e7
--- /dev/null
+++ b/hooks/useCategories.ts
@@ -0,0 +1,89 @@
+import { useEffect, useState } from "react";
+import {
+ Category,
+ getCategoriesTree,
+ getCategoriesFlat,
+ createCategory,
+ updateCategory,
+ deleteCategory,
+} from "@/lib/categories.api";
+import { slugify } from "@/lib/slug";
+
+export function useCategories() {
+ const [tree, setTree] = useState([]);
+ const [flat, setFlat] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const [form, setForm] = useState({
+ id: null as string | null,
+ name: "",
+ slug: "",
+ parentId: null as string | null,
+ });
+
+ async function load() {
+ setLoading(true);
+ try {
+ const [t, f] = await Promise.all([
+ getCategoriesTree(),
+ getCategoriesFlat(),
+ ]);
+
+ setTree(t);
+ setFlat(f);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ useEffect(() => {
+ load();
+ }, []);
+
+ async function save() {
+ const payload = {
+ name: form.name,
+ slug: form.slug || slugify(form.name),
+ parentId: form.parentId,
+ };
+
+ if (form.id) {
+ await updateCategory(form.id, payload);
+ } else {
+ await createCategory(payload);
+ }
+
+ resetForm();
+ load();
+ }
+
+ async function remove(id: string) {
+ await deleteCategory(id);
+ load();
+ }
+
+ function edit(cat: Category) {
+ setForm({
+ id: cat.id,
+ name: cat.name,
+ slug: cat.slug,
+ parentId: cat.parentId || null,
+ });
+ }
+
+ function resetForm() {
+ setForm({ id: null, name: "", slug: "", parentId: null });
+ }
+
+ return {
+ tree,
+ flat,
+ form,
+ setForm,
+ save,
+ remove,
+ edit,
+ resetForm,
+ loading,
+ };
+}
\ No newline at end of file
diff --git a/lib/categories.api.ts b/lib/categories.api.ts
new file mode 100644
index 0000000..e9e143a
--- /dev/null
+++ b/lib/categories.api.ts
@@ -0,0 +1,54 @@
+const API = "http://localhost:3001/categories";
+
+export interface Category {
+ id: string;
+ name: string;
+ slug: string;
+ parentId?: string | null;
+ children?: Category[];
+}
+
+export async function getCategoriesTree(): Promise {
+ const res = await fetch(`${API}/`);
+ const data = await res.json();
+ return Array.isArray(data) ? data : data?.data ?? [];
+}
+
+export async function getCategoriesFlat(): Promise {
+ const res = await fetch(API);
+ const data = await res.json();
+ return Array.isArray(data) ? data : [];
+}
+
+export async function createCategory(payload: Partial) {
+ return fetch(API, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+}
+
+export async function updateCategory(id: string, payload: Partial) {
+ return fetch(`${API}/${id}`, {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+}
+
+export async function deleteCategory(id: string) {
+ return fetch(`${API}/${id}`, { method: "DELETE" });
+}
+
+
+export async function getTree(): Promise {
+ const res = await fetch(`${API}/`);
+ const data = await res.json();
+ return Array.isArray(data) ? data : data?.data ?? [];
+ }
+
+ export async function getFlat(): Promise {
+ const res = await fetch(API);
+ const data = await res.json();
+ return Array.isArray(data) ? data : [];
+ }
\ No newline at end of file
diff --git a/lib/slider-photos.ts b/lib/slider-photos.ts
index c2f2c48..36421fc 100644
--- a/lib/slider-photos.ts
+++ b/lib/slider-photos.ts
@@ -1,63 +1,63 @@
-import fs from "fs";
-import path from "path";
+// import fs from "fs";
+// import path from "path";
-export type SliderPhoto = { src: string; alt: string };
+// export type SliderPhoto = { src: string; alt: string };
-function slugToAlt(filename: string): string {
- const base = filename.replace(/\.[^.]+$/, "");
- const words = base.replace(/[-_]+/g, " ").trim();
- return words || "Slide";
-}
+// function slugToAlt(filename: string): string {
+// const base = filename.replace(/\.[^.]+$/, "");
+// const words = base.replace(/[-_]+/g, " ").trim();
+// return words || "Slide";
+// }
-export function parseManifest(data: unknown): SliderPhoto[] {
- if (!Array.isArray(data)) return [];
- const out: SliderPhoto[] = [];
- for (const item of data) {
- if (typeof item !== "object" || item === null || !("src" in item)) continue;
- const src = (item as { src: unknown }).src;
- if (typeof src !== "string" || !src.startsWith("/")) continue;
- const altRaw = (item as { alt?: unknown }).alt;
- const alt = typeof altRaw === "string" && altRaw.trim() ? altRaw.trim() : slugToAlt(src.split("/").pop() ?? "");
- out.push({ src, alt });
- }
- return out;
-}
+// export function parseManifest(data: unknown): SliderPhoto[] {
+// if (!Array.isArray(data)) return [];
+// const out: SliderPhoto[] = [];
+// for (const item of data) {
+// if (typeof item !== "object" || item === null || !("src" in item)) continue;
+// const src = (item as { src: unknown }).src;
+// if (typeof src !== "string" || !src.startsWith("/")) continue;
+// const altRaw = (item as { alt?: unknown }).alt;
+// const alt = typeof altRaw === "string" && altRaw.trim() ? altRaw.trim() : slugToAlt(src.split("/").pop() ?? "");
+// out.push({ src, alt });
+// }
+// return out;
+// }
-const IMAGE_EXT = /\.(jpe?g|png|webp|gif|avif)$/i;
+// const IMAGE_EXT = /\.(jpe?g|png|webp|gif|avif)$/i;
-function scanSliderDirectory(dir: string): SliderPhoto[] {
- let names: string[] = [];
- try {
- names = fs.readdirSync(dir);
- } catch {
- return [];
- }
- return names
- .filter((f) => IMAGE_EXT.test(f))
- .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
- .map((f) => ({
- src: `/slider/${f}`,
- alt: slugToAlt(f),
- }));
-}
+// function scanSliderDirectory(dir: string): SliderPhoto[] {
+// let names: string[] = [];
+// try {
+// names = fs.readdirSync(dir);
+// } catch {
+// return [];
+// }
+// return names
+// .filter((f) => IMAGE_EXT.test(f))
+// .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }))
+// .map((f) => ({
+// src: `/slider/${f}`,
+// alt: slugToAlt(f),
+// }));
+// }
-/**
- * Reads `public/slider/manifest.json` when present (full control: order + alt).
- * Otherwise scans `public/slider` for image files (drop-in updates, no code edits).
- */
-export function readSliderPhotos(): SliderPhoto[] {
- const dir = path.join(process.cwd(), "public", "slider");
- if (!fs.existsSync(dir)) return [];
+// /**
+// * Reads `public/slider/manifest.json` when present (full control: order + alt).
+// * Otherwise scans `public/slider` for image files (drop-in updates, no code edits).
+// */
+// export function readSliderPhotos(): SliderPhoto[] {
+// const dir = path.join(process.cwd(), "public", "slider");
+// if (!fs.existsSync(dir)) return [];
- const manifestPath = path.join(dir, "manifest.json");
- if (fs.existsSync(manifestPath)) {
- try {
- const raw = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as unknown;
- return parseManifest(raw);
- } catch {
- return [];
- }
- }
+// const manifestPath = path.join(dir, "manifest.json");
+// if (fs.existsSync(manifestPath)) {
+// try {
+// const raw = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as unknown;
+// return parseManifest(raw);
+// } catch {
+// return [];
+// }
+// }
- return scanSliderDirectory(dir);
-}
+// return scanSliderDirectory(dir);
+// }
diff --git a/lib/slug.ts b/lib/slug.ts
new file mode 100644
index 0000000..77aada8
--- /dev/null
+++ b/lib/slug.ts
@@ -0,0 +1,7 @@
+export function slugify(text: string) {
+ return text
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, "-")
+ .replace(/[^a-z0-9-]/g, "");
+ }
\ No newline at end of file
diff --git a/package.json b/package.json
index 075d6d2..c58bcbd 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,9 @@
"lint": "eslint"
},
"dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@react-oauth/google": "^0.13.5",
"@tinymce/tinymce-react": "^6.3.0",
"framer-motion": "^12.38.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6a9c447..a8246f5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,15 @@ importers:
.:
dependencies:
+ '@dnd-kit/core':
+ specifier: ^6.3.1
+ version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@dnd-kit/sortable':
+ specifier: ^10.0.0
+ version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)
+ '@dnd-kit/utilities':
+ specifier: ^3.2.2
+ version: 3.2.2(react@19.2.4)
'@react-oauth/google':
specifier: ^0.13.5
version: 0.13.5(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -143,6 +152,28 @@ packages:
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
+ '@dnd-kit/accessibility@3.1.1':
+ resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@dnd-kit/core@6.3.1':
+ resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@dnd-kit/sortable@10.0.0':
+ resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
+ peerDependencies:
+ '@dnd-kit/core': ^6.3.0
+ react: '>=16.8.0'
+
+ '@dnd-kit/utilities@3.2.2':
+ resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
'@emnapi/core@1.9.1':
resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==}
@@ -2154,6 +2185,31 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.28.5
+ '@dnd-kit/accessibility@3.1.1(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ tslib: 2.8.1
+
+ '@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@dnd-kit/accessibility': 3.1.1(react@19.2.4)
+ '@dnd-kit/utilities': 3.2.2(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ tslib: 2.8.1
+
+ '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@dnd-kit/core': 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@dnd-kit/utilities': 3.2.2(react@19.2.4)
+ react: 19.2.4
+ tslib: 2.8.1
+
+ '@dnd-kit/utilities@3.2.2(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ tslib: 2.8.1
+
'@emnapi/core@1.9.1':
dependencies:
'@emnapi/wasi-threads': 1.2.0