Skip to content

Commit b717285

Browse files
authored
Olivia breaks daniel fixes (#717)
* Fix weekends and delete * Added persistence
1 parent f5d05a0 commit b717285

8 files changed

Lines changed: 217 additions & 125 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 5.0.2 on 2025-05-13 23:27
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("plan", "0015_schedule_breaks"),
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
]
13+
14+
operations = [
15+
migrations.AddConstraint(
16+
model_name="break",
17+
constraint=models.UniqueConstraint(
18+
condition=models.Q(("name__isnull", False), models.Q(("name", ""), _negated=True)),
19+
fields=("name", "person"),
20+
name="unique_break_name_per_person",
21+
),
22+
),
23+
]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 5.0.2 on 2025-05-14 00:27
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("plan", "0016_break_unique_break_name_per_person"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="break",
15+
name="checked",
16+
field=models.BooleanField(
17+
default=True,
18+
help_text="\nWhether or not the break has been checked by the user. This is used to\ndetermine if the break should be displayed in the user's schedule.\n",
19+
),
20+
),
21+
]

backend/plan/models.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ class Break(models.Model):
4242
),
4343
)
4444

45+
checked = models.BooleanField(
46+
default=True,
47+
help_text=dedent(
48+
"""
49+
Whether or not the break has been checked by the user. This is used to
50+
determine if the break should be displayed in the user's schedule.
51+
"""
52+
),
53+
)
54+
4555
meeting_times = models.TextField(
4656
blank=True,
4757
help_text=dedent(
@@ -62,7 +72,12 @@ class Meta:
6272
fields=["person", "meeting_times"],
6373
condition=Q(meeting_times__isnull=False) & ~Q(meeting_times=""),
6474
name="unique_break_meeting_times_per_person",
65-
)
75+
),
76+
UniqueConstraint(
77+
fields=["name", "person"],
78+
condition=Q(name__isnull=False) & ~Q(name=""),
79+
name="unique_break_name_per_person",
80+
),
6681
]
6782

6883
def __str__(self):

backend/plan/views.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@ def get_breaks(data):
501501
if break_id:
502502
break_candidate = Break.objects.filter(id=break_id).first()
503503
if break_candidate:
504+
break_candidate.checked = b.get("checked", False)
505+
break_candidate.save()
504506
breaks.append(break_candidate)
505507
return breaks
506508

@@ -701,7 +703,8 @@ def update(self, request):
701703
{"detail": "Error setting meetings: " + str(e)}, status=status.HTTP_400_BAD_REQUEST
702704
)
703705

704-
print("current_break", current_break)
706+
checked = request.data.get("checked")
707+
current_break.checked = checked if checked is not None else current_break.checked
705708

706709
try:
707710
current_break.save()
@@ -724,7 +727,6 @@ def create(self, request, *args, **kwargs):
724727
{"detail": "Break name is required."}, status=status.HTTP_400_BAD_REQUEST
725728
)
726729
location_string = request.data.get("location_string")
727-
print(request.data)
728730
try:
729731
if break_id:
730732
new_break = self.get_queryset().create(
@@ -762,7 +764,6 @@ def create(self, request, *args, **kwargs):
762764
}
763765
for m in meetings
764766
]
765-
print(meetings_with_codes)
766767
try:
767768
set_meetings(new_break, meetings_with_codes)
768769
except Exception as e:

