Skip to content

Commit b2e38cd

Browse files
committed
issue-814 Enhance visibility control logic and UI to support dual date display
1 parent 65e2bca commit b2e38cd

3 files changed

Lines changed: 146 additions & 53 deletions

File tree

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/edit/VisibilityControl.tsx

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { RadioButton } from "primereact/radiobutton";
22
import { Control, Controller, UseFormSetValue } from "react-hook-form";
33

44
import { Assignment } from "@/types/assignment";
5-
import { convertDateToISO, formatUTCDateLocaleString } from "@/utils/date";
5+
import { convertDateToISO, formatUTCDateLocaleString, parseUTCDate } from "@/utils/date";
66

77
import { DateTimePicker } from "../../../../ui/DateTimePicker";
88

@@ -47,6 +47,32 @@ export const VisibilityControl = ({ control, watch, setValue }: VisibilityContro
4747

4848
const visibilityMode = getVisibilityMode();
4949

50+
const DAY_MS = 24 * 60 * 60 * 1000;
51+
52+
const handleVisibleOnChange = (val: string) => {
53+
setValue("visible_on", val);
54+
if (hiddenOn) {
55+
const newVisibleDate = parseUTCDate(val);
56+
const currentHiddenDate = parseUTCDate(hiddenOn);
57+
if (newVisibleDate >= currentHiddenDate) {
58+
const adjusted = new Date(newVisibleDate.getTime() + DAY_MS);
59+
setValue("hidden_on", convertDateToISO(adjusted));
60+
}
61+
}
62+
};
63+
64+
const handleHiddenOnChange = (val: string) => {
65+
setValue("hidden_on", val);
66+
if (visibleOn) {
67+
const newHiddenDate = parseUTCDate(val);
68+
const currentVisibleDate = parseUTCDate(visibleOn);
69+
if (newHiddenDate <= currentVisibleDate) {
70+
const adjusted = new Date(newHiddenDate.getTime() - DAY_MS);
71+
setValue("visible_on", convertDateToISO(adjusted));
72+
}
73+
}
74+
};
75+
5076
const handleModeChange = (mode: VisibilityMode) => {
5177
switch (mode) {
5278
case "hidden":
@@ -62,30 +88,33 @@ export const VisibilityControl = ({ control, watch, setValue }: VisibilityContro
6288
case "scheduled_visible":
6389
setValue("visible", false);
6490
setValue("hidden_on", null);
65-
// Keep visible_on or set to current date if null
6691
if (!visibleOn) {
67-
setValue("visible_on", convertDateToISO(new Date()));
92+
const startOfDay = new Date();
93+
startOfDay.setHours(0, 0, 0, 0);
94+
setValue("visible_on", convertDateToISO(startOfDay));
6895
}
6996
break;
7097
case "scheduled_hidden":
7198
setValue("visible", true);
7299
setValue("visible_on", null);
73-
// Keep hidden_on or set to current date if null
74100
if (!hiddenOn) {
75-
setValue("hidden_on", convertDateToISO(new Date()));
101+
const endOfDay = new Date();
102+
endOfDay.setHours(23, 59, 0, 0);
103+
setValue("hidden_on", convertDateToISO(endOfDay));
76104
}
77105
break;
78106
case "scheduled_period":
79107
setValue("visible", false);
80108
// Set both dates if not already set
81109
if (!visibleOn) {
82-
const now = new Date();
83-
setValue("visible_on", convertDateToISO(now));
110+
const startOfDay = new Date();
111+
startOfDay.setHours(0, 0, 0, 0);
112+
setValue("visible_on", convertDateToISO(startOfDay));
84113
}
85114
if (!hiddenOn) {
86-
const twoHoursLater = new Date();
87-
twoHoursLater.setHours(twoHoursLater.getHours() + 2);
88-
setValue("hidden_on", convertDateToISO(twoHoursLater));
115+
const endOfDay = new Date();
116+
endOfDay.setHours(23, 59, 0, 0);
117+
setValue("hidden_on", convertDateToISO(endOfDay));
89118
}
90119
break;
91120
}
@@ -192,7 +221,7 @@ export const VisibilityControl = ({ control, watch, setValue }: VisibilityContro
192221
render={({ field: dateField }) => (
193222
<DateTimePicker
194223
value={dateField.value}
195-
onChange={(val) => dateField.onChange(val)}
224+
onChange={(val) => handleVisibleOnChange(val)}
196225
utc
197226
/>
198227
)}
@@ -206,7 +235,7 @@ export const VisibilityControl = ({ control, watch, setValue }: VisibilityContro
206235
render={({ field: dateField }) => (
207236
<DateTimePicker
208237
value={dateField.value}
209-
onChange={(val) => dateField.onChange(val)}
238+
onChange={(val) => handleHiddenOnChange(val)}
210239
utc
211240
/>
212241
)}

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/list/VisibilityDropdown.tsx

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type VisibilityMode =
1616

1717
interface VisibilityStatus {
1818
text: string;
19+
secondLine?: string;
1920
color: string;
2021
icon: string;
2122
}
@@ -49,21 +50,35 @@ const getVisibilityStatus = (assignment: Assignment): VisibilityStatus => {
4950
const visibleDate = parseUTCDate(visible_on);
5051
const hiddenDate = parseUTCDate(hidden_on);
5152

53+
const fmt = (d: Date) =>
54+
d.toLocaleDateString(undefined, {
55+
month: "short",
56+
day: "numeric",
57+
hour: "2-digit",
58+
minute: "2-digit"
59+
});
60+
5261
if (now < visibleDate) {
5362
return {
54-
text: visibleDate.toLocaleDateString(undefined, {
55-
month: "short",
56-
day: "numeric",
57-
hour: "2-digit",
58-
minute: "2-digit"
59-
}),
63+
text: fmt(visibleDate),
64+
secondLine: fmt(hiddenDate),
6065
color: "#FFA500",
6166
icon: "pi pi-clock"
6267
};
6368
} else if (now >= visibleDate && now < hiddenDate) {
64-
return { text: "Visible", color: "#28A745", icon: "pi pi-eye" };
69+
return {
70+
text: fmt(visibleDate),
71+
secondLine: fmt(hiddenDate),
72+
color: "#28A745",
73+
icon: "pi pi-calendar"
74+
};
6575
} else {
66-
return { text: "Hidden", color: "#DC3545", icon: "pi pi-eye-slash" };
76+
return {
77+
text: fmt(visibleDate),
78+
secondLine: fmt(hiddenDate),
79+
color: "#DC3545",
80+
icon: "pi pi-calendar-times"
81+
};
6782
}
6883
}
6984

@@ -165,19 +180,25 @@ export const VisibilityDropdown = ({ assignment, onChange }: VisibilityDropdownP
165180
let newHiddenOn = hiddenOn;
166181

167182
if (newMode === "scheduled_visible" && !newVisibleOn) {
168-
newVisibleOn = convertDateToISO(new Date());
183+
const startOfDay = new Date();
184+
startOfDay.setHours(0, 0, 0, 0);
185+
newVisibleOn = convertDateToISO(startOfDay);
169186
}
170187
if (newMode === "scheduled_hidden" && !newHiddenOn) {
171-
newHiddenOn = convertDateToISO(new Date());
188+
const endOfDay = new Date();
189+
endOfDay.setHours(23, 59, 0, 0);
190+
newHiddenOn = convertDateToISO(endOfDay);
172191
}
173192
if (newMode === "scheduled_period") {
174193
if (!newVisibleOn) {
175-
newVisibleOn = convertDateToISO(new Date());
194+
const startOfDay = new Date();
195+
startOfDay.setHours(0, 0, 0, 0);
196+
newVisibleOn = convertDateToISO(startOfDay);
176197
}
177198
if (!newHiddenOn) {
178-
const twoHoursLater = new Date();
179-
twoHoursLater.setHours(twoHoursLater.getHours() + 2);
180-
newHiddenOn = convertDateToISO(twoHoursLater);
199+
const endOfDay = new Date();
200+
endOfDay.setHours(23, 59, 0, 0);
201+
newHiddenOn = convertDateToISO(endOfDay);
181202
}
182203
}
183204

@@ -189,15 +210,37 @@ export const VisibilityDropdown = ({ assignment, onChange }: VisibilityDropdownP
189210
onChange(assignment, values);
190211
};
191212

213+
const DAY_MS = 24 * 60 * 60 * 1000;
214+
192215
const handleVisibleOnChange = (val: string) => {
216+
let newHiddenOn = hiddenOn;
217+
if (mode === "scheduled_period" && newHiddenOn) {
218+
const newVisibleDate = parseUTCDate(val);
219+
const currentHiddenDate = parseUTCDate(newHiddenOn);
220+
if (newVisibleDate >= currentHiddenDate) {
221+
const adjusted = new Date(newVisibleDate.getTime() + DAY_MS);
222+
newHiddenOn = convertDateToISO(adjusted);
223+
setHiddenOn(newHiddenOn);
224+
}
225+
}
193226
setVisibleOn(val);
194-
const values = computeValues(mode, val, hiddenOn);
227+
const values = computeValues(mode, val, newHiddenOn);
195228
onChange(assignment, values);
196229
};
197230

198231
const handleHiddenOnChange = (val: string) => {
232+
let newVisibleOn = visibleOn;
233+
if (mode === "scheduled_period" && newVisibleOn) {
234+
const newHiddenDate = parseUTCDate(val);
235+
const currentVisibleDate = parseUTCDate(newVisibleOn);
236+
if (newHiddenDate <= currentVisibleDate) {
237+
const adjusted = new Date(newHiddenDate.getTime() - DAY_MS);
238+
newVisibleOn = convertDateToISO(adjusted);
239+
setVisibleOn(newVisibleOn);
240+
}
241+
}
199242
setHiddenOn(val);
200-
const values = computeValues(mode, visibleOn, val);
243+
const values = computeValues(mode, newVisibleOn, val);
201244
onChange(assignment, values);
202245
};
203246

@@ -211,18 +254,32 @@ export const VisibilityDropdown = ({ assignment, onChange }: VisibilityDropdownP
211254
fontWeight: 500,
212255
cursor: "pointer",
213256
display: "flex",
257+
flexDirection: status.secondLine ? "column" : "row",
214258
alignItems: "center",
215259
justifyContent: "center",
216-
gap: "4px"
260+
gap: status.secondLine ? "0px" : "4px"
217261
}}
218262
title="Click to change visibility"
219263
>
220-
<i className={status.icon} style={{ fontSize: "0.9rem" }} />
221-
<span>{status.text}</span>
222-
<i
223-
className="pi pi-chevron-down"
224-
style={{ fontSize: "0.7rem", marginLeft: "2px", opacity: 0.6 }}
225-
/>
264+
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
265+
<i className={status.icon} style={{ fontSize: "0.9rem" }} />
266+
<span>{status.text}</span>
267+
{!status.secondLine && (
268+
<i
269+
className="pi pi-chevron-down"
270+
style={{ fontSize: "0.7rem", marginLeft: "2px", opacity: 0.6 }}
271+
/>
272+
)}
273+
</div>
274+
{status.secondLine && (
275+
<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
276+
<span>{status.secondLine}</span>
277+
<i
278+
className="pi pi-chevron-down"
279+
style={{ fontSize: "0.7rem", marginLeft: "2px", opacity: 0.6 }}
280+
/>
281+
</div>
282+
)}
226283
</div>
227284
<OverlayPanel
228285
ref={overlayRef}

bases/rsptx/assignment_server_api/assignment_builder/src/components/routes/AssignmentBuilder/components/list/VisibilityStatusBadge.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { parseUTCDate } from "@/utils/date";
55

66
interface VisibilityStatus {
77
text: string;
8+
secondLine?: string;
89
color: string;
910
icon: string;
1011
tooltip: string;
@@ -23,34 +24,37 @@ const getVisibilityStatus = (assignment: Assignment): VisibilityStatus => {
2324
const visibleDate = parseUTCDate(visible_on);
2425
const hiddenDate = parseUTCDate(hidden_on);
2526

27+
const formatShort = (d: Date) =>
28+
d.toLocaleDateString(undefined, {
29+
month: "short",
30+
day: "numeric",
31+
hour: "2-digit",
32+
minute: "2-digit"
33+
});
34+
2635
if (now < visibleDate) {
27-
// Period hasn't started yet
2836
return {
29-
text: visibleDate.toLocaleDateString(undefined, {
30-
month: "short",
31-
day: "numeric",
32-
hour: "2-digit",
33-
minute: "2-digit"
34-
}),
37+
text: formatShort(visibleDate),
38+
secondLine: formatShort(hiddenDate),
3539
color: "#FFA500",
3640
icon: "pi pi-clock",
3741
tooltip: `Visible from ${visibleDate.toLocaleString()} to ${hiddenDate.toLocaleString()}`
3842
};
3943
} else if (now >= visibleDate && now < hiddenDate) {
40-
// Currently in visible period
4144
return {
42-
text: "Visible",
45+
text: formatShort(visibleDate),
46+
secondLine: formatShort(hiddenDate),
4347
color: "#28A745",
44-
icon: "pi pi-eye",
48+
icon: "pi pi-calendar",
4549
tooltip: `Visible until ${hiddenDate.toLocaleString()}`
4650
};
4751
} else {
48-
// Period has ended
4952
return {
50-
text: "Hidden",
53+
text: formatShort(visibleDate),
54+
secondLine: formatShort(hiddenDate),
5155
color: "#DC3545",
52-
icon: "pi pi-eye-slash",
53-
tooltip: ""
56+
icon: "pi pi-calendar-times",
57+
tooltip: `Period ended on ${hiddenDate.toLocaleString()}`
5458
};
5559
}
5660
}
@@ -157,8 +161,8 @@ export const VisibilityStatusBadge = ({ assignment }: VisibilityStatusBadgeProps
157161
<div
158162
className={
159163
hasTooltip
160-
? "visibility-status-badge flex align-items-center justify-content-center gap-1"
161-
: "flex align-items-center justify-content-center gap-1"
164+
? "visibility-status-badge flex flex-column align-items-center justify-content-center"
165+
: "flex flex-column align-items-center justify-content-center"
162166
}
163167
style={{
164168
fontSize: "0.85rem",
@@ -171,8 +175,11 @@ export const VisibilityStatusBadge = ({ assignment }: VisibilityStatusBadgeProps
171175
"data-pr-position": "top"
172176
})}
173177
>
174-
<i className={status.icon} style={{ fontSize: "0.9rem" }}></i>
175-
<span>{status.text}</span>
178+
<div className="flex align-items-center gap-1">
179+
<i className={status.icon} style={{ fontSize: "0.9rem" }}></i>
180+
<span>{status.text}</span>
181+
</div>
182+
{status.secondLine && <span>{status.secondLine}</span>}
176183
</div>
177184
</>
178185
);

0 commit comments

Comments
 (0)