handle drag n drop

This commit is contained in:
2026-03-02 09:09:00 +01:00
parent f3b645ac53
commit 22067c4904
8 changed files with 195 additions and 96 deletions

View File

@@ -1,9 +1,10 @@
import { useCallback, useMemo, useState } from "react";
import { createFileRoute } from "@tanstack/react-router";
import { useSuspenseQuery, useQueryClient } from "@tanstack/react-query";
import { useSuspenseQuery } from "@tanstack/react-query";
import type { FlutterEvent, FlutterTimelineState } from "@/lib/flutter-bridge";
import { timelineQueryOptions } from "@/functions/get-timeline";
import { updateTimelineItem } from "@/functions/update-timeline-item";
import { FlutterView } from "@/components/flutter-view";
import { useEntryMovedMutation } from "@/hooks/use-entry-moved-mutation";
export const Route = createFileRoute("/timeline/$timelineId")({
loader: async ({ context, params }) => {
@@ -16,12 +17,12 @@ export const Route = createFileRoute("/timeline/$timelineId")({
function RouteComponent() {
const { timelineId } = Route.useParams();
const timelineQuery = useSuspenseQuery(timelineQueryOptions(timelineId));
const timeline = timelineQuery.data;
const queryClient = useQueryClient();
const { data: timeline } = useSuspenseQuery(timelineQueryOptions(timelineId));
const [selectedItemId, setSelectedItemId] = useState<string | null>(null);
const [flutterHeight, setFlutterHeight] = useState<number | undefined>();
const entryMoved = useEntryMovedMutation(timelineId);
const flutterState = useMemo(
const flutterState: FlutterTimelineState = useMemo(
() => ({
timeline: {
id: timeline.id,
@@ -34,8 +35,8 @@ function RouteComponent() {
id: item.id,
title: item.title,
description: item.description,
start: item.start,
end: item.end,
start: item.start.toISOString(),
end: item.end?.toISOString() ?? null,
lane: item.lane,
})),
})),
@@ -45,89 +46,24 @@ function RouteComponent() {
[timeline, selectedItemId]
);
const [flutterHeight, setFlutterHeight] = useState<number | undefined>();
const handleEvent = useCallback(
(event: { type: string; payload?: Record<string, unknown> }) => {
(event: FlutterEvent) => {
switch (event.type) {
case "content_height":
setFlutterHeight(event.payload?.height as number);
setFlutterHeight(event.payload.height);
break;
case "item_selected":
// setSelectedItemId((event.payload?.itemId as string) ?? null);
setSelectedItemId(event.payload.itemId);
break;
case "item_deselected":
setSelectedItemId(null);
break;
case "entry_moved": {
const p = event.payload;
if (!p) break;
const entryId = p.entryId as string;
const newStart = p.newStart as string;
const newEnd = p.newEnd as string;
const newGroupId = p.newGroupId as string;
const newLane = p.newLane as number;
// Optimistic cache update
queryClient.setQueryData(
["timeline", timelineId],
(old: typeof timeline | undefined) => {
if (!old) return old;
let movedItem:
| (typeof old.groups)[number]["items"][number]
| undefined;
const groups = old.groups.map((group) => {
const filtered = group.items.filter((item) => {
if (item.id === entryId) {
movedItem = item;
return false;
}
return true;
});
return { ...group, items: filtered };
});
if (!movedItem) return old;
return {
...old,
groups: groups.map((group) =>
group.id === newGroupId
? {
...group,
items: [
...group.items,
{
...movedItem!,
start: newStart,
end: newEnd,
lane: newLane,
timelineGroupId: newGroupId,
},
],
}
: group
),
};
}
);
// Persist to DB
updateTimelineItem({
data: {
id: entryId,
start: newStart,
end: newEnd,
timelineGroupId: newGroupId,
lane: newLane,
},
});
case "entry_moved":
entryMoved.mutate(event.payload);
break;
}
}
},
[queryClient, timelineId]
[entryMoved]
);
return (