Repository: UnlockedLabs/UnlockEdv2
Pull request #820: feat: add functionality so facility admin can edit an existing section per ticket 48
Author: login: carddev81
Labels: name: frontend
name: Backend
Created: 2025-04-01T03:02:47Z | Merged: 2025-04-08T19:11:37Z

## Description of the change

Added the functionality to modify class section details. This change included some html, javascript, and backend golang coding modifications and additions. 

NOTE: Scheduling section is currently disabled for edit.

- **Related issues**: Link to Asana task: https://app.asana.com/0/1209460078641109/1209485911445029/f

## Screenshot(s)
[example.webm](https://github.com/user-attachments/assets/0da2b80e-9400-48db-82d2-b45a8d751393)



Diff:
diff --git a/backend/src/database/program_classes.go b/backend/src/database/program_classes.go
index c8b93b4c7..47e69ba09 100644
--- a/backend/src/database/program_classes.go
+++ b/backend/src/database/program_classes.go
@@ -2,11 +2,13 @@ package database
 
 import (
 	"UnlockEdv2/src/models"
+
+	"gorm.io/gorm"
 )
 
 func (db *DB) GetClassByID(id int) (*models.ProgramClass, error) {
 	content := &models.ProgramClass{}
-	if err := db.First(content, "id = ?", id).Error; err != nil {
+	if err := db.Preload("Events").First(content, "id = ?", id).Error; err != nil {
 		return nil, newNotFoundDBError(err, "program classes")
 	}
 	return content, nil
@@ -29,17 +31,37 @@ func (db *DB) CreateProgramClass(content *models.ProgramClass) (*models.ProgramC
 	if err != nil {
 		return nil, newCreateDBError(err, "create program classes validation error")
 	}
-	if err := db.Create(content).Error; err != nil {
+	if err := db.Create(&content).Error; err != nil {
 		return nil, newCreateDBError(err, "program classes")
 	}
 	return content, nil
 }
 
-func (db *DB) UpdateProgramClass(content *models.ProgramClass, ids []int) (*models.ProgramClass, error) {
-	if err := db.Model(&models.ProgramClass{}).Where("id IN ?", ids).Updates(content).Error; err != nil {
+func (db *DB) UpdateProgramClass(content *models.ProgramClass, id int) (*models.ProgramClass, error) {
+	existing := &models.ProgramClass{}
+	if err := db.Preload("Events").First(existing, "id = ?", id).Error; err != nil {
+		return nil, newNotFoundDBError(err, "program classes")
+	}
+	models.UpdateStruct(existing, content)
+	if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&existing).Error; err != nil {
 		return nil, newUpdateDBError(err, "program classes")
 	}
-	return content, nil
+	return existing, nil
+}
+
+func (db *DB) GetTotalEnrollmentsByClassID(id int) (int64, error) {
+	var count int64
+	if err := db.Model(&models.ProgramClassEnrollment{}).Where("class_id = ? and enrollment_status = 'Enrolled'", id).Count(&count).Error; err != nil {
+		return 0, NewDBError(err, "program_class_enrollments")
+	}
+	return count, nil
+}
+
+func (db *DB) UpdateProgramClasses(classMap map[string]interface{}, ids []int) error {
+	if err := db.Model(&models.ProgramClass{}).Where("id IN ?", ids).Updates(classMap).Error; err != nil {
+		return newUpdateDBError(err, "program classes")
+	}
+	return nil
 }
 
 func (db *DB) GetProgramClassDetailsByID(id int, args *models.QueryContext) ([]models.ProgramClassDetail, error) {
diff --git a/backend/src/handlers/classes_handler.go b/backend/src/handlers/classes_handler.go
index 5c116caf6..eb325b342 100644
--- a/backend/src/handlers/classes_handler.go
+++ b/backend/src/handlers/classes_handler.go
@@ -14,7 +14,8 @@ func (srv *Server) registerClassesRoutes() []routeDef {
 		{"GET /api/program-classes/{class_id}", srv.handleGetClass, false, axx},
 		{"GET /api/program-classes", srv.handleIndexClassesForFacility, false, axx},
 		{"POST /api/programs/{id}/classes", srv.handleCreateClass, true, axx},
-		{"PATCH /api/program-classes", srv.handleUpdateClass, true, axx},
+		{"PATCH /api/program-classes", srv.handleUpdateClasses, true, axx},
+		{"PATCH /api/program-classes/{id}", srv.handleUpdateClass, true, axx},
 	}
 }
 
@@ -79,6 +80,29 @@ func (srv *Server) handleCreateClass(w http.ResponseWriter, r *http.Request, log
 }
 
 func (srv *Server) handleUpdateClass(w http.ResponseWriter, r *http.Request, log sLog) error {
+	id, err := strconv.Atoi(r.PathValue("id"))
+	if err != nil {
+		return newInvalidIdServiceError(err, "class ID")
+	}
+	class := models.ProgramClass{}
+	if err := json.NewDecoder(r.Body).Decode(&class); err != nil {
+		return newJSONReqBodyServiceError(err)
+	}
+	enrolled, err := srv.Db.GetTotalEnrollmentsByClassID(id)
+	if err != nil {
+		return newDatabaseServiceError(err)
+	}
+	if enrolled > class.Capacity {
+		return writeJsonResponse(w, http.StatusBadRequest, "Cannot update class until unenrolling residents")
+	}
+	updated, err := srv.Db.UpdateProgramClass(&class, id)
+	if err != nil {
+		return newDatabaseServiceError(err)
+	}
+	return writeJsonResponse(w, http.StatusOK, updated)
+}
+
+func (srv *Server) handleUpdateClasses(w http.ResponseWriter, r *http.Request, log sLog) error {
 	ids := r.URL.Query()["id"]
 	classIDs := make([]int, 0, len(ids))
 	for _, id := range ids {
@@ -87,13 +111,13 @@ func (srv *Server) handleUpdateClass(w http.ResponseWriter, r *http.Request, log
 		}
 	}
 	defer r.Body.Close()
-	class := models.ProgramClass{}
-	if err := json.NewDecoder(r.Body).Decode(&class); err != nil {
+	classMap := make(map[string]interface{})
+	if err := json.NewDecoder(r.Body).Decode(&classMap); err != nil {
 		return newJSONReqBodyServiceError(err)
 	}
-	updated, err := srv.Db.UpdateProgramClass(&class, classIDs)
+	err := srv.Db.UpdateProgramClasses(classMap, classIDs)
 	if err != nil {
 		return newDatabaseServiceError(err)
 	}
-	return writeJsonResponse(w, http.StatusOK, updated)
+	return writeJsonResponse(w, http.StatusOK, "Successfully updated program class")
 }
diff --git a/backend/src/handlers/classes_handler_test.go b/backend/src/handlers/classes_handler_test.go
index 821523dba..8e0873b18 100644
--- a/backend/src/handlers/classes_handler_test.go
+++ b/backend/src/handlers/classes_handler_test.go
@@ -167,8 +167,8 @@ func TestHandleCreateClass(t *testing.T) {
 				if err != nil {
 					t.Fatalf("unable to get program class from db, error is %v", err)
 				}
-				if diff := cmp.Diff(class, &data.Data); diff != "" {
-					t.Errorf("handler returned unexpected results: %v", diff)
+				if class.ID != data.Data.ID {
+					t.Errorf("handler returned unexpected results got %v want %v", class.ID, data.Data.ID)
 				}
 			}
 		})
@@ -197,12 +197,12 @@ func TestHandleUpdateClass(t *testing.T) {
 			} else {
 				id = 1
 			}
-			programClass.FacilityID = 2 //update
+			programClass.Capacity = 10 //update
 			jsonForm, err := json.Marshal(programClass)
 			if err != nil {
 				t.Fatalf("unable to marshal form, error is %v", err)
 			}
-			req, err := http.NewRequest(http.MethodPatch, "/api/program-classes", bytes.NewBuffer(jsonForm))
+			req, err := http.NewRequest(http.MethodPatch, "/api/program-classes/{id}", bytes.NewBuffer(jsonForm))
 			if err != nil {
 				t.Fatalf("unable to create new request, error is %v", err)
 			}
diff --git a/backend/src/models/program_classes.go b/backend/src/models/program_classes.go
index af2bddf79..58ad009d4 100644
--- a/backend/src/models/program_classes.go
+++ b/backend/src/models/program_classes.go
@@ -30,7 +30,7 @@ type ProgramClass struct {
 	ArchivedAt     *time.Time  `json:"archived_at"`
 	StartDt        time.Time   `gorm:"type:date" json:"start_dt"`
 	EndDt          *time.Time  `gorm:"type:date" json:"end_dt"`
-	Status         ClassStatus `json:"class_status" gorm:"type:class_status" validate:"required"`
+	Status         ClassStatus `json:"status" gorm:"type:class_status" validate:"required"`
 	CreditHours    *int64      `json:"credit_hours"`
 
 	Program  *Program            `json:"program" gorm:"foreignKey:ProgramID;references:ID"`
diff --git a/frontend/src/Components/PageNav.tsx b/frontend/src/Components/PageNav.tsx
index 8f6e02ce8..2ae5c1b1d 100644
--- a/frontend/src/Components/PageNav.tsx
+++ b/frontend/src/Components/PageNav.tsx
@@ -2,14 +2,11 @@ import { useEffect, useRef } from 'react';
 import { useAuth, canSwitchFacility } from '@/useAuth';
 import { Bars3Icon, BuildingOffice2Icon } from '@heroicons/react/24/solid';
 import ULIComponent from '@/Components/ULIComponent.tsx';
-import { Facility, TitleHandler } from '@/common';
-import { useLocation, useMatches } from 'react-router-dom';
+import { Facility, RouteTitleHandler, TitleHandler } from '@/common';
+import { useMatches } from 'react-router-dom';
 import API from '@/api/api';
 import { usePageTitle } from '@/Context/AuthLayoutPageTitleContext';
-
-interface CustomTitle {
-    title?: string;
-}
+import { resolveTitle } from '@/routeLoaders';
 
 export default function PageNav({
     showOpenMenu,
@@ -24,11 +21,10 @@ export default function PageNav({
     const detailsRef = useRef<HTMLDetailsElement>(null);
     const matches = useMatches();
     const currentRoute = matches[matches.length - 1];
-    const pageTitle = (currentRoute?.handle as TitleHandler)?.title;
-    const location = useLocation() as { state: CustomTitle }; //added for custom titles
-    const customTitle = location.state?.title ? location.state?.title : '';
+    const routeData = currentRoute?.data as TitleHandler;
+    const routeHandle = currentRoute?.handle as RouteTitleHandler<TitleHandler>;
+    const pageTitle = resolveTitle(routeHandle, routeData);
     const { pageTitle: authLayoutPageTitle, setPageTitle } = usePageTitle();
-
     useEffect(() => {
         const closeDropdown = ({ target }: MouseEvent) => {
             if (
@@ -82,9 +78,7 @@ export default function PageNav({
                 <h1>
                     {pageTitle == 'Library Viewer'
                         ? authLayoutPageTitle
-                        : customTitle === ''
-                          ? pageTitle
-                          : customTitle}
+                        : pageTitle}
                 </h1>
             </div>
             {user && canSwitchFacility(user) ? (
diff --git a/frontend/src/Components/TitleManager.tsx b/frontend/src/Components/TitleManager.tsx
index e7bdcd2ca..4cff6fbcd 100644
--- a/frontend/src/Components/TitleManager.tsx
+++ b/frontend/src/Components/TitleManager.tsx
@@ -1,6 +1,7 @@
 import { useMatches, useLocation } from 'react-router-dom';
 import { useEffect } from 'react';
-import { TitleHandler } from '@/common';
+import { RouteTitleHandler, TitleHandler } from '@/common';
+import { resolveTitle } from '@/routeLoaders';
 
 export function TitleManager() {
     const matches = useMatches();
@@ -31,13 +32,18 @@ export function TitleManager() {
                 return;
             }
 
-            const activeHandle = matches
-                .filter((match) =>
-                    Boolean((match.handle as TitleHandler)?.title)
+            const activeMatch = [...matches]
+                .filter(
+                    (match) =>
+                        typeof match.handle === 'object' &&
+                        match.handle !== null &&
+                        'title' in match.handle
                 )
                 .pop();
-            const title =
-                (activeHandle?.handle as TitleHandler)?.title ?? 'UnlockEd';
+            const title = resolveTitle(
+                activeMatch?.handle as RouteTitleHandler<TitleHandler>,
+                activeMatch?.data as TitleHandler
+            );
             document.title = title + ' - UnlockEd';
         };
 
diff --git a/frontend/src/Components/inputs/DateInput.tsx b/frontend/src/Components/inputs/DateInput.tsx
index 005d9c4d6..e13d7956f 100644
--- a/frontend/src/Components/inputs/DateInput.tsx
+++ b/frontend/src/Components/inputs/DateInput.tsx
@@ -35,7 +35,7 @@ export function DateInput({
     const now = new Date();
     now.setHours(0, 0, 0, 0);
     const validateRules: Record<string, Validate<any, any>> = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
-    if (!allowPastDate) {
+    if (!allowPastDate && !disabled) {
         validateRules.noPast = (userVal: string) => {
             if (userVal === '') {
                 return;
diff --git a/frontend/src/Components/inputs/RRuleControl.tsx b/frontend/src/Components/inputs/RRuleControl.tsx
index dde11552c..f4817ce7b 100644
--- a/frontend/src/Components/inputs/RRuleControl.tsx
+++ b/frontend/src/Components/inputs/RRuleControl.tsx
@@ -6,7 +6,8 @@ import {
     FieldErrors,
     UseFormGetValues
 } from 'react-hook-form';
-
+import { useLoaderData } from 'react-router';
+import { ClassLoaderData } from '@/common';
 export interface RRuleFormHandle {
     createRule: () => { rule: string; duration: string };
     validate: () => boolean;
@@ -23,6 +24,7 @@ const weekdays = [
 ];
 
 interface RRuleControlProp {
+    disabled?: boolean; //TODO using flag for edits only can remove later
     onChange?: (isValid: boolean) => void;
     startDateRef: string;
     endDateRef: string;
@@ -32,9 +34,18 @@ interface RRuleControlProp {
 }
 export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
     function RRuleControl(
-        { onChange, startDateRef, endDateRef, getValues, errors, register },
+        {
+            disabled,
+            onChange,
+            startDateRef,
+            endDateRef,
+            getValues,
+            errors,
+            register
+        },
         ref
     ) {
+        const clsLoader = useLoaderData() as ClassLoaderData;
         const [timeErrors, setTimeErrors] = useState({
             startTime: '',
             endTime: '',
@@ -158,6 +169,54 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
             onChange?.(canCreateRule());
         }, [startTime, endTime, interval, frequency, byWeekDays, endOption]);
 
+        useEffect(() => {
+            let recurrenceRule;
+            let duration;
+            if (clsLoader.class) {
+                recurrenceRule = clsLoader.class.events?.[0]?.recurrence_rule;
+                duration = clsLoader.class.events?.[0]?.duration;
+            }
+            if (!recurrenceRule || !duration) return;
+            try {
+                const rule = RRule.fromString(recurrenceRule);
+                const dtStart = rule.options.dtstart;
+                const startTime = dtStart
+                    ? dtStart.toTimeString().slice(0, 5)
+                    : '';
+
+                let endTime = '';
+                const matches = /(\d+)h(\d+)m/.exec(duration);
+                if (matches && dtStart) {
+                    const hours = parseInt(matches[1]);
+                    const minutes = parseInt(matches[2]);
+                    const endDt = new Date(
+                        dtStart.getTime() + (hours * 60 + minutes) * 60000
+                    );
+                    endTime = endDt.toTimeString().slice(0, 5);
+                }
+
+                const frequency =
+                    Object.keys(RRule.FREQUENCIES).find(
+                        (key) =>
+                            RRule.FREQUENCIES[
+                                key as keyof typeof RRule.FREQUENCIES
+                            ] === rule.options.freq
+                    ) ?? 'WEEKLY';
+                const weekDays =
+                    (rule.options.byweekday as number[] | undefined)?.map(
+                        (n) => weekdays[n].value
+                    ) ?? [];
+                setFrequency(frequency);
+                setByWeekDays(weekDays);
+                setStartTime(startTime);
+                setEndTime(endTime);
+                setInterval(rule.options.interval ?? 1);
+                setEndOption(rule.options.until ? 'until' : 'never');
+            } catch (err) {
+                console.error('Failed to parse recurrenceRule', err);
+            }
+        }, []);
+
         function resetErrors() {
             const errors = {
                 startTime: '',
@@ -175,6 +234,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                     interfaceRef={startDateRef}
                     required
                     errors={errors}
+                    disabled={disabled}
                 />
                 <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
                     <div>
@@ -187,6 +247,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                                 resetErrors();
                             }}
                             className="input input-bordered w-full"
+                            disabled={disabled}
                         />
                         {timeErrors.startTime && (
                             <div className="text-error text-sm">
@@ -204,6 +265,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                                 resetErrors();
                             }}
                             className="input input-bordered w-full"
+                            disabled={disabled}
                         />
                         {timeErrors.endTime && (
                             <div className="text-error text-sm">
@@ -218,6 +280,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                         value={frequency}
                         onChange={(e) => setFrequency(e.target.value)}
                         className="select select-bordered w-full"
+                        disabled={disabled}
                     >
                         <option value="DAILY">Daily</option>
                         <option value="WEEKLY">Weekly</option>
@@ -232,6 +295,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                         min={1}
                         onChange={(e) => setInterval(parseInt(e.target.value))}
                         className="input input-bordered w-full"
+                        disabled={disabled}
                     />
                     {frequency === 'WEEKLY'
                         ? 'week(s)'
@@ -253,6 +317,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                                         resetErrors();
                                     }}
                                     className={`catalog-pill border ${byWeekDays.includes(value) ? 'bg-primary text-white' : 'border-grey-3 text-body-text'}`}
+                                    disabled={disabled}
                                 >
                                     {label}
                                 </button>
@@ -274,6 +339,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                             setEndOption(e.target.value as 'never' | 'until');
                         }}
                         className="select select-bordered w-full"
+                        disabled={disabled}
                     >
                         <option value="never">Never</option>
                         <option value="until">Until Date</option>
@@ -288,6 +354,7 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
                             getValues={getValues}
                             errors={errors}
                             required={endOption === 'until'}
+                            disabled={disabled}
                         />
                     </>
                 )}
diff --git a/frontend/src/Components/modals/ModifyClassModal.tsx b/frontend/src/Components/modals/ModifyClassModal.tsx
index 359070fae..c87b457a0 100644
--- a/frontend/src/Components/modals/ModifyClassModal.tsx
+++ b/frontend/src/Components/modals/ModifyClassModal.tsx
@@ -68,7 +68,7 @@ const ModifyClassModal = forwardRef(function (
         const updatedStatus = ClassStatusMap[action!];
 
         const resp = await API.patch(`program-classes?id=${program_class.id}`, {
-            class_status: updatedStatus
+            status: updatedStatus
         });
         if (resp.success) setSelectedStatus(updatedStatus);
 
@@ -88,7 +88,7 @@ const ModifyClassModal = forwardRef(function (
                 ref={ref}
                 type={TextModalType.Confirm}
                 title={title}
-                text={text(program_class.class_status) ?? ''}
+                text={text(program_class.status) ?? ''}
                 onSubmit={() => void onConfirm()}
                 onClose={close}
             />
diff --git a/frontend/src/Layouts/AuthenticatedLayout.tsx b/frontend/src/Layouts/AuthenticatedLayout.tsx
index 591be3090..431ac5d8e 100644
--- a/frontend/src/Layouts/AuthenticatedLayout.tsx
+++ b/frontend/src/Layouts/AuthenticatedLayout.tsx
@@ -2,12 +2,18 @@ import { useState } from 'react';
 import Navbar from '@/Components/Navbar';
 import { useMatches, UIMatch, Outlet, useLoaderData } from 'react-router-dom';
 import PageNav from '@/Components/PageNav';
-import { Facility, RouteLabel } from '@/common';
+import {
+    Facility,
+    RouteLabel,
+    RouteTitleHandler,
+    TitleHandler
+} from '@/common';
 import { PageTitleProvider } from '@/Context/AuthLayoutPageTitleContext';
 import WebsocketSession from '@/session_ws';
 import { useAuth } from '@/useAuth';
 import HelpCenter from '@/Pages/HelpCenter';
 import UnlockEdTour from '@/Components/UnlockEdTour';
+import { resolveTitle } from '@/routeLoaders';
 
 // Extend RouteMatch with custom RouteMeta
 interface CustomRouteMatch extends UIMatch {
@@ -20,9 +26,13 @@ export default function AuthenticatedLayout() {
         window.websocket = new WebsocketSession(user);
     }
     const matches = useMatches() as CustomRouteMatch[];
-    const currentMatch = matches.find((match) => match?.handle?.title);
     const facilities = useLoaderData() as Facility[] | null;
-    const title = currentMatch?.handle?.title ?? 'UnlockEd';
+
+    const currentRoute = matches[matches.length - 1];
+    const routeData = currentRoute?.data as TitleHandler;
+    const routeHandle = currentRoute?.handle as RouteTitleHandler<TitleHandler>;
+    const title = resolveTitle(routeHandle, routeData) ?? 'UnlockEd';
+
     // We have three states we need to factor for.
     // 1. If the nav is open & pinned (Large screens only & uses lg:drawer-open)
     // 2. If the nav is open & not pinned (Large screens only)
diff --git a/frontend/src/Pages/ClassManagementForm.tsx b/frontend/src/Pages/ClassManagementForm.tsx
index 889862837..51451ea0d 100644
--- a/frontend/src/Pages/ClassManagementForm.tsx
+++ b/frontend/src/Pages/ClassManagementForm.tsx
@@ -1,4 +1,4 @@
-import { useNavigate, useParams } from 'react-router-dom';
+import { useLoaderData, useNavigate, useParams } from 'react-router-dom';
 import {
     DropdownInput,
     NumberInput,
@@ -8,7 +8,7 @@ import {
     CancelButton,
     CloseX
 } from '@/Components/inputs';
-import { PrgClassStatus, ToastState } from '@/common';
+import { PrgClassStatus, Class, ToastState, ClassLoaderData } from '@/common';
 import { SubmitHandler, useForm } from 'react-hook-form';
 import { useState, useRef, useEffect } from 'react';
 import API from '@/api/api';
@@ -19,27 +19,14 @@ import {
     RRuleFormHandle
 } from '@/Components/inputs/RRuleControl';
 
-interface ClassInputs {
-    capacity: number;
-    name: string;
-    instructor_name: string;
-    description: string;
-    start_dt: Date;
-    end_dt: Date;
-    class_id: number;
-    room: string;
-    class_status: string;
-    credit_hours: number;
-    recurrence_rule: string;
-}
-
 export default function ClassManagementForm() {
+    const clsLoader = useLoaderData() as ClassLoaderData;
     const [rruleIsValid, setRruleIsValid] = useState(false);
     const rruleFormRef = useRef<RRuleFormHandle>(null);
     const [calendarRule, setCalendarRule] = useState('');
     const [calendarDuration, setCalendarDuration] = useState('');
     const [calendarEventTitle, setCalendarEventTitle] = useState('');
-    const { id } = useParams<{ id: string }>();
+    const { id, class_id } = useParams<{ id: string; class_id?: string }>();
     const navigate = useNavigate();
     const [showCalendar, setShowCalendar] = useState(false);
     const [errorMessage, setErrorMessage] = useState('');
@@ -51,10 +38,15 @@ export default function ClassManagementForm() {
         watch,
         reset,
         formState: { errors }
-    } = useForm<ClassInputs>();
+    } = useForm<Class>({
+        defaultValues: {
+            events: [{ room: '', recurrence_rule: '', duration: '' }]
+        }
+    });
+
     const nameValue = watch('name');
     const [canOpenCalendar, setCanOpenCalendar] = useState(false);
-    const onSubmit: SubmitHandler<ClassInputs> = async (data) => {
+    const onSubmit: SubmitHandler<Class> = async (data) => {
         setErrorMessage('');
         const rruleString = rruleFormRef.current?.createRule();
         if (rruleString?.rule === '') {
@@ -62,30 +54,44 @@ export default function ClassManagementForm() {
         }
         const formattedJson = {
             ...data,
+            ...(class_id && { id: Number(class_id) }),
             start_dt: new Date(data.start_dt),
-            end_dt: new Date(data.end_dt),
+            end_dt: data.end_dt ? new Date(data.end_dt) : null,
             capacity: Number(data.capacity),
             credit_hours: Number(data.credit_hours),
             events: [
                 {
+                    ...(class_id && { id: Number(data?.events[0].id) }),
+                    ...(class_id && { class_id: Number(class_id) }),
                     recurrence_rule: rruleString?.rule,
-                    room: data.room,
+                    room: data.events[0].room,
                     duration: rruleString?.duration
                 }
             ]
         };
-        const response = await API.post(
-            `programs/${id}/classes`,
-            formattedJson
-        );
+
+        const response = class_id
+            ? await API.patch(`program-classes/${class_id}`, formattedJson)
+            : await API.post(`programs/${id}/classes`, formattedJson);
         if (!response.success) {
-            toaster('Failed to create class', ToastState.error);
+            const toasterMsg =
+                class_id && response.message.includes('unenrolling')
+                    ? 'Cannot update class until unenrolling residents'
+                    : class_id
+                      ? 'Failed to update class'
+                      : 'Failed to create class';
+            toaster(toasterMsg, ToastState.error);
             console.log(
-                `error occurred while trying to create class, error message: ${response.message}`
+                `error occurred while trying to create/update class, error message: ${response.message}`
             );
             return;
         }
-        toaster('Class created successfully', ToastState.success);
+        toaster(
+            class_id
+                ? 'Class updated successfully'
+                : 'Class created successfully',
+            ToastState.success
+        );
         reset();
         navigate(`/programs/${id}`);
     };
@@ -93,6 +99,25 @@ export default function ClassManagementForm() {
         setCanOpenCalendar(!!nameValue && rruleIsValid);
     }, [nameValue, rruleIsValid]);
 
+    useEffect(() => {
+        if (!class_id) return;
+        if (clsLoader.class) {
+            setEditFormValues(clsLoader.class);
+        }
+    }, [id, class_id, reset]);
+
+    function setEditFormValues(editCls: Class) {
+        const { credit_hours, ...values } = editCls;
+        reset({
+            ...values,
+            ...(credit_hours > 0 ? { credit_hours } : {}),
+            start_dt: new Date(editCls.start_dt).toISOString().split('T')[0],
+            end_dt: editCls.end_dt
+                ? new Date(editCls.end_dt).toISOString().split('T')[0]
+                : editCls.end_dt
+        });
+    }
+
     return (
         <div className="p-4 px-5">
             <form
@@ -142,7 +167,7 @@ export default function ClassManagementForm() {
                         <TextInput
                             label="Room"
                             register={register}
-                            interfaceRef="room"
+                            interfaceRef="events.0.room"
                             required
                             length={255}
                             errors={errors}
@@ -189,14 +214,16 @@ export default function ClassManagementForm() {
                             errors={errors}
                             register={register}
                             onChange={setRruleIsValid}
+                            disabled={!!class_id}
                         />
                         <DropdownInput
                             label="Status"
                             register={register}
                             enumType={PrgClassStatus}
-                            interfaceRef="class_status"
+                            interfaceRef="status"
                             required
                             errors={errors}
+                            disabled={!!class_id}
                         />
                     </div>
                     <div className="col-span-4 flex justify-end gap-4 mt-4">
diff --git a/frontend/src/Pages/ProgramOverviewDashboard.tsx b/frontend/src/Pages/ProgramOverviewDashboard.tsx
index 67b79b4ae..a23a3d813 100644
--- a/frontend/src/Pages/ProgramOverviewDashboard.tsx
+++ b/frontend/src/Pages/ProgramOverviewDashboard.tsx
@@ -1,7 +1,11 @@
 import { useParams } from 'react-router-dom';
 import { useEffect, useRef, useState } from 'react';
 import StatsCard from '@/Components/StatsCard';
-import { ArchiveBoxIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
+import {
+    ArchiveBoxIcon,
+    PlusCircleIcon,
+    PencilSquareIcon
+} from '@heroicons/react/24/outline';
 import Pagination from '@/Components/Pagination';
 import SearchBar from '@/Components/inputs/SearchBar';
 import DropdownControl from '@/Components/inputs/DropdownControl';
@@ -76,9 +80,8 @@ export default function ProgramOverview() {
         const unableToArchive = classes.filter(
             (program_class) =>
                 selectedClasses.includes(program_class.id) &&
-                (program_class.class_status === SelectedClassStatus.Active ||
-                    program_class.class_status ===
-                        SelectedClassStatus.Scheduled) &&
+                (program_class.status === SelectedClassStatus.Active ||
+                    program_class.status === SelectedClassStatus.Scheduled) &&
                 program_class.enrolled >= 1
         );
 
@@ -242,13 +245,7 @@ export default function ProgramOverview() {
                     ) : (
                         <button
                             className="button flex items-center"
-                            onClick={() =>
-                                navigate(`/programs/${id}/class`, {
-                                    state: {
-                                        title: `Program: ${program?.name}`
-                                    }
-                                })
-                            }
+                            onClick={() => navigate(`/programs/${id}/class`)}
                         >
                             <PlusCircleIcon className="w-4 h-4 mr-1" />
                             Add Class
@@ -303,20 +300,33 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(program_class) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(
-                                                        program_class.id
-                                                    )
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(program_class) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            program_class.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${program_class.id}`
+                                                    );
+                                                }}
                                             />
-                                        )}
+                                        </div>
                                     </td>
                                     <td>{program_class.facility_name}</td>
                                     <td>{program_class.instructor_name}</td>
@@ -358,7 +368,7 @@ export default function ProgramOverview() {
                                     </td>
                                     <td>
                                         <ClassStatus
-                                            status={program_class.class_status}
+                                            status={program_class.status}
                                             program_class={program_class}
                                             mutateClasses={mutateClasses}
                                         />
diff --git a/frontend/src/Routes/Programs.tsx b/frontend/src/Routes/Programs.tsx
index fd4d8335d..e87f26621 100644
--- a/frontend/src/Routes/Programs.tsx
+++ b/frontend/src/Routes/Programs.tsx
@@ -1,7 +1,7 @@
-import { getProgramData } from '@/routeLoaders';
+import { getProgramData, getProgramTitle } from '@/routeLoaders';
 import { DeclareAuthenticatedRoutes } from './Routes';
 import Programs from '@/Pages/ProgramManagement';
-import { FeatureAccess } from '@/common';
+import { FeatureAccess, TitleHandler } from '@/common';
 import { AdminRoles } from '@/useAuth';
 import ProgramOverviewDashboard from '@/Pages/ProgramOverviewDashboard';
 import CreateProgramPage from '@/Pages/CreateProgram';
@@ -36,10 +36,11 @@ export const AdminProgramRoutes = DeclareAuthenticatedRoutes(
             handle: { title: 'Program Overview Dashboard' }
         },
         {
-            path: 'programs/:id/class',
+            path: 'programs/:id/class/:class_id?',
+            loader: getProgramTitle,
             element: <ClassManagementForm />,
             handle: {
-                title: 'Class Details'
+                title: (data: TitleHandler) => data.title
             }
         },
         {
diff --git a/frontend/src/common.ts b/frontend/src/common.ts
index 4af82f172..b9a64a657 100644
--- a/frontend/src/common.ts
+++ b/frontend/src/common.ts
@@ -672,12 +672,24 @@ export interface Class {
     facility_id: number;
     facility_name: string;
     instructor_name: string;
+    name: string;
+    description: string;
     start_dt: string;
     end_dt: string;
-    class_status: SelectedClassStatus;
+    status: SelectedClassStatus;
     enrolled: number;
     capacity: number;
+    credit_hours: number;
     archived_at: string | null;
+    events: ProgramClassEvent[];
+}
+
+export interface ProgramClassEvent {
+    id: number;
+    class_id: number;
+    duration: string;
+    room: string;
+    recurrence_rule: string;
 }
 
 export enum SelectedClassStatus {
@@ -767,10 +779,23 @@ export interface OpenContentItem {
     provider_name?: string;
     channel_title?: string;
 }
+
+export interface ClassLoaderData extends TitleHandler {
+    class?: Class;
+}
+
+export type RouteTitleHandler<T> = TitleHandler | DynamicTitleHandler<T>;
+
 export interface TitleHandler {
     title: string;
     path?: string[];
 }
+
+export interface DynamicTitleHandler<T> {
+    title: (data: T) => string;
+    path?: string[];
+}
+
 export interface ActivityMapData {
     date: string;
     total_time: string;
diff --git a/frontend/src/routeLoaders.ts b/frontend/src/routeLoaders.ts
index 92b3d5d86..03746c610 100644
--- a/frontend/src/routeLoaders.ts
+++ b/frontend/src/routeLoaders.ts
@@ -9,7 +9,12 @@ import {
     ActivityMapData,
     UserRole,
     Option,
-    ProviderPlatform
+    ProviderPlatform,
+    Program,
+    RouteTitleHandler,
+    Class,
+    ServerResponseOne,
+    ClassLoaderData
 } from './common';
 import API from './api/api';
 import { fetchUser } from './useAuth';
@@ -143,3 +148,39 @@ export const getProviderPlatforms: LoaderFunction = async () => {
     }
     return json({ providerPlatforms: [] });
 };
+
+export const getProgramTitle: LoaderFunction = async ({
+    params
+}): Promise<ClassLoaderData> => {
+    const { id, class_id } = params;
+    let cls: Class | undefined;
+    let programName = 'Class Details';
+    if (id) {
+        const resp = await API.get(`programs/${id}`);
+        if (resp.success) {
+            programName = 'Program: ' + (resp.data as Program).name;
+        }
+    }
+    if (class_id) {
+        const classResp = (await API.get(
+            `program-classes/${class_id}`
+        )) as ServerResponseOne<Class>;
+        if (classResp.success) {
+            cls = classResp.data;
+        }
+    }
+    return {
+        title: programName,
+        class: cls
+    };
+};
+
+export function resolveTitle<T>(
+    handle: RouteTitleHandler<T> | undefined,
+    data: T
+): string {
+    if (!handle) return '';
+    return typeof handle.title === 'function'
+        ? handle.title(data)
+        : handle.title;
+}


Reviews:
user: login: jtucholski
body: I left a few comments on the files here in the PR. Can you respond to those? I don't know if changes are needed.

However...we do have PR #821 out there to contend with which renames sections to classes. 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T20:37:06Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T21:38:19Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T21:40:20Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T21:47:48Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T21:49:50Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T22:12:05Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T22:18:17Z
user: login: jtucholski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T13:56:29Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-06T15:17:25Z
user: login: PThorpe92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T16:40:50Z
user: login: jtucholski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T16:52:33Z
user: login: jtucholski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T16:53:34Z
user: login: PThorpe92
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-07T17:01:38Z
user: login: calisio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T17:11:28Z
user: login: jtucholski
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T17:14:03Z
user: login: calisio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T17:16:06Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:17:34Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:17:42Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T18:25:54Z
user: login: PThorpe92
body: This LGTM as soon as the `status` bug is fixed :+1:  
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-07T18:50:11Z
user: login: calisio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T19:18:59Z
user: login: calisio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T19:31:13Z
user: login: calisio
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T19:33:07Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T19:45:35Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T20:03:42Z
user: login: carddev81
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T20:17:27Z
user: login: jtucholski
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T15:35:16Z

Review comments:
diff_hunk: @@ -29,17 +31,37 @@ func (db *DB) CreateProgramSection(content *models.ProgramSection) (*models.Prog
 	if err != nil {
 		return nil, newCreateDBError(err, "create program sections validation error")
 	}
-	if err := db.Create(content).Error; err != nil {
+	if err := db.Create(&content).Error; err != nil {
 		return nil, newCreateDBError(err, "program sections")
 	}
 	return content, nil
 }
 
-func (db *DB) UpdateProgramSection(content *models.ProgramSection, ids []int) (*models.ProgramSection, error) {
-	if err := db.Model(&models.ProgramSection{}).Where("id IN ?", ids).Updates(content).Error; err != nil {
+func (db *DB) UpdateProgramSection(content *models.ProgramSection, id int) (*models.ProgramSection, error) {
+	existing := &models.ProgramSection{}
+	if err := db.Preload("Events").First(existing, "id = ?", id).Error; err != nil {
+		return nil, newNotFoundDBError(err, "program sections")
+	}
+	models.UpdateStruct(existing, content)
+	if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&existing).Error; err != nil {
 		return nil, newUpdateDBError(err, "program sections")
 	}
-	return content, nil
+	return existing, nil
+}
+
+func (db *DB) GetTotalEnrollmentsBySectionID(id int) (int64, error) {
+	var count int64
+	if err := db.Model(&models.ProgramSectionEnrollment{}).Where("section_id = ?", id).Count(&count).Error; err != nil {
path: backend/src/database/program_sections.go
user: login: jtucholski
body: Do we need to filter this by enrollment status? It looks like it's being used to determine the enrollment vs. capacity figure.
created_at: 2025-04-02T19:22:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,19 +79,41 @@ func (srv *Server) handleCreateSection(w http.ResponseWriter, r *http.Request, l
 }
 
 func (srv *Server) handleUpdateSection(w http.ResponseWriter, r *http.Request, log sLog) error {
-	ids := r.URL.Query()["id"]
-	sectionIDs := make([]int, 0, len(ids))
-	for _, id := range ids {
-		if sectionID, err := strconv.Atoi(id); err == nil {
-			sectionIDs = append(sectionIDs, sectionID)
+	id, err := strconv.Atoi(r.PathValue("id"))
+	if err != nil {
+		return newInvalidIdServiceError(err, "section ID")
+	}
+	if id == 0 {
+		ids := r.URL.Query()["id"]
+		sectionIDs := make([]int, 0, len(ids))
+		for _, id := range ids {
+			if sectionID, err := strconv.Atoi(id); err == nil {
+				sectionIDs = append(sectionIDs, sectionID)
+			}
+		}
+		defer r.Body.Close()
+		sectionMap := make(map[string]interface{})
+		if err := json.NewDecoder(r.Body).Decode(&sectionMap); err != nil {
+			return newJSONReqBodyServiceError(err)
+		}
+		err := srv.Db.UpdateProgramSections(sectionMap, sectionIDs)
path: backend/src/handlers/sections_handler.go
user: login: jtucholski
body: Do you need to worry about checking to make sure capacity doesn't go below the enrollment threshold or is not that not a likely scenario when performing the operation in bulk?
created_at: 2025-04-02T19:31:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -149,7 +153,7 @@ export default function ProgramOverview() {
             ableToArchiveSections.some((section) => section.id === id)
         );
         const idString = '?id=' + selectedAbleToArchiveSections.join('&id=');
-        const resp = await API.patch(`program-sections${idString}`, {
+        const resp = await API.patch(`program-sections/0${idString}`, {
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: jtucholski
body: I can see this `0` easily getting removed from the code on accident.
created_at: 2025-04-02T20:12:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,18 +305,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(section) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(section.id)
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(section) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            section.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${section.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: jtucholski
body: When I first saw this I thought it was a good find but it introduces the expectation that the user follows the expected flow. If the user navigates enter the URL directly in the address bar to the URL in their browser, the Program name isn't included and instead they see "Class Details." Is that expected?
created_at: 2025-04-02T20:16:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,17 +31,37 @@ func (db *DB) CreateProgramSection(content *models.ProgramSection) (*models.Prog
 	if err != nil {
 		return nil, newCreateDBError(err, "create program sections validation error")
 	}
-	if err := db.Create(content).Error; err != nil {
+	if err := db.Create(&content).Error; err != nil {
 		return nil, newCreateDBError(err, "program sections")
 	}
 	return content, nil
 }
 
-func (db *DB) UpdateProgramSection(content *models.ProgramSection, ids []int) (*models.ProgramSection, error) {
-	if err := db.Model(&models.ProgramSection{}).Where("id IN ?", ids).Updates(content).Error; err != nil {
+func (db *DB) UpdateProgramSection(content *models.ProgramSection, id int) (*models.ProgramSection, error) {
+	existing := &models.ProgramSection{}
+	if err := db.Preload("Events").First(existing, "id = ?", id).Error; err != nil {
+		return nil, newNotFoundDBError(err, "program sections")
+	}
+	models.UpdateStruct(existing, content)
+	if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&existing).Error; err != nil {
 		return nil, newUpdateDBError(err, "program sections")
 	}
-	return content, nil
+	return existing, nil
+}
+
+func (db *DB) GetTotalEnrollmentsBySectionID(id int) (int64, error) {
+	var count int64
+	if err := db.Model(&models.ProgramSectionEnrollment{}).Where("section_id = ?", id).Count(&count).Error; err != nil {
path: backend/src/database/program_sections.go
user: login: carddev81
body: yes, this will need to filter by enrollment status.  I will make the change.
created_at: 2025-04-02T21:38:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,19 +79,41 @@ func (srv *Server) handleCreateSection(w http.ResponseWriter, r *http.Request, l
 }
 
 func (srv *Server) handleUpdateSection(w http.ResponseWriter, r *http.Request, log sLog) error {
-	ids := r.URL.Query()["id"]
-	sectionIDs := make([]int, 0, len(ids))
-	for _, id := range ids {
-		if sectionID, err := strconv.Atoi(id); err == nil {
-			sectionIDs = append(sectionIDs, sectionID)
+	id, err := strconv.Atoi(r.PathValue("id"))
+	if err != nil {
+		return newInvalidIdServiceError(err, "section ID")
+	}
+	if id == 0 {
+		ids := r.URL.Query()["id"]
+		sectionIDs := make([]int, 0, len(ids))
+		for _, id := range ids {
+			if sectionID, err := strconv.Atoi(id); err == nil {
+				sectionIDs = append(sectionIDs, sectionID)
+			}
+		}
+		defer r.Body.Close()
+		sectionMap := make(map[string]interface{})
+		if err := json.NewDecoder(r.Body).Decode(&sectionMap); err != nil {
+			return newJSONReqBodyServiceError(err)
+		}
+		err := srv.Db.UpdateProgramSections(sectionMap, sectionIDs)
path: backend/src/handlers/sections_handler.go
user: login: carddev81
body: This is the logic that runs only for archiving sections and will not execute for updating a section values. 
created_at: 2025-04-02T21:40:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,18 +305,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(section) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(section.id)
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(section) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            section.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${section.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: carddev81
body: ha, good find. I wasn't expecting the user to do that.  I can fix this but i'll have to think on it a bit. First thought---brief logic flow here: 

if 'Class Details'
    then change to Program: <fetch_name_from_db_call>
    
    
created_at: 2025-04-02T21:47:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -149,7 +153,7 @@ export default function ProgramOverview() {
             ableToArchiveSections.some((section) => section.id === id)
         );
         const idString = '?id=' + selectedAbleToArchiveSections.join('&id=');
-        const resp = await API.patch(`program-sections${idString}`, {
+        const resp = await API.patch(`program-sections/0${idString}`, {
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: carddev81
body: Can we talk about this one? 
created_at: 2025-04-02T21:49:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,17 +31,37 @@ func (db *DB) CreateProgramSection(content *models.ProgramSection) (*models.Prog
 	if err != nil {
 		return nil, newCreateDBError(err, "create program sections validation error")
 	}
-	if err := db.Create(content).Error; err != nil {
+	if err := db.Create(&content).Error; err != nil {
 		return nil, newCreateDBError(err, "program sections")
 	}
 	return content, nil
 }
 
-func (db *DB) UpdateProgramSection(content *models.ProgramSection, ids []int) (*models.ProgramSection, error) {
-	if err := db.Model(&models.ProgramSection{}).Where("id IN ?", ids).Updates(content).Error; err != nil {
+func (db *DB) UpdateProgramSection(content *models.ProgramSection, id int) (*models.ProgramSection, error) {
+	existing := &models.ProgramSection{}
+	if err := db.Preload("Events").First(existing, "id = ?", id).Error; err != nil {
+		return nil, newNotFoundDBError(err, "program sections")
+	}
+	models.UpdateStruct(existing, content)
+	if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&existing).Error; err != nil {
 		return nil, newUpdateDBError(err, "program sections")
 	}
-	return content, nil
+	return existing, nil
+}
+
+func (db *DB) GetTotalEnrollmentsBySectionID(id int) (int64, error) {
+	var count int64
+	if err := db.Model(&models.ProgramSectionEnrollment{}).Where("section_id = ?", id).Count(&count).Error; err != nil {
path: backend/src/database/program_sections.go
user: login: carddev81
body: @jtucholski pushed changes for this.
created_at: 2025-04-02T22:12:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,18 +305,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(section) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(section.id)
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(section) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            section.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${section.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: carddev81
body: @jtucholski is this something you would like me to fix? I know you asked the question 'is that expected'. No, the user navigating in that particular way was not expected, but the default value of 'Class Details' is expected when there is no custom title available.
created_at: 2025-04-02T22:18:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,18 +305,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(section) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(section.id)
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(section) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            section.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${section.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: jtucholski
body: There's nothing wrong with it saying Class Details other than I think Lisa asked for the requirement.  

But it raises the question if we should be relying on navigation state to affect what the user sees on the next page. I think we should try and make each page self-sufficient and not have it rely on the page the user came from.
created_at: 2025-04-03T13:56:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -301,18 +305,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(section) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(section.id)
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(section) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            section.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${section.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: carddev81
body: @jtucholski made the modifications for dynamically loading the title on page render versus relying on navigate
created_at: 2025-04-06T15:17:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +14,8 @@ func (srv *Server) registerClassesRoutes() []routeDef {
 		{"GET /api/program-classes/{class_id}", srv.handleGetClass, false, axx},
 		{"GET /api/program-classes", srv.handleIndexClassesForFacility, false, axx},
 		{"POST /api/programs/{id}/classes", srv.handleCreateClass, true, axx},
-		{"PATCH /api/program-classes", srv.handleUpdateClass, true, axx},
+		{"PATCH /api/program-classes", srv.handleUpdateClasses, true, axx},
+		{"PATCH /api/program-class/{id}", srv.handleUpdateClass, true, axx},
path: backend/src/handlers/classes_handler.go
user: login: PThorpe92
body: i know is going to change anyway soon but i cant help myself :)
`/api/program-classes` for multiple and `/api/program-classes/{id}` for singular 
created_at: 2025-04-07T16:40:50Z
author_association: MEMBER
diff_hunk: @@ -14,7 +14,8 @@ func (srv *Server) registerClassesRoutes() []routeDef {
 		{"GET /api/program-classes/{class_id}", srv.handleGetClass, false, axx},
 		{"GET /api/program-classes", srv.handleIndexClassesForFacility, false, axx},
 		{"POST /api/programs/{id}/classes", srv.handleCreateClass, true, axx},
-		{"PATCH /api/program-classes", srv.handleUpdateClass, true, axx},
+		{"PATCH /api/program-classes", srv.handleUpdateClasses, true, axx},
+		{"PATCH /api/program-class/{id}", srv.handleUpdateClass, true, axx},
path: backend/src/handlers/classes_handler.go
user: login: jtucholski
body: We'll have to consolidate to a single handler function then because we can't have both `/api/program-classes` and `api/program-classes/{id}`. The reason two paths exist was because one was used to perform a batch update (querystring the id values and switch status to archived) while the other performed a single more comprehensive update. 

Not great, but the single update performed more validation logic (capacity, enrollment) than the first one.
created_at: 2025-04-07T16:52:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,9 +26,14 @@ export default function AuthenticatedLayout() {
         window.websocket = new WebsocketSession(user);
     }
     const matches = useMatches() as CustomRouteMatch[];
-    const currentMatch = matches.find((match) => match?.handle?.title);
+    // const currentMatch = matches.find((match) => match?.handle?.title);
path: frontend/src/Layouts/AuthenticatedLayout.tsx
user: login: jtucholski
body: Can we remove this commented out line?
created_at: 2025-04-07T16:53:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +14,8 @@ func (srv *Server) registerClassesRoutes() []routeDef {
 		{"GET /api/program-classes/{class_id}", srv.handleGetClass, false, axx},
 		{"GET /api/program-classes", srv.handleIndexClassesForFacility, false, axx},
 		{"POST /api/programs/{id}/classes", srv.handleCreateClass, true, axx},
-		{"PATCH /api/program-classes", srv.handleUpdateClass, true, axx},
+		{"PATCH /api/program-classes", srv.handleUpdateClasses, true, axx},
+		{"PATCH /api/program-class/{id}", srv.handleUpdateClass, true, axx},
path: backend/src/handlers/classes_handler.go
user: login: PThorpe92
body: why not? it follows the paradigm of `/api/resources` for many and `/api/resources/{id}` for a singular.

EDIT: rich explained the discussion you guys had, I understand the context behind it but ig its gonna change anyway soon to `/api/programs/{id}/classes` & `/api/programs/{id}/classes/{c_id}` but I just want to be consistent 
created_at: 2025-04-07T17:01:38Z
author_association: MEMBER
diff_hunk: @@ -43,6 +43,7 @@ export default function EventCalendar({
         }
         const durationMs = parseDurationToMs(durationStr);
         const rule = RRule.fromString(recurrenceRule);
+        //rule.options.byweekday = rule.options.byweekday.map((idx) => idx + 1);
path: frontend/src/Components/EventCalendar.tsx
user: login: calisio
body: remove ?
created_at: 2025-04-07T17:11:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +14,8 @@ func (srv *Server) registerClassesRoutes() []routeDef {
 		{"GET /api/program-classes/{class_id}", srv.handleGetClass, false, axx},
 		{"GET /api/program-classes", srv.handleIndexClassesForFacility, false, axx},
 		{"POST /api/programs/{id}/classes", srv.handleCreateClass, true, axx},
-		{"PATCH /api/program-classes", srv.handleUpdateClass, true, axx},
+		{"PATCH /api/program-classes", srv.handleUpdateClasses, true, axx},
+		{"PATCH /api/program-class/{id}", srv.handleUpdateClass, true, axx},
path: backend/src/handlers/classes_handler.go
user: login: jtucholski
body: Sorry my brain broke too and thought it would result in a failure to find a match between `/` and `/{id}`. I don't know why I had that block.
created_at: 2025-04-07T17:14:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -303,20 +301,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(program_class) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(
-                                                        program_class.id
-                                                    )
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(program_class) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            program_class.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${program_class.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: calisio
body: Just checking- should you be able to update a program if it is archived? In the case of section status, you are not allowed to update it anymore. I would assume it is the same for edit? I think you should disable this action so they are not allowed to edit once archived, but unsure if that was a requirement already previously discussed.
created_at: 2025-04-07T17:16:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -43,6 +43,7 @@ export default function EventCalendar({
         }
         const durationMs = parseDurationToMs(durationStr);
         const rule = RRule.fromString(recurrenceRule);
+        //rule.options.byweekday = rule.options.byweekday.map((idx) => idx + 1);
path: frontend/src/Components/EventCalendar.tsx
user: login: carddev81
body: removed
created_at: 2025-04-07T18:17:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,9 +26,14 @@ export default function AuthenticatedLayout() {
         window.websocket = new WebsocketSession(user);
     }
     const matches = useMatches() as CustomRouteMatch[];
-    const currentMatch = matches.find((match) => match?.handle?.title);
+    // const currentMatch = matches.find((match) => match?.handle?.title);
path: frontend/src/Layouts/AuthenticatedLayout.tsx
user: login: carddev81
body: removed
created_at: 2025-04-07T18:17:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -303,20 +301,38 @@ export default function ProgramOverview() {
                                     }
                                 >
                                     <td onClick={(e) => e.stopPropagation()}>
-                                        {isArchived(program_class) ? (
-                                            <div></div>
-                                        ) : (
-                                            <input
-                                                type="checkbox"
-                                                className="checkbox checkbox-sm"
-                                                checked={isSelected}
-                                                onChange={() =>
-                                                    handleToggleRow(
-                                                        program_class.id
-                                                    )
+                                        <div className="flex items-center gap-2">
+                                            {isArchived(program_class) ? (
+                                                <div></div>
+                                            ) : (
+                                                <input
+                                                    type="checkbox"
+                                                    className="checkbox checkbox-sm"
+                                                    checked={isSelected}
+                                                    onChange={() =>
+                                                        handleToggleRow(
+                                                            program_class.id
+                                                        )
+                                                    }
+                                                />
+                                            )}
+                                            <ULIComponent
+                                                icon={PencilSquareIcon}
+                                                iconClassName={
+                                                    '!w-5 !h-5 cursor-pointer'
                                                 }
+                                                onClick={() => {
+                                                    navigate(
+                                                        `/programs/${id}/class/${program_class.id}`,
+                                                        {
+                                                            state: {
+                                                                title: `Program: ${program?.name}`
+                                                            }
path: frontend/src/Pages/ProgramOverviewDashboard.tsx
user: login: carddev81
body: @jtucholski I believe we are removing the edit from this page altogether?  Should I add this logic here? or hold off?
created_at: 2025-04-07T18:25:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,48 +38,104 @@ export default function ClassManagementForm() {
         watch,
         reset,
         formState: { errors }
-    } = useForm<ClassInputs>();
+    } = useForm<Class>({
+        defaultValues: {
+            events: [{ room: '', recurrence_rule: '', duration: '' }]
+        }
+    });
+
     const nameValue = watch('name');
     const [canOpenCalendar, setCanOpenCalendar] = useState(false);
-    const onSubmit: SubmitHandler<ClassInputs> = async (data) => {
+    const onSubmit: SubmitHandler<Class> = async (data) => {
         setErrorMessage('');
         const rruleString = rruleFormRef.current?.createRule();
         if (rruleString?.rule === '') {
             return;
         }
         const formattedJson = {
             ...data,
+            ...(class_id && { id: Number(class_id) }),
             start_dt: new Date(data.start_dt),
             end_dt: new Date(data.end_dt),
             capacity: Number(data.capacity),
             credit_hours: Number(data.credit_hours),
             events: [
                 {
+                    ...(class_id && { id: Number(data?.events[0].id) }),
+                    ...(class_id && { class_id: Number(class_id) }),
                     recurrence_rule: rruleString?.rule,
-                    room: data.room,
+                    room: data.events[0].room,
                     duration: rruleString?.duration
                 }
             ]
         };
-        const response = await API.post(
-            `programs/${id}/classes`,
-            formattedJson
-        );
+
+        const response = class_id
+            ? await API.patch(`program-classes/${class_id}`, formattedJson)
+            : await API.post(`programs/${id}/classes`, formattedJson);
         if (!response.success) {
-            toaster('Failed to create class', ToastState.error);
+            const toasterMsg =
+                class_id && response.message.includes('unenrolling')
+                    ? 'Cannot update class until unenrolling residents'
+                    : class_id
+                      ? 'Failed to update class'
+                      : 'Failed to create class';
+            toaster(toasterMsg, ToastState.error);
             console.log(
-                `error occurred while trying to create class, error message: ${response.message}`
+                `error occurred while trying to create/update class, error message: ${response.message}`
             );
             return;
         }
-        toaster('Class created successfully', ToastState.success);
+        toaster(
+            class_id
+                ? 'Class updated successfully'
+                : 'Class created successfully',
+            ToastState.success
+        );
         reset();
         navigate(`/programs/${id}`);
     };
     useEffect(() => {
         setCanOpenCalendar(!!nameValue && rruleIsValid);
     }, [nameValue, rruleIsValid]);
 
+    useEffect(() => {
+        if (!class_id) return;
+
+        async function fetchClassDetails() {
+            try {
+                const response = (await API.get(
+                    `program-classes/${class_id}`
+                )) as ServerResponseOne<Class>;
+                if (!response.success) {
+                    toaster('Failed to fetch class data', ToastState.error);
+                    return;
+                }
+
+                const cls = response.data;
+                setClass(cls);
+                setEditFormValues(cls);
+            } catch (error) {
+                toaster('Error loading class details', ToastState.error);
+                console.error('Fetch error:', error);
+            }
+        }
+
+        void fetchClassDetails();
+    }, [id, class_id, reset]);
path: frontend/src/Pages/ClassManagementForm.tsx
user: login: calisio
body: Could this maybe be fetched and passed in through the route loader directly rather than used in this useEffect? That is typically how we fetch data when it does not need to be updated. If there was a reason why it needs to be in this file, let me know.. I might be missing something!
created_at: 2025-04-07T19:18:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -189,6 +232,9 @@ export default function ClassManagementForm() {
                             errors={errors}
                             register={register}
                             onChange={setRruleIsValid}
+                            recurrenceRule={cls?.events?.[0]?.recurrence_rule}
+                            duration={cls?.events?.[0]?.duration}
+                            disabled={!!class_id}
path: frontend/src/Pages/ClassManagementForm.tsx
user: login: calisio
body: if you do implement fetching the class through route loader, you would then actually be able to remove these and check for them directly in the RRuleControl. When you fetch and pass data through routeLoader, you are able to fetch it through its children using the `useLoaderData` hook in the same way you would fetch it in this parent component. This would be beneficial so you dont have to keep passing these down
created_at: 2025-04-07T19:31:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,6 +171,48 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
             onChange?.(canCreateRule());
         }, [startTime, endTime, interval, frequency, byWeekDays, endOption]);
 
+        useEffect(() => {
+            if (!recurrenceRule || !duration) return;
+            try {
+                const rule = RRule.fromString(recurrenceRule);
path: frontend/src/Components/inputs/RRuleControl.tsx
user: login: calisio
body: Okay, I'm a little bit lost on the purpose of this function here. Is this to set all of the default values? 
created_at: 2025-04-07T19:33:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,6 +171,48 @@ export const RRuleControl = forwardRef<RRuleFormHandle, RRuleControlProp>(
             onChange?.(canCreateRule());
         }, [startTime, endTime, interval, frequency, byWeekDays, endOption]);
 
+        useEffect(() => {
+            if (!recurrenceRule || !duration) return;
+            try {
+                const rule = RRule.fromString(recurrenceRule);
path: frontend/src/Components/inputs/RRuleControl.tsx
user: login: carddev81
body: Yes, this is used to set the values on the recurrence rule control form. The recurrence rule is parsed and values are used to set the form.
created_at: 2025-04-07T19:45:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,7 +14,8 @@ func (srv *Server) registerClassesRoutes() []routeDef {
 		{"GET /api/program-classes/{class_id}", srv.handleGetClass, false, axx},
 		{"GET /api/program-classes", srv.handleIndexClassesForFacility, false, axx},
 		{"POST /api/programs/{id}/classes", srv.handleCreateClass, true, axx},
-		{"PATCH /api/program-classes", srv.handleUpdateClass, true, axx},
+		{"PATCH /api/program-classes", srv.handleUpdateClasses, true, axx},
+		{"PATCH /api/program-class/{id}", srv.handleUpdateClass, true, axx},
path: backend/src/handlers/classes_handler.go
user: login: carddev81
body: made change
created_at: 2025-04-07T20:03:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,48 +38,104 @@ export default function ClassManagementForm() {
         watch,
         reset,
         formState: { errors }
-    } = useForm<ClassInputs>();
+    } = useForm<Class>({
+        defaultValues: {
+            events: [{ room: '', recurrence_rule: '', duration: '' }]
+        }
+    });
+
     const nameValue = watch('name');
     const [canOpenCalendar, setCanOpenCalendar] = useState(false);
-    const onSubmit: SubmitHandler<ClassInputs> = async (data) => {
+    const onSubmit: SubmitHandler<Class> = async (data) => {
         setErrorMessage('');
         const rruleString = rruleFormRef.current?.createRule();
         if (rruleString?.rule === '') {
             return;
         }
         const formattedJson = {
             ...data,
+            ...(class_id && { id: Number(class_id) }),
             start_dt: new Date(data.start_dt),
             end_dt: new Date(data.end_dt),
             capacity: Number(data.capacity),
             credit_hours: Number(data.credit_hours),
             events: [
                 {
+                    ...(class_id && { id: Number(data?.events[0].id) }),
+                    ...(class_id && { class_id: Number(class_id) }),
                     recurrence_rule: rruleString?.rule,
-                    room: data.room,
+                    room: data.events[0].room,
                     duration: rruleString?.duration
                 }
             ]
         };
-        const response = await API.post(
-            `programs/${id}/classes`,
-            formattedJson
-        );
+
+        const response = class_id
+            ? await API.patch(`program-classes/${class_id}`, formattedJson)
+            : await API.post(`programs/${id}/classes`, formattedJson);
         if (!response.success) {
-            toaster('Failed to create class', ToastState.error);
+            const toasterMsg =
+                class_id && response.message.includes('unenrolling')
+                    ? 'Cannot update class until unenrolling residents'
+                    : class_id
+                      ? 'Failed to update class'
+                      : 'Failed to create class';
+            toaster(toasterMsg, ToastState.error);
             console.log(
-                `error occurred while trying to create class, error message: ${response.message}`
+                `error occurred while trying to create/update class, error message: ${response.message}`
             );
             return;
         }
-        toaster('Class created successfully', ToastState.success);
+        toaster(
+            class_id
+                ? 'Class updated successfully'
+                : 'Class created successfully',
+            ToastState.success
+        );
         reset();
         navigate(`/programs/${id}`);
     };
     useEffect(() => {
         setCanOpenCalendar(!!nameValue && rruleIsValid);
     }, [nameValue, rruleIsValid]);
 
+    useEffect(() => {
+        if (!class_id) return;
+
+        async function fetchClassDetails() {
+            try {
+                const response = (await API.get(
+                    `program-classes/${class_id}`
+                )) as ServerResponseOne<Class>;
+                if (!response.success) {
+                    toaster('Failed to fetch class data', ToastState.error);
+                    return;
+                }
+
+                const cls = response.data;
+                setClass(cls);
+                setEditFormValues(cls);
+            } catch (error) {
+                toaster('Error loading class details', ToastState.error);
+                console.error('Fetch error:', error);
+            }
+        }
+
+        void fetchClassDetails();
+    }, [id, class_id, reset]);
path: frontend/src/Pages/ClassManagementForm.tsx
user: login: carddev81
body: Since there was a switch involved on calling fetchClassDetails I left this here. So when class_id is null or undefined the fetch won't happen.  Should I still make the change to add it to a loader?
created_at: 2025-04-07T20:17:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: carddev81
created_at: 2025-04-04T16:43:00Z
body: @jtucholski merge conflicts resolved + changes we talked about have been committed/pushed.
author_association: CONTRIBUTOR
user: login: calisio
created_at: 2025-04-07T17:20:27Z
body: For some reason, when I edit a class with no end date, and then update it, it sets the end date as Jan 1, 1970?

<img width="1238" alt="Screenshot 2025-04-07 at 1 19 58 PM" src="https://github.com/user-attachments/assets/9e8c4ab4-921e-4ae1-b579-56a98dac6ccd" />


author_association: CONTRIBUTOR
user: login: calisio
created_at: 2025-04-07T17:26:21Z
body: I am also now getting this bug- I added a class and cannot update the section status. This happens both when I have not edited the class, and also post-editing of the class. I have attached a screenshot of my terminal log with it
<img width="759" alt="Screenshot 2025-04-07 at 1 22 00 PM" src="https://github.com/user-attachments/assets/5307a9c0-ca9c-42a8-8bef-da9cce1a0ba0" />

author_association: CONTRIBUTOR
user: login: carddev81
created_at: 2025-04-07T18:27:20Z
body: oh man, good catch on the status column not updating...must have missed the name change in a migration?  Yup, it was in the last migration.  I'll fix this.
author_association: CONTRIBUTOR