Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 52 additions & 35 deletions src/components/classes/edit/content/class-general-section.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"use client";

import { useEffect } from "react";
import { useWatch } from "react-hook-form";

import { CLASS_CATEGORIES } from "@/components/classes/constants";
import { FormInputField } from "@/components/form/FormInput";
import { FormSelectField } from "@/components/form/FormSelect";
Expand All @@ -22,9 +25,20 @@ import { FormError } from "@/components/form/FormLayout";

export function ClassGeneralSection() {
const {
form: { control },
form: { control, setValue, getValues },
} = useClassForm();

const category = useWatch({ control, name: "category" });
const isExercise = category?.includes("Exercise") ?? false;

useEffect(() => {
if (isExercise && !getValues("levelRange")) {
setValue("levelRange", [1, 4], { shouldValidate: true, shouldDirty: true });
} else if (!isExercise && getValues("levelRange")) {
setValue("levelRange", null, { shouldValidate: true, shouldDirty: true });
}
}, [isExercise, setValue, getValues]);

return (
<Card className="w-full">
<CardHeader>
Expand Down Expand Up @@ -65,42 +79,45 @@ export function ClassGeneralSection() {
/>
</FieldGroup>

<FormFieldController control={control} name="levelRange">
{({ onChange, value, ...field }) => (
<>
<FieldLabel className="w-full flex justify-between items-end">
<span>
Levels <LabelRequiredMarker />
</span>
<span className="text-sm">
Level {value[0]} {value[0] !== value[1] && `- ${value[1]}`}
</span>
</FieldLabel>

<div>
<Slider
min={1}
max={4}
step={1}
value={value}
onValueChange={onChange}
className="my-2"
{...field}
/>

{/* Labels for each level */}
<div className="flex justify-between text-xs text-muted-foreground">
<span>Level 1</span>
<span>Level 2</span>
<span>Level 3</span>
<span>Level 4</span>
{isExercise && (
<FormFieldController control={control} name="levelRange">
{({ onChange, value, ...field }) => (
<>
<FieldLabel className="w-full flex justify-between items-end">
<span>
Levels <LabelRequiredMarker />
</span>
<span className="text-sm">
Level {value?.[0] ?? 1}{" "}
{value?.[0] !== value?.[1] && `- ${value?.[1] ?? 4}`}
</span>
</FieldLabel>

<div>
<Slider
min={1}
max={4}
step={1}
value={value ?? [1, 4]}
onValueChange={onChange}
className="my-2"
{...field}
/>

{/* Labels for each level */}
<div className="flex justify-between text-xs text-muted-foreground">
<span>Level 1</span>
<span>Level 2</span>
<span>Level 3</span>
<span>Level 4</span>
</div>
</div>
</div>

<FormError />
</>
)}
</FormFieldController>
<FormError />
</>
)}
</FormFieldController>
)}

<FormTextareaField
control={control}
Expand Down
8 changes: 4 additions & 4 deletions src/components/classes/edit/hooks/use-class-form-submit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ export function useClassFormSubmit({

if (
!isEditing ||
initial.levelRange[0] !== levelRange[0] ||
initial.levelRange[1] !== levelRange[1]
initial.levelRange?.[0] !== levelRange?.[0] ||
initial.levelRange?.[1] !== levelRange?.[1]
) {
updatedValues.lowerLevel = levelRange[0]!;
updatedValues.upperLevel = levelRange[1]!;
updatedValues.lowerLevel = levelRange?.[0] ?? null;
updatedValues.upperLevel = levelRange?.[1] ?? null;
}

return updatedValues;
Expand Down
4 changes: 2 additions & 2 deletions src/components/classes/edit/hooks/use-class-upsert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ export function useClassUpsert({
const createdId = await createClass({
termId,
name: payload.name!,
lowerLevel: payload.lowerLevel!,
upperLevel: payload.upperLevel!,
lowerLevel: payload.lowerLevel ?? null,
upperLevel: payload.upperLevel ?? null,
category: payload.category!,
subcategory: payload.subcategory ?? undefined,
image: payload.image ?? undefined,
Expand Down
28 changes: 23 additions & 5 deletions src/components/classes/edit/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,32 @@ export const ClassEditSchema = z
meetingURL: asNullishField(z.url("Please enter a valid meeting url.")),
category: z.string().nonempty("Please fill out this field."),
subcategory: asNullishField(z.string()),
levelRange: z.array(z.int().min(1).max(4)).length(2),
levelRange: z.array(z.int().min(1).max(4)).length(2).nullish(),
schedules: z.array(ScheduleEditSchema),
image: z.string().nullable(),
})
.refine((val) => val.levelRange[0]! <= val.levelRange[1]!, {
error: "The upper level must be greater than the lower level",
path: ["levelRange"],
});
.refine(
(val) => {
if (!val.levelRange) return true;
return val.levelRange[0]! <= val.levelRange[1]!;
},
{
error: "The upper level must be greater than the lower level",
path: ["levelRange"],
},
)
.refine(
(val) => {
if (val.category.includes("Exercise")) {
return !!val.levelRange;
}
return true;
},
{
error: "Levels are required for exercise classes",
path: ["levelRange"],
},
);
export type ClassEditSchemaType = z.infer<typeof ClassEditSchema>;
export type ClassEditSchemaInput = z.input<typeof ClassEditSchema>;
export type ClassEditSchemaOutput = z.output<typeof ClassEditSchema>;
2 changes: 1 addition & 1 deletion src/components/classes/edit/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function classToFormValues(
category: c?.category ?? "",
subcategory: c?.subcategory ?? "",
image: (config ? buildImageUrl(config, c?.image) : undefined) ?? null,
levelRange: [c?.lowerLevel ?? 1, c?.upperLevel ?? 4],
levelRange: c?.lowerLevel && c?.upperLevel ? [c.lowerLevel, c.upperLevel] : null,
schedules:
c?.schedules.map((s) => ({
id: s.id,
Expand Down
20 changes: 11 additions & 9 deletions src/components/classes/list/components/class-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,17 @@ export function ClassCard({
/>

<div className="flex flex-col items-start">
<TypographySmall className="text-primary-muted mb-1">
{classData.lowerLevel === classData.upperLevel ? (
<>Level {classData.lowerLevel}</>
) : (
<>
Level {classData.lowerLevel}-{classData.upperLevel}
</>
)}
</TypographySmall>
{classData.lowerLevel && classData.upperLevel ? (
<TypographySmall className="text-primary-muted mb-1">
{classData.lowerLevel === classData.upperLevel ? (
<>Level {classData.lowerLevel}</>
) : (
<>
Level {classData.lowerLevel}-{classData.upperLevel}
</>
)}
</TypographySmall>
) : null}

<TypographyRegBold className="mb-0.5">
{classData.name}
Expand Down
8 changes: 4 additions & 4 deletions src/models/api/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const CreateClass = z.object({
name: z.string().nonempty(),
description: z.string().optional(),
meetingURL : z.url().optional(),
lowerLevel: z.int().min(1).max(4),
upperLevel: z.int().min(1).max(4),
lowerLevel: z.int().min(1).max(4).nullish(),
upperLevel: z.int().min(1).max(4).nullish(),
category: z.string(),
subcategory: z.string().optional(),
schedules: z.array(CreateSchedule).default([]),
Expand All @@ -29,8 +29,8 @@ export const UpdateClass = z.object({
meetingURL: z.url().nullish(),
category: z.string().optional(),
subcategory: z.string().nullish(),
lowerLevel: z.int().optional(),
upperLevel: z.int().optional(),
lowerLevel: z.int().nullish(),
upperLevel: z.int().nullish(),
addedSchedules: z.array(CreateSchedule).default([]),
updatedSchedules: z.array(UpdateSchedule).default([]),
deletedSchedules: z.array(z.uuid()).default([]),
Expand Down
4 changes: 2 additions & 2 deletions src/models/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export type Class = {
meetingURL?: string;
category: string;
subcategory?: string;
lowerLevel: number;
upperLevel: number;
lowerLevel: number | null;
upperLevel: number | null;
schedules: Schedule[];
createdAt: Date;
updatedAt: Date;
Expand Down
6 changes: 6 additions & 0 deletions src/server/db/migrations/0008_strong_saracen.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ALTER TABLE "course" DROP CONSTRAINT "chk_lower_level_bounds";--> statement-breakpoint
ALTER TABLE "course" DROP CONSTRAINT "chk_upper_level_bounds";--> statement-breakpoint
ALTER TABLE "course" ALTER COLUMN "lower_level" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "course" ALTER COLUMN "upper_level" DROP NOT NULL;--> statement-breakpoint
ALTER TABLE "course" ADD CONSTRAINT "chk_lower_level_bounds" CHECK ("course"."lower_level" IS NULL OR ("course"."lower_level" >= 1 AND "course"."lower_level" <= 4));--> statement-breakpoint
ALTER TABLE "course" ADD CONSTRAINT "chk_upper_level_bounds" CHECK ("course"."upper_level" IS NULL OR ("course"."upper_level" >= 1 AND "course"."upper_level" <= 4));
Loading