Skip to content

Commit 748f9ca

Browse files
author
Korolev, Alexey (ak3140)
committed
feat: add flow visibility toggling and event editing overlay with improved form auto-sequencing
1 parent 19a7265 commit 748f9ca

6 files changed

Lines changed: 337 additions & 213 deletions

File tree

src/components/ControlsSection/AddEventForm.tsx

Lines changed: 102 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,144 @@
1-
import React, { useState, useRef } from 'react';
1+
import React, { useState, useRef, useEffect, useCallback } from 'react';
22
import { useTimelineStore } from '../../store/timelineStore';
33
import { PREDEFINED_COLORS } from '../../constants/colors';
44

55
export const AddEventForm: React.FC = () => {
6-
const { flows, addEvent } = useTimelineStore();
6+
const { flows, events, addEvent } = useTimelineStore();
77
const titleInputRef = useRef<HTMLInputElement>(null);
8-
8+
99
const [eventFlowId, setEventFlowId] = useState('');
1010
const [eventTitle, setEventTitle] = useState('');
1111
const [eventStartMs, setEventStartMs] = useState(0);
1212
const [eventEndMs, setEventEndMs] = useState(1000);
1313
const [eventColor, setEventColor] = useState('#7dd3fc');
1414

15-
const isValidFlowId = flows.some(f => f.id === eventFlowId);
16-
const effectiveFlowId = isValidFlowId ? eventFlowId : (flows.length > 0 ? flows[0].id : '');
15+
const isValidFlowId = flows.some((f) => f.id === eventFlowId);
16+
const effectiveFlowId = isValidFlowId
17+
? eventFlowId
18+
: flows.length > 0 ? flows[0].id : '';
19+
20+
const computeTimesForFlow = useCallback((flowId: string) => {
21+
const flowEvents = events.filter((e) => e.flowId === flowId);
22+
if (flowEvents.length === 0) return { start: 0, end: 1000 };
23+
const lastEnd = Math.max(...flowEvents.map((e) => e.endMs));
24+
return { start: lastEnd, end: lastEnd + 1000 };
25+
}, [events]);
26+
27+
// Sync times when effective flow changes
28+
useEffect(() => {
29+
if (!effectiveFlowId) return;
30+
const { start, end } = computeTimesForFlow(effectiveFlowId);
31+
// eslint-disable-next-line react-hooks/set-state-in-effect
32+
setEventStartMs(start);
33+
// eslint-disable-next-line react-hooks/set-state-in-effect
34+
setEventEndMs(end);
35+
}, [effectiveFlowId, computeTimesForFlow]);
36+
37+
const handleFlowChange = (flowId: string) => {
38+
setEventFlowId(flowId);
39+
const { start, end } = computeTimesForFlow(flowId);
40+
setEventStartMs(start);
41+
setEventEndMs(end);
42+
};
1743

1844
const handleAddEvent = (e: React.FormEvent) => {
1945
e.preventDefault();
20-
if (effectiveFlowId && eventTitle.trim() && eventEndMs > eventStartMs) {
21-
const added = addEvent({
22-
flowId: effectiveFlowId,
23-
title: eventTitle.trim(),
24-
startMs: Number(eventStartMs),
25-
endMs: Number(eventEndMs),
26-
color: eventColor,
27-
});
28-
29-
if (added) {
30-
setEventTitle('');
31-
setEventStartMs(eventEndMs);
32-
setEventEndMs(eventEndMs + 1000);
33-
titleInputRef.current?.focus();
34-
} else {
35-
alert("Cannot add event: overlaps with an existing event in this flow.");
36-
}
46+
if (!effectiveFlowId || !eventTitle.trim()) return;
47+
if (eventEndMs <= eventStartMs) return;
48+
const added = addEvent({
49+
flowId: effectiveFlowId,
50+
title: eventTitle.trim(),
51+
startMs: Number(eventStartMs),
52+
endMs: Number(eventEndMs),
53+
color: eventColor,
54+
});
55+
if (added) {
56+
setEventTitle('');
57+
setEventStartMs(eventEndMs);
58+
setEventEndMs(eventEndMs + 1000);
59+
titleInputRef.current?.focus();
60+
} else {
61+
alert('Cannot add: overlaps with an existing event.');
3762
}
3863
};
3964

4065
return (
41-
<form onSubmit={handleAddEvent} className="bg-white p-4 rounded shadow border border-gray-200 flex-[2]">
42-
<div className="grid grid-cols-2 md:grid-cols-6 gap-2 items-end">
66+
<form onSubmit={handleAddEvent}
67+
className="bg-white p-4 rounded shadow border
68+
border-gray-200 flex-[2]">
69+
<div className="grid grid-cols-2 md:grid-cols-6 gap-2
70+
items-end">
4371
<div className="col-span-2">
44-
<label className="block text-xs text-gray-500 mb-1">Flow</label>
45-
<select
46-
className="w-full border border-gray-300 rounded px-2 py-1 flex-1 text-sm"
47-
value={effectiveFlowId}
48-
onChange={(e) => setEventFlowId(e.target.value)}
49-
>
50-
{flows.length === 0 && <option value="" disabled>No flows available</option>}
72+
<label className="block text-xs text-gray-500 mb-1">
73+
Flow
74+
</label>
75+
<select className="w-full border border-gray-300 rounded
76+
px-2 py-1 flex-1 text-sm" value={effectiveFlowId}
77+
onChange={(e) => handleFlowChange(e.target.value)}>
78+
{flows.length === 0 && (
79+
<option value="" disabled>No flows</option>
80+
)}
5181
{flows.map((f) => (
5282
<option key={f.id} value={f.id}>{f.title}</option>
5383
))}
5484
</select>
5585
</div>
5686
<div className="col-span-1">
57-
<label className="block text-xs text-gray-500 mb-1">Title</label>
58-
<input ref={titleInputRef} type="text" required className="w-full border border-gray-300 rounded px-2 py-1 text-sm" placeholder="Event Name" value={eventTitle} onChange={(e) => setEventTitle(e.target.value)} />
87+
<label className="block text-xs text-gray-500 mb-1">
88+
Title
89+
</label>
90+
<input ref={titleInputRef} type="text" required
91+
className="w-full border border-gray-300 rounded px-2
92+
py-1 text-sm" placeholder="Event Name"
93+
value={eventTitle}
94+
onChange={(e) => setEventTitle(e.target.value)} />
5995
</div>
6096
<div className="col-span-1">
61-
<label className="block text-xs text-gray-500 mb-1">Start (ms)</label>
62-
<input type="text" inputMode="numeric" pattern="\d*" required className="w-full border border-gray-300 rounded px-2 py-1 text-sm" value={eventStartMs} onChange={(e) => setEventStartMs(Number(e.target.value.replace(/\D/g, '')))} />
97+
<label className="block text-xs text-gray-500 mb-1">
98+
Start (ms)
99+
</label>
100+
<input type="text" inputMode="numeric" pattern="\d*"
101+
required className="w-full border border-gray-300
102+
rounded px-2 py-1 text-sm"
103+
value={eventStartMs}
104+
onChange={(e) =>
105+
setEventStartMs(Number(e.target.value.replace(/\D/g, '')))
106+
} />
63107
</div>
64108
<div className="col-span-1">
65-
<label className="block text-xs text-gray-500 mb-1">End (ms)</label>
66-
<input type="text" inputMode="numeric" pattern="\d*" required className="w-full border border-gray-300 rounded px-2 py-1 text-sm" value={eventEndMs} onChange={(e) => setEventEndMs(Number(e.target.value.replace(/\D/g, '')))} />
109+
<label className="block text-xs text-gray-500 mb-1">
110+
End (ms)
111+
</label>
112+
<input type="text" inputMode="numeric" pattern="\d*"
113+
required className="w-full border border-gray-300
114+
rounded px-2 py-1 text-sm"
115+
value={eventEndMs}
116+
onChange={(e) =>
117+
setEventEndMs(Number(e.target.value.replace(/\D/g, '')))
118+
} />
67119
</div>
68120
<div className="col-span-1 flex items-end justify-between">
69121
<div>
70-
<label className="block text-xs text-gray-500 mb-1">Color</label>
71-
<input
72-
type="color"
73-
className="w-8 h-8 rounded border-none cursor-pointer p-0 m-0 box-border"
122+
<label className="block text-xs text-gray-500 mb-1">
123+
Color
124+
</label>
125+
<input type="color"
126+
className="w-8 h-8 rounded border-none cursor-pointer
127+
p-0 m-0 box-border"
74128
value={eventColor}
75129
onChange={(e) => setEventColor(e.target.value)}
76-
list="presetColors"
77-
/>
130+
list="presetColors" />
78131
<datalist id="presetColors">
79-
{PREDEFINED_COLORS.map(color => (
80-
<option key={color} value={color}>{color}</option>
132+
{PREDEFINED_COLORS.map((c) => (
133+
<option key={c} value={c}>{c}</option>
81134
))}
82135
</datalist>
83136
</div>
84-
<button
85-
type="submit"
137+
<button type="submit"
86138
disabled={!eventTitle || eventEndMs <= eventStartMs}
87-
className="bg-green-600 hover:bg-green-700 text-white px-3 py-1.5 rounded text-sm font-medium disabled:opacity-50"
88-
>
139+
className="bg-green-600 hover:bg-green-700 text-white
140+
px-3 py-1.5 rounded text-sm font-medium
141+
disabled:opacity-50">
89142
Add
90143
</button>
91144
</div>

0 commit comments

Comments
 (0)