[cm] feat: 대시보드 페이지 dnd 적용
This commit is contained in:
@@ -12,6 +12,9 @@
|
||||
"@common/assets": "workspace:*",
|
||||
"@common/components": "workspace:*",
|
||||
"@common/config": "workspace:*",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import {
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import { sortableKeyboardCoordinates } from "@dnd-kit/sortable";
|
||||
|
||||
export function useDndSensors() {
|
||||
return useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { useState } from "react";
|
||||
import { arrayMove } from "@dnd-kit/sortable";
|
||||
import type { DragEndEvent } from "@dnd-kit/core";
|
||||
import type { PanelItem } from "../../model/types";
|
||||
|
||||
interface UsePanelOrderProps {
|
||||
initialPanels: PanelItem[];
|
||||
storageKey: string;
|
||||
}
|
||||
|
||||
export function usePanelOrder({
|
||||
initialPanels,
|
||||
storageKey,
|
||||
}: UsePanelOrderProps) {
|
||||
const [panels, setPanels] = useState<PanelItem[]>(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (saved) {
|
||||
try {
|
||||
const savedIds = JSON.parse(saved);
|
||||
const restoredPanels = savedIds
|
||||
.map((id: string) => initialPanels.find((p) => p.id === id))
|
||||
.filter(Boolean);
|
||||
if (restoredPanels.length === initialPanels.length) {
|
||||
return restoredPanels;
|
||||
}
|
||||
} catch {
|
||||
return initialPanels;
|
||||
}
|
||||
}
|
||||
}
|
||||
return initialPanels;
|
||||
});
|
||||
|
||||
const handleDragEnd = (event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
setPanels((items) => {
|
||||
const oldIndex = items.findIndex((item) => item.id === active.id);
|
||||
const newIndex = items.findIndex((item) => item.id === over.id);
|
||||
const newItems = arrayMove(items, oldIndex, newIndex);
|
||||
|
||||
// localStorage에 저장
|
||||
localStorage.setItem(
|
||||
storageKey,
|
||||
JSON.stringify(newItems.map((item) => item.id)),
|
||||
);
|
||||
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return { panels, handleDragEnd };
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import FoFcltCntRt from "@/pages/dashboard/ui/fo-fclt-cnt-rt";
|
||||
import SnPplStatus from "@/pages/dashboard/ui/sn-ppl-status";
|
||||
import ReuseChart from "@/pages/dashboard/ui/reuse-chart";
|
||||
import FoWqltInflEffl from "@/pages/dashboard/ui/fo-wqlt-Infl-effl";
|
||||
import SnOldPpl from "@/pages/dashboard/ui/sn-old-ppl";
|
||||
import ReuseStatus from "@/pages/dashboard/ui/reuse-status";
|
||||
import SupportList from "@/pages/dashboard/ui/support-list";
|
||||
import DataStatusList from "@/pages/dashboard/ui/datastatus-list";
|
||||
import NoticeList from "@/pages/dashboard/ui/notice-list";
|
||||
import BannerList from "@/pages/dashboard/ui/banner-list";
|
||||
import type { PanelItem } from "./types";
|
||||
|
||||
export function getInitialTopPanels(): PanelItem[] {
|
||||
return [
|
||||
{
|
||||
id: "panel-1",
|
||||
component: <FoFcltCntRt />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-2",
|
||||
component: <SnPplStatus />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-3",
|
||||
component: <ReuseChart />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-4",
|
||||
component: <FoWqltInflEffl />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-5",
|
||||
component: <SnOldPpl />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-6",
|
||||
component: <ReuseStatus />,
|
||||
className: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getInitialBottomPanels(): PanelItem[] {
|
||||
return [
|
||||
{
|
||||
id: "panel-7",
|
||||
component: <SupportList />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-8",
|
||||
component: <DataStatusList />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-9",
|
||||
component: <NoticeList />,
|
||||
className: "",
|
||||
},
|
||||
{
|
||||
id: "panel-10",
|
||||
component: <BannerList />,
|
||||
className: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface PanelItem {
|
||||
id: string;
|
||||
component: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type PanelType = "top" | "bottom";
|
||||
@@ -6,6 +6,8 @@ import { tokenManager } from "@/entities/auth/lib/token-manager";
|
||||
import { client } from "@/shared/api";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { KoreaBasinMap } from "../lib/korea-basin-map";
|
||||
import { useDndSensors } from "../lib/hooks/use-dnd-sensors";
|
||||
import { usePanelOrder } from "../lib/hooks/use-panel-order";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
@@ -18,18 +20,15 @@ import {
|
||||
Tooltip,
|
||||
ArcElement,
|
||||
} from "chart.js";
|
||||
import SnPplStatus from "./sn-ppl-status";
|
||||
import SnOldPpl from "./sn-old-ppl";
|
||||
import FoFcltCntRt from "./fo-fclt-cnt-rt";
|
||||
import FoWqltInflEffl from "./fo-wqlt-Infl-effl";
|
||||
import ReuseChart from "./reuse-chart";
|
||||
import ReuseStatus from "./reuse-status";
|
||||
import NoticeList from "./notice-list";
|
||||
import BannerList from "./banner-list";
|
||||
import SupportList from "./support-list";
|
||||
import DataStatusList from "./datastatus-list";
|
||||
import { DndContext, closestCenter } from "@dnd-kit/core";
|
||||
import { SortableContext, rectSortingStrategy } from "@dnd-kit/sortable";
|
||||
import {
|
||||
getInitialTopPanels,
|
||||
getInitialBottomPanels,
|
||||
} from "../model/panel-config";
|
||||
import MyPostList from "./mypost-list";
|
||||
import ScheduleList from "./schedule-list";
|
||||
import { SortablePanel } from "./sortable-panel";
|
||||
|
||||
// Chart.js 등록
|
||||
ChartJS.register(
|
||||
@@ -41,7 +40,7 @@ ChartJS.register(
|
||||
Filler,
|
||||
Legend,
|
||||
Tooltip,
|
||||
ArcElement
|
||||
ArcElement,
|
||||
);
|
||||
|
||||
interface UserInfo {
|
||||
@@ -54,7 +53,23 @@ interface UserInfo {
|
||||
function DashboardPage() {
|
||||
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
|
||||
const [error, setError] = useState("");
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
// dnd-kit 센서 설정
|
||||
const sensors = useDndSensors();
|
||||
|
||||
// 패널 순서 관리
|
||||
const { panels: topPanels, handleDragEnd: handleTopDragEnd } = usePanelOrder({
|
||||
initialPanels: getInitialTopPanels(),
|
||||
storageKey: "dashboard-top-panels-order",
|
||||
});
|
||||
|
||||
const { panels: bottomPanels, handleDragEnd: handleBottomDragEnd } =
|
||||
usePanelOrder({
|
||||
initialPanels: getInitialBottomPanels(),
|
||||
storageKey: "dashboard-bottom-panels-order",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserInfo = async () => {
|
||||
console.log("fetchUserInfo called");
|
||||
@@ -115,96 +130,129 @@ function DashboardPage() {
|
||||
return (
|
||||
<div className="p-2 sm:p-4 md:p-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* 첫번째 라인 */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-8 gap-1">
|
||||
{/* 첫번쨰 열 */}
|
||||
<div className="col-span-1 md:col-span-1 lg:col-span-1 xl:col-span-2 h-250 !p-0">
|
||||
{/* 전국 하수처리시설 개소수 및 가동률 */}
|
||||
<FoFcltCntRt />
|
||||
|
||||
{/* 오수관로 및 우수관로 현황 */}
|
||||
<SnPplStatus />
|
||||
|
||||
{/* 하수처리수 재이용 (m³) */}
|
||||
<ReuseChart />
|
||||
</div>
|
||||
|
||||
{/* 두번쨰 열 */}
|
||||
<div className="col-span-1 md:col-span-1 lg:col-span-1 xl:col-span-2 h-250 !p-0">
|
||||
{/* 수계별 유입ㆍ방류수질 분석 */}
|
||||
<FoWqltInflEffl />
|
||||
|
||||
{/* 노후하수관로현황 */}
|
||||
<SnOldPpl />
|
||||
|
||||
{/* 물재이용 현황 (재이용량, m³) */}
|
||||
<ReuseStatus />
|
||||
</div>
|
||||
|
||||
{/* 지도영역 */}
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-1 xl:col-span-3">
|
||||
<KoreaBasinMap />
|
||||
</div>
|
||||
|
||||
{/* 사용자 정보 카드 */}
|
||||
<div className="col-span-1 md:col-span-2 lg:col-span-1 xl:col-span-1 !p-0">
|
||||
{error && <div className="mb-4 text-red-500">{error}</div>}
|
||||
{userInfo ? (
|
||||
<div className="card card_border login_info_box" style={{ minHeight: "150px", height: "auto" }}>
|
||||
<div className="login_header" style={{ paddingLeft: "10px", paddingRight: "10px" }}>
|
||||
<div className="login_avatar">
|
||||
<Image
|
||||
src="/images/icon_loginID.png"
|
||||
alt="프로필"
|
||||
width={40}
|
||||
height={40}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='18' fill='%23e1e5e9'/%3E%3Ctext x='20' y='26' font-size='20' text-anchor='middle' fill='%23999'%3E%3F%3C/text%3E%3C/svg%3E";
|
||||
}}
|
||||
/>
|
||||
{/* 첫번째 라인 - 좌측 6개 패널(2x3) + 우측 지도/사용자 정보 */}
|
||||
<div className="flex flex-col gap-1 lg:flex-row">
|
||||
{/* 좌측: 드래그 가능한 6개 패널 (2x3 그리드, 화면의 50%) */}
|
||||
<div className="w-full lg:w-1/2">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleTopDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={topPanels.map((p) => p.id)}
|
||||
strategy={rectSortingStrategy}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-1">
|
||||
{topPanels.map((panel) => (
|
||||
<SortablePanel
|
||||
key={panel.id}
|
||||
id={panel.id}
|
||||
className={panel.className}
|
||||
>
|
||||
{panel.component}
|
||||
</SortablePanel>
|
||||
))}
|
||||
</div>
|
||||
<div className="login_user_info">
|
||||
<div className="user_name">{userInfo.userName || "-"}</div>
|
||||
<div className="user_status">온라인</div>
|
||||
<div className="user_status text-xs sm:text-sm">
|
||||
<b>2FA 활성화:</b>{" "}
|
||||
{userInfo.twoFactorEnabled ? "예" : "아니오"}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</div>
|
||||
|
||||
{/* 우측: 고정된 지도와 사용자 정보 (화면의 50%) */}
|
||||
<div className="flex w-full gap-1 lg:w-1/2">
|
||||
{/* 지도영역 - 고정 (드래그 불가) */}
|
||||
<div className="flex-1">
|
||||
<KoreaBasinMap />
|
||||
</div>
|
||||
|
||||
{/* 사용자 정보 카드 - 고정 (드래그 불가) */}
|
||||
<div className="flex w-full flex-col gap-1 !p-0 lg:w-[300px]">
|
||||
{error && <div className="mb-4 text-red-500">{error}</div>}
|
||||
{userInfo ? (
|
||||
<div
|
||||
className="card card_border login_info_box"
|
||||
style={{ minHeight: "150px", height: "auto" }}
|
||||
>
|
||||
<div
|
||||
className="login_header"
|
||||
style={{ paddingLeft: "10px", paddingRight: "10px" }}
|
||||
>
|
||||
<div className="login_avatar">
|
||||
<Image
|
||||
src="/images/icon_loginID.png"
|
||||
alt="프로필"
|
||||
width={40}
|
||||
height={40}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
target.src =
|
||||
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' viewBox='0 0 40 40'%3E%3Ccircle cx='20' cy='20' r='18' fill='%23e1e5e9'/%3E%3Ctext x='20' y='26' font-size='20' text-anchor='middle' fill='%23999'%3E%3F%3C/text%3E%3C/svg%3E";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="login_user_info">
|
||||
<div className="user_name">
|
||||
{userInfo.userName || "-"}
|
||||
</div>
|
||||
<div className="user_status">온라인</div>
|
||||
<div className="user_status text-xs sm:text-sm">
|
||||
<b>2FA 활성화:</b>{" "}
|
||||
{userInfo.twoFactorEnabled ? "예" : "아니오"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="login_actions"
|
||||
style={{ paddingLeft: "10px", paddingRight: "10px" }}
|
||||
>
|
||||
<button
|
||||
className="login_btn text-xs sm:text-sm"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
<button
|
||||
className="signup_btn text-xs sm:text-sm"
|
||||
onClick={handleChangePassword}
|
||||
>
|
||||
마이페이지
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="login_actions" style={{ paddingLeft: "10px", paddingRight: "10px" }}>
|
||||
<button className="login_btn text-xs sm:text-sm" onClick={handleLogout}>로그아웃</button>
|
||||
<button className="signup_btn text-xs sm:text-sm" onClick={handleChangePassword}>마이페이지</button>
|
||||
</div>
|
||||
) : (
|
||||
!error && <div>사용자 정보를 불러오는 중...</div>
|
||||
)}
|
||||
|
||||
<ScheduleList />
|
||||
|
||||
<MyPostList />
|
||||
</div>
|
||||
) : (
|
||||
!error && <div>사용자 정보를 불러오는 중...</div>
|
||||
)}
|
||||
|
||||
<ScheduleList />
|
||||
|
||||
<MyPostList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 두번째 라인 */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-1">
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<SupportList />
|
||||
</div>
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<DataStatusList />
|
||||
</div>
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<NoticeList />
|
||||
</div>
|
||||
|
||||
{/* 배너 영역 */}
|
||||
<div className="card card-border" style={{ height: "250px" }}>
|
||||
<BannerList />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 두번째 라인 - 드래그 가능한 4개 패널 */}
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleBottomDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={bottomPanels.map((p) => p.id)}
|
||||
strategy={rectSortingStrategy}
|
||||
>
|
||||
<div className="grid grid-cols-1 gap-1 sm:grid-cols-2 lg:grid-cols-4">
|
||||
{bottomPanels.map((panel) => (
|
||||
<SortablePanel
|
||||
key={panel.id}
|
||||
id={panel.id}
|
||||
className="card card-border"
|
||||
>
|
||||
<div style={{ height: "250px" }}>{panel.component}</div>
|
||||
</SortablePanel>
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
interface SortablePanelProps {
|
||||
id: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SortablePanel({ id, children, className }: SortablePanelProps) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
cursor: isDragging ? "grabbing" : "grab",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Generated
+59
-2
@@ -56,6 +56,15 @@ importers:
|
||||
'@common/config':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/common/config
|
||||
'@dnd-kit/core':
|
||||
specifier: ^6.3.1
|
||||
version: 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@dnd-kit/sortable':
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
|
||||
'@dnd-kit/utilities':
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2(react@19.2.3)
|
||||
'@fontsource-variable/noto-sans-kr':
|
||||
specifier: ^5.2.5
|
||||
version: 5.2.9
|
||||
@@ -1735,6 +1744,28 @@ packages:
|
||||
resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1':
|
||||
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||
peerDependencies:
|
||||
react: ^19.2.1
|
||||
|
||||
'@dnd-kit/core@6.3.1':
|
||||
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
|
||||
peerDependencies:
|
||||
react: ^19.2.1
|
||||
react-dom: ^19.2.1
|
||||
|
||||
'@dnd-kit/sortable@10.0.0':
|
||||
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
|
||||
peerDependencies:
|
||||
'@dnd-kit/core': ^6.3.0
|
||||
react: ^19.2.1
|
||||
|
||||
'@dnd-kit/utilities@3.2.2':
|
||||
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
|
||||
peerDependencies:
|
||||
react: ^19.2.1
|
||||
|
||||
'@emnapi/core@1.7.1':
|
||||
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
||||
|
||||
@@ -4999,6 +5030,7 @@ packages:
|
||||
|
||||
glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
globals@14.0.0:
|
||||
@@ -7268,6 +7300,31 @@ snapshots:
|
||||
gonzales-pe: 4.3.0
|
||||
node-source-walk: 7.0.1
|
||||
|
||||
'@dnd-kit/accessibility@3.1.1(react@19.2.3)':
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@dnd-kit/accessibility': 3.1.1(react@19.2.3)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.2.3)
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@dnd-kit/utilities': 3.2.2(react@19.2.3)
|
||||
react: 19.2.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@dnd-kit/utilities@3.2.2(react@19.2.3)':
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
tslib: 2.8.1
|
||||
|
||||
'@emnapi/core@1.7.1':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.1.0
|
||||
@@ -10388,7 +10445,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@@ -10439,7 +10496,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 9.39.2(jiti@2.6.1)
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1))
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
||||
Reference in New Issue
Block a user