Group class term dates, single-class embed mode, and offering editing
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / Tests (PHP 8.1) (pull_request) Successful in 48s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 1m14s
CI / PHPStan (pull_request) Successful in 1m16s
CI / Tests (PHP 8.3) (pull_request) Successful in 37s
CI / Build Plugin Zip (pull_request) Has been skipped

Group class offerings now carry real dates: the add/edit form takes a
start date plus a sessions control (one-off, or weekly for N sessions;
the end date is computed as start + (N-1) weeks via
Offering::weeklyTermEnd). Dates are validated strictly (Y-m-d) and shown
in the offerings list and on the student-facing class card, including
the weekly session count.

[us_group_classes offering="<id>"] (block attribute offeringId, chosen
from a dropdown of active classes fetched from the public offerings
endpoint) restricts the page to a single class so the enrolment flow can
be embedded on a page dedicated to that class; a pinned class that is no
longer offered reports itself closed instead of falling back to the
catalog.

Offerings are now editable from the admin screen: an Edit button
prefills the shared add/edit form and saving posts usc_action=update.
Updates always preserve the original owner and currency, and non-admin
instructors can only load and update their own offerings. The form also
gains the previously missing description field and an Active toggle (the
admin-UI counterpart of the REST is_active flag) so an edit cannot wipe
data the form never collected.

Closes #59

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-07-05 23:18:59 -03:00
co-authored by Claude Fable 5
parent 9d8924132c
commit cde8704267
15 changed files with 653 additions and 42 deletions
+21
View File
@@ -46,6 +46,27 @@ class Offering {
public readonly ?int $id = null,
) {}
/**
* Normalise a submitted term date to canonical `Y-m-d`, or null when it is
* not a real calendar date. Round-trips through DateTimeImmutable so
* strings PHP would silently coerce (e.g. `2026-02-30`) are rejected.
*/
public static function normalizeDate( string $value ): ?string {
$date = \DateTimeImmutable::createFromFormat( '!Y-m-d', $value );
return false !== $date && $date->format( 'Y-m-d' ) === $value ? $date->format( 'Y-m-d' ) : null;
}
/**
* Last class date of a weekly term: the start date plus `$occurrences - 1`
* weeks. A one-off class (one occurrence) ends the day it starts.
*/
public static function weeklyTermEnd( string $termStart, int $occurrences ): string {
$weeks = max( 1, $occurrences ) - 1;
return ( new \DateTimeImmutable( $termStart ) )->modify( '+' . ( 7 * $weeks ) . ' days' )->format( 'Y-m-d' );
}
public static function fromRow( \stdClass $row ): self {
return new self(
instructorId: Val::int( $row->instructor_id ),