Skip to content

Commit 02e5e79

Browse files
committed
added editing functionality for description and topics
1 parent 96a0db6 commit 02e5e79

4 files changed

Lines changed: 136 additions & 23 deletions

File tree

client/src/components/task_item.tsx

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useState } from "react";
22

3+
import { TopicInput } from "@/components/topic_input";
4+
35
interface Time {
46
id: number;
57
day: number;
@@ -27,35 +29,71 @@ type ItemProps = {
2729
item: Item;
2830
onToggle?: (id: number) => void;
2931
onUpdate: (item: Item) => void;
32+
availableTopics: Topic[];
3033
};
3134

32-
export function TaskItem({ item, onToggle, onUpdate }: ItemProps) {
35+
type TopicSelection =
36+
| { type: "existing"; id: number }
37+
| { type: "new"; name: string; color_hex: number };
38+
39+
export function TaskItem({
40+
item,
41+
onToggle,
42+
onUpdate,
43+
availableTopics,
44+
}: ItemProps) {
45+
{
46+
/* Editing Functionality */
47+
}
3348
const [isEditing, setIsEditing] = useState(false);
3449
const [draftName, setDraftName] = useState(item.name);
50+
const [draftDescription, setDraftDescription] = useState(item.description);
51+
const [draftTopics, setDraftTopics] = useState<TopicSelection[]>([]);
3552
const [saving, setSaving] = useState(false);
3653

3754
function startEdit() {
3855
setDraftName(item.name);
56+
setDraftDescription(item.description);
57+
setDraftTopics(
58+
item.topics.map((t) => ({
59+
type: "existing",
60+
id: t.id,
61+
})),
62+
);
3963
setIsEditing(true);
4064
}
4165

4266
function cancelEdit() {
43-
setIsEditing(false);
4467
setDraftName(item.name);
68+
setDraftDescription(item.description);
69+
setDraftTopics(item.topics.map((t) => ({ type: "existing", id: t.id })));
70+
setIsEditing(false);
4571
}
4672

4773
async function saveEdit() {
4874
setSaving(true);
4975
try {
76+
const existing_topic_ids = draftTopics
77+
.filter((t) => t.type === "existing")
78+
.map((t) => t.id);
79+
80+
const new_topics = draftTopics
81+
.filter((t) => t.type === "new")
82+
.map((t) => ({
83+
name: t.name,
84+
color_hex: t.color_hex,
85+
}));
5086
const response = await fetch(
5187
`http://localhost:8000/api/planner/tasks/${item.id}/`,
5288
{
5389
method: "PUT",
5490
headers: { "Content-Type": "application/json" },
5591
body: JSON.stringify({
5692
name: draftName,
57-
description: item.description,
93+
description: draftDescription,
5894
completed: item.completed,
95+
existing_topic_ids: existing_topic_ids,
96+
new_topics: new_topics,
5997
}),
6098
},
6199
);
@@ -72,9 +110,13 @@ export function TaskItem({ item, onToggle, onUpdate }: ItemProps) {
72110
}
73111
}
74112

113+
{
114+
/* Item Display */
115+
}
75116
return (
76-
<label className="m-1 flex w-96 flex-col rounded-md border-2 border-zinc-200 bg-zinc-700 p-2">
117+
<div className="m-1 flex w-96 flex-col rounded-md border-2 border-zinc-200 bg-zinc-700 p-2">
77118
<div className="flex items-center justify-between">
119+
{/* Task Name and Completion */}
78120
<div className="flex items-center space-x-2">
79121
<input
80122
type="checkbox"
@@ -98,6 +140,8 @@ export function TaskItem({ item, onToggle, onUpdate }: ItemProps) {
98140
</span>
99141
)}
100142
</div>
143+
144+
{/* Task Times */}
101145
<div className="flex flex-col items-end text-sm text-zinc-300">
102146
{item.times?.length > 0
103147
? item.times.map((t) => (
@@ -110,26 +154,45 @@ export function TaskItem({ item, onToggle, onUpdate }: ItemProps) {
110154
</div>
111155
</div>
112156

157+
{/* Task Topics */}
113158
<div className="flex flex-wrap gap-1 text-sm text-zinc-300">
114-
{item.topics?.length > 0
115-
? item.topics.map((topic) => (
159+
{isEditing ? (
160+
<TopicInput
161+
availableTopics={availableTopics}
162+
topics={draftTopics}
163+
setTopics={setDraftTopics}
164+
/>
165+
) : item.topics.length > 0 ? (
166+
item.topics.map((topic) => (
167+
<span
168+
key={topic.id}
169+
className="flex items-center gap-1 rounded-lg border-2 border-zinc-500 px-2 py-0.5 text-zinc-100"
170+
>
116171
<span
117-
key={topic.id}
118-
className="flex items-center gap-1 rounded-lg border-2 border-zinc-500 px-2 py-0.5 text-zinc-100"
119-
>
120-
<span
121-
className="h-2 w-2 rounded-full"
122-
style={{
123-
backgroundColor: `#${topic.color_hex.toString(16).padStart(6, "0")}`,
124-
}}
125-
/>
126-
{topic.name}
127-
</span>
128-
))
129-
: "No topics"}
172+
className="h-2 w-2 rounded-full"
173+
style={{
174+
backgroundColor: `#${topic.color_hex
175+
.toString(16)
176+
.padStart(6, "0")}`,
177+
}}
178+
/>
179+
{topic.name}
180+
</span>
181+
))
182+
) : (
183+
"No topics"
184+
)}
130185
</div>
186+
187+
{/* Task Description */}
131188
<div>
132-
{item.description ? (
189+
{isEditing ? (
190+
<textarea
191+
value={draftDescription}
192+
onChange={(e) => setDraftDescription(e.target.value)}
193+
className="mt-2 w-full rounded border-2 bg-zinc-600 p-1 text-zinc-200"
194+
/>
195+
) : item.description ? (
133196
<span className="mt-2 block text-zinc-300">{item.description}</span>
134197
) : (
135198
<span className="mt-2 block italic text-zinc-500">
@@ -149,7 +212,7 @@ export function TaskItem({ item, onToggle, onUpdate }: ItemProps) {
149212
<button onClick={startEdit}>Edit</button>
150213
)}
151214
</div>
152-
</label>
215+
</div>
153216
);
154217
}
155218

client/src/components/task_list.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,15 @@ type ListProps = {
2727
items: Item[];
2828
onToggleTask?: (id: number) => void;
2929
onUpdate: (item: Item) => void;
30+
availableTopics: Topic[];
3031
};
3132

32-
export function TaskList({ items, onToggleTask, onUpdate }: ListProps) {
33+
export function TaskList({
34+
items,
35+
onToggleTask,
36+
onUpdate,
37+
availableTopics,
38+
}: ListProps) {
3339
if (items.length === 0) {
3440
return (
3541
<div>
@@ -42,7 +48,12 @@ export function TaskList({ items, onToggleTask, onUpdate }: ListProps) {
4248
<ul className="space-y-2">
4349
{items.map((item) => (
4450
<li key={item.id}>
45-
<TaskItem item={item} onToggle={onToggleTask} onUpdate={onUpdate} />
51+
<TaskItem
52+
item={item}
53+
onToggle={onToggleTask}
54+
onUpdate={onUpdate}
55+
availableTopics={availableTopics}
56+
/>
4657
</li>
4758
))}
4859
</ul>

client/src/pages/[id]/tasks.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export default function TasksPage() {
3232
const { id } = router.query;
3333
const [items, setItems] = useState<Item[]>([]);
3434
const [loading, setLoading] = useState<boolean>(true);
35+
const [availableTopics, setAvailableTopics] = useState<Topic[]>([]);
3536

3637
useEffect(() => {
3738
if (!id) return;
@@ -53,6 +54,13 @@ export default function TasksPage() {
5354
fetchTasks();
5455
}, [id]);
5556

57+
useEffect(() => {
58+
fetch("http://localhost:8000/api/planner/topic/")
59+
.then((res) => res.json())
60+
.then((data) => setAvailableTopics(data))
61+
.catch((err) => console.error("Failed to load topics", err));
62+
}, []);
63+
5664
if (loading) {
5765
return <p>Loading tasks...</p>;
5866
}
@@ -103,6 +111,7 @@ export default function TasksPage() {
103111
items={items}
104112
onToggleTask={handleToggleTask}
105113
onUpdate={handleTaskUpdated}
114+
availableTopics={availableTopics}
106115
/>
107116
</div>
108117
<div className="m-4 h-fit w-fit rounded-xl bg-zinc-700 p-4 text-center">

server/task_planner/serializers.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,36 @@ def create(self, validated_data):
6868
task.save()
6969
task.refresh_from_db()
7070
return Task.objects.get(id=task.id)
71+
72+
@transaction.atomic
73+
def update(self, instance, validated_data):
74+
existing_topics = validated_data.pop("existing_topic_ids", [])
75+
new_topics_data = validated_data.pop("new_topics", [])
76+
#times_data = validated_data.pop("times", [])
77+
78+
for attr, value in validated_data.items():
79+
setattr(instance, attr, value)
80+
instance.save()
81+
82+
if existing_topics or new_topics_data:
83+
instance.topics.clear()
84+
for topic in existing_topics:
85+
instance.topics.add(topic)
86+
for topic_data in new_topics_data:
87+
topic = Topic.objects.create(**topic_data, user_id=instance.user_id)
88+
instance.topics.add(topic)
89+
"""
90+
if times_data is not None:
91+
instance.times.all().delete()
92+
Time.objects.bulk_create([
93+
Time(task=instance, **time_data)
94+
for time_data in times_data
95+
])
96+
"""
97+
98+
instance.save()
99+
instance.refresh_from_db()
100+
return Task.objects.get(id=instance.id)
71101

72102
class TaskCompleteSerializer(serializers.ModelSerializer):
73103
class Meta:

0 commit comments

Comments
 (0)