frontend/plan/actions/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,9 @@ export const updateScheduleOnBackend = (name, schedule) => (dispatch) => {
703703
...schedule,
704704
name,
705705
sections: schedule.sections,
706-
breaks: (schedule.breaks ?? []).map((breakItem) => breakItem.break),
706+
breaks: (schedule.breaks ?? []).map((breakItem) => {
707+
return { ...breakItem.break, checked: breakItem.checked };
708+
}),
707709
};
708710
doAPIRequest(`/plan/schedules/${schedule.id}/`, {
709711
method: "PUT",

frontend/plan/components/schedule/ScheduleDisplay.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Day,
1212
Meeting,
1313
Section,
14+
Break,
1415
MeetingBlock,
1516
FriendshipState,
1617
BreakSectionItem,
@@ -142,23 +143,27 @@ const ScheduleDisplay = ({
142143
let breaks;
143144

144145
breaks = friendshipState.activeFriendSchedule?.breaks
145-
|| schedData.breaks || [];
146+
|| schedData.breaks || [];
146147

147-
const notEmpty = sections.length > 0;
148+
const notEmpty = sections.length > 0 || breaks.length > 0;
148149

149150
let startHour = 10.5;
150151
let endHour = 16;
151152

153+
152154
// show the weekend days only if there's a section which meets on saturday (S) or sunday (U)
153155
const showWeekend =
154-
sections.filter((sec: Section) => {
155-
if (sec.meetings) {
156-
sec.meetings.filter(
157-
(meeting: Meeting) =>
158-
meeting.day === "S" || meeting.day === "U"
159-
).length > 0;
160-
}
161-
}).length > 0;
156+
sections.some((sec: Section) =>
157+
sec.meetings?.some(
158+
(meeting: Meeting) => meeting.day === "S" || meeting.day === "U"
159+
)
160+
) ||
161+
breaks.some((brk: BreakSectionItem) =>
162+
brk.break.meetings?.some(
163+
(meeting: Meeting) => meeting.day === "S" || meeting.day === "U"
164+
)
165+
);
166+
162167

163168
// 15 minute time intervals
164169
const getNumRows = () => (endHour - startHour + 1) * 4 + rowOffset;

frontend/plan/components/solver/DayTimeSelector.tsx

Lines changed: 107 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const FilterContainer = styled.div`
2121
flex-wrap: wrap;
2222
justify-content: center;
2323
margin-top: 0.5rem;
24-
`;
24+
`;
2525

2626
const DayRow = styled.div`
2727
padding: 0.75rem;
@@ -61,115 +61,115 @@ const NameInput = styled.input`
6161
`;
6262

6363
const intToTime = (t: number) => {
64-
let hour = Math.floor(t % 12);
65-
const min = Math.round((t % 1) * 60);
66-
let meridian;
67-
if (t === 24) {
68-
meridian = "AM";
69-
} else {
70-
meridian = t < 12 ? "AM" : "PM";
71-
}
72-
if (hour === 0) {
73-
hour = 12;
74-
}
75-
const minString = min > 9 ? min : `0${min}`;
76-
if (min === 0) {
77-
return `${hour} ${meridian}`;
78-
}
79-
return `${hour}:${minString} ${meridian}`;
64+
let hour = Math.floor(t % 12);
65+
const min = Math.round((t % 1) * 60);
66+
let meridian;
67+
if (t === 24) {
68+
meridian = "AM";
69+
} else {
70+
meridian = t < 12 ? "AM" : "PM";
71+
}
72+
if (hour === 0) {
73+
hour = 12;
74+
}
75+
const minString = min > 9 ? min : `0${min}`;
76+
if (min === 0) {
77+
return `${hour} ${meridian}`;
78+
}
79+
return `${hour}:${minString} ${meridian}`;
8080
};
8181

8282
interface DayTimeSelectorProps {
83-
minRange: number;
84-
maxRange: number;
85-
step: number;
86-
selectedDays: string[];
87-
setSelectedDays: (days: string[]) => void;
88-
selectedTimes: [number, number];
89-
setSelectedTimes: (times: [number, number]) => void;
90-
name: string;
91-
setName: (name: string) => void;
83+
minRange: number;
84+
maxRange: number;
85+
step: number;
86+
selectedDays: string[];
87+
setSelectedDays: (days: string[]) => void;
88+
selectedTimes: [number, number];
89+
setSelectedTimes: (times: [number, number]) => void;
90+
name: string;
91+
setName: (name: string) => void;
9292
}
9393

94-
export function DayTimeSelector({
95-
minRange, maxRange, step, selectedDays, setSelectedDays, selectedTimes, setSelectedTimes, name, setName }: DayTimeSelectorProps) {
96-
97-
const daysOfWeek = ["M", "T", "W", "R", "F", "S", "U"];
98-
99-
const handleDayToggle = (day: string) => {
100-
const updatedDays = selectedDays.includes(day)
101-
? selectedDays.filter((d) => d !== day)
102-
: [...selectedDays, day];
103-
104-
setSelectedDays(updatedDays);
105-
};
106-
107-
const handleTimeChange = (values: number | number[]) => {
108-
const newTimes = values as [number, number];
109-
setSelectedTimes(newTimes);
110-
};
111-
112-
return (
113-
<DayTimeFilterContainer>
114-
<NameRow>
115-
<p><strong>Name</strong></p>
116-
<NameInput
117-
type="text"
118-
placeholder="Break Name"
119-
value={name}
120-
onChange={(e) => setName(e.target.value)}
121-
/>
122-
</NameRow>
123-
124-
<DayRow>
125-
<p><strong>Day</strong></p>
126-
<FilterContainer>
127-
{daysOfWeek.map((day) => (
128-
<FilterRow key={"day-" + day}>
129-
<CheckboxInput
130-
type="checkbox"
131-
id={"day-" + day}
132-
checked={selectedDays.includes(day)}
133-
onChange={() => handleDayToggle(day)}
134-
/>
135-
<CheckboxLabel htmlFor={"day-" + day}>{day}</CheckboxLabel>
136-
</FilterRow>
137-
))}
138-
</FilterContainer>
139-
</DayRow>
140-
<Column>
141-
<p><strong>Time</strong></p>
142-
<RangeFilterContainer>
143-
<StyledRangeWrapper>
144-
<Slider
145-
range
146-
min={minRange}
147-
max={maxRange}
148-
value={selectedTimes}
149-
marks={{
150-
10.5: {
151-
style: {},
152-
label: intToTime(
153-
selectedTimes[0]
154-
),
155-
},
156-
157-
22: {
158-
label:
159-
intToTime(
160-
selectedTimes[1]
161-
)
162-
},
163-
}}
164-
step={step}
165-
vertical={false}
166-
allowCross={false}
167-
onChange={handleTimeChange}
168-
// style={{ width: "100%" }}
169-
/>
170-
</StyledRangeWrapper>
171-
</RangeFilterContainer>
172-
</Column>
173-
</DayTimeFilterContainer>
174-
);
94+
export function DayTimeSelector({
95+
minRange, maxRange, step, selectedDays, setSelectedDays, selectedTimes, setSelectedTimes, name, setName }: DayTimeSelectorProps) {
96+
97+
const daysOfWeek = ["M", "T", "W", "R", "F", "S", "U"];
98+
99+
const handleDayToggle = (day: string) => {
100+
const updatedDays = selectedDays.includes(day)
101+
? selectedDays.filter((d) => d !== day)
102+
: [...selectedDays, day];
103+
104+
setSelectedDays(updatedDays);
105+
};
106+
107+
const handleTimeChange = (values: number | number[]) => {
108+
const newTimes = values as [number, number];
109+
setSelectedTimes(newTimes);
110+
};
111+
112+
return (
113+
<DayTimeFilterContainer>
114+
<NameRow>
115+
<p><strong>Name</strong></p>
116+
<NameInput
117+
type="text"
118+
placeholder="Break Name"
119+
value={name}
120+
onChange={(e) => setName(e.target.value)}
121+
/>
122+
</NameRow>
123+
124+
<DayRow>
125+
<p><strong>Day</strong></p>
126+
<FilterContainer>
127+
{daysOfWeek.map((day) => (
128+
<FilterRow key={"day-" + day}>
129+
<CheckboxInput
130+
type="checkbox"
131+
id={"day-" + day}
132+
checked={selectedDays.includes(day)}
133+
onChange={() => handleDayToggle(day)}
134+
/>
135+
<CheckboxLabel htmlFor={"day-" + day}>{day}</CheckboxLabel>
136+
</FilterRow>
137+
))}
138+
</FilterContainer>
139+
</DayRow>
140+
<Column>
141+
<p><strong>Time</strong></p>
142+
<RangeFilterContainer>
143+
<StyledRangeWrapper>
144+
<Slider
145+
range
146+
min={minRange}
147+
max={maxRange}
148+
value={selectedTimes}
149+
marks={{
150+
10.5: {
151+
style: {},
152+
label: intToTime(
153+
selectedTimes[0]
154+
),
155+
},
156+
157+
22: {
158+
label:
159+
intToTime(
160+
selectedTimes[1]
161+
)
162+
},
163+
}}
164+
step={step}
165+
vertical={false}
166+
allowCross={false}
167+
onChange={handleTimeChange}
168+
// style={{ width: "100%" }}
169+
/>
170+
</StyledRangeWrapper>
171+
</RangeFilterContainer>
172+
</Column>
173+
</DayTimeFilterContainer>
174+
);
175175
}

0 commit comments

Comments
 (0)