Files
unsupervised-scheduler/templates/admin/availability.php
T
thatguygriffandClaude Fable 5 b7d5e3039e
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / No Debug Code (pull_request) Failing after 2s
CI / Coding Standards (pull_request) Successful in 52s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
Split availability windows into bookable lesson-length slots with weekly calendar views
Availability windows were stored and served as a single bookable row, so a
9:00 AM-4:00 PM window showed to students as one giant slot and booking it
consumed the whole day; past and multi-day windows also leaked into the
booking page as nonsense entries.

- Split windows into consecutive lesson-length slots on save (REST and admin
  form); each chunk is independently bookable and weekly recurrence creates a
  series per chunk so "reserve this time weekly" holds the same hour each week
- Reject windows spanning multiple days or shorter than the lesson length
  (400 invalid_window)
- Never return slots whose start has passed from GET /availability
- Migrate pre-split rows: Plugin::boot re-runs the Installer on version change
  and AvailabilityRepository::splitOversizedWindows() rewrites unbooked
  same-day oversized windows in place
- Display all times in 12-hour AM/PM form (booking page, wp-admin lists,
  editor previews)
- Add a List | Week view toggle to the student booking page and the
  instructor availability page, with previous/next-week navigation honouring
  the site's start_of_week option (new WeekCalendar helper)

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 16:00:47 -03:00

167 lines
8.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
if (! defined('ABSPATH')) {
exit;
}
/**
* @var list<\Unsupervised\Schedular\Availability\AvailabilitySlot> $slots
* @var list<\Unsupervised\Schedular\Offering\Offering> $offeringChoices
* @var 'list'|'week' $view
* @var string $weekStart
* @var list<array{date: string, slots: list<\Unsupervised\Schedular\Availability\AvailabilitySlot>}> $weekDays
* @var string $prevWeek
* @var string $nextWeek
*/
$baseUrl = admin_url('admin.php?page=us-availability');
$deleteForm = static function (\Unsupervised\Schedular\Availability\AvailabilitySlot $slot): void {
?>
<form method="post" style="display:inline;">
<?php wp_nonce_field('usc_availability_action'); ?>
<input type="hidden" name="usc_action" value="delete">
<input type="hidden" name="slot_id" value="<?php echo esc_attr((string) $slot->id); ?>">
<button type="submit" class="button button-small button-link-delete">
<?php esc_html_e('Delete', 'unsupervised-schedular'); ?>
</button>
</form>
<?php
};
?>
<div class="wrap">
<h1><?php esc_html_e('My Availability', 'unsupervised-schedular'); ?></h1>
<h2><?php esc_html_e('Add Availability', 'unsupervised-schedular'); ?></h2>
<p><?php esc_html_e('The window must start and end on the same day. It is split into bookable slots of the chosen lesson length — for example, 9:00 AM4:00 PM with 60-minute lessons creates seven slots.', 'unsupervised-schedular'); ?></p>
<form method="post">
<?php wp_nonce_field('usc_availability_action'); ?>
<input type="hidden" name="usc_action" value="add">
<table class="form-table">
<tr>
<th><label for="start_dt"><?php esc_html_e('Start', 'unsupervised-schedular'); ?></label></th>
<td><input type="datetime-local" name="start_dt" id="start_dt" required></td>
</tr>
<tr>
<th><label for="end_dt"><?php esc_html_e('End', 'unsupervised-schedular'); ?></label></th>
<td><input type="datetime-local" name="end_dt" id="end_dt" required></td>
</tr>
<tr>
<th><label for="duration_minutes"><?php esc_html_e('Lesson length (minutes)', 'unsupervised-schedular'); ?></label></th>
<td>
<select name="duration_minutes" id="duration_minutes">
<option value="30">30</option>
<option value="60" selected>60</option>
</select>
</td>
</tr>
<tr>
<th><label for="offering_id"><?php esc_html_e('Offering', 'unsupervised-schedular'); ?></label></th>
<td>
<select name="offering_id" id="offering_id">
<option value="0"><?php esc_html_e('— Any private lesson —', 'unsupervised-schedular'); ?></option>
<?php foreach ($offeringChoices as $offering) : ?>
<option value="<?php echo esc_attr((string) $offering->id); ?>"><?php echo esc_html($offering->title); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th><?php esc_html_e('Repeat', 'unsupervised-schedular'); ?></th>
<td>
<label><input type="radio" name="recurrence" value="single" checked> <?php esc_html_e('One-off', 'unsupervised-schedular'); ?></label>
&nbsp;
<label><input type="radio" name="recurrence" value="weekly"> <?php esc_html_e('Weekly for', 'unsupervised-schedular'); ?></label>
<input type="number" name="weeks" min="1" max="52" value="12" style="width:5em;"> <?php esc_html_e('weeks', 'unsupervised-schedular'); ?>
</td>
</tr>
</table>
<?php submit_button(esc_html__('Add Availability', 'unsupervised-schedular')); ?>
</form>
<h2><?php esc_html_e('Current Slots', 'unsupervised-schedular'); ?></h2>
<ul class="subsubsub" style="margin-bottom:12px;">
<li>
<a href="<?php echo esc_url($baseUrl); ?>" <?php echo 'list' === $view ? 'class="current"' : ''; ?>><?php esc_html_e('List', 'unsupervised-schedular'); ?></a> |
</li>
<li>
<a href="<?php echo esc_url(add_query_arg('usc_view', 'week', $baseUrl)); ?>" <?php echo 'week' === $view ? 'class="current"' : ''; ?>><?php esc_html_e('Week', 'unsupervised-schedular'); ?></a>
</li>
</ul>
<div class="clear"></div>
<?php if ('week' === $view) : ?>
<p>
<a class="button" href="<?php echo esc_url(add_query_arg(['usc_view' => 'week', 'usc_week' => $prevWeek], $baseUrl)); ?>">&lsaquo; <?php esc_html_e('Previous week', 'unsupervised-schedular'); ?></a>
<strong style="margin:0 12px;">
<?php
/* translators: %s: date of the first day of the displayed week */
echo esc_html(sprintf(__('Week of %s', 'unsupervised-schedular'), (string) mysql2date('M j, Y', $weekStart)));
?>
</strong>
<a class="button" href="<?php echo esc_url(add_query_arg(['usc_view' => 'week', 'usc_week' => $nextWeek], $baseUrl)); ?>"><?php esc_html_e('Next week', 'unsupervised-schedular'); ?> &rsaquo;</a>
</p>
<table class="wp-list-table widefat fixed">
<thead>
<tr>
<?php foreach ($weekDays as $day) : ?>
<th><?php echo esc_html((string) mysql2date('D M j', $day['date'])); ?></th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<tr>
<?php foreach ($weekDays as $day) : ?>
<td style="vertical-align:top;">
<?php if (empty($day['slots'])) : ?>
<span aria-hidden="true">—</span>
<?php endif; ?>
<?php foreach ($day['slots'] as $slot) : ?>
<p style="margin:0 0 8px;">
<?php echo esc_html((string) mysql2date('g:i A', $slot->startDt) . '' . (string) mysql2date('g:i A', $slot->endDt)); ?><br>
<?php if ($slot->isBooked) : ?>
<em><?php esc_html_e('Booked', 'unsupervised-schedular'); ?></em>
<?php else : ?>
<?php $deleteForm($slot); ?>
<?php endif; ?>
</p>
<?php endforeach; ?>
</td>
<?php endforeach; ?>
</tr>
</tbody>
</table>
<?php elseif (empty($slots)) : ?>
<p><?php esc_html_e('No availability slots configured.', 'unsupervised-schedular'); ?></p>
<?php else : ?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th><?php esc_html_e('Start', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('End', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Length', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Status', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Actions', 'unsupervised-schedular'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($slots as $slot) : ?>
<tr>
<td><?php echo esc_html((string) mysql2date('M j, Y g:i A', $slot->startDt)); ?></td>
<td><?php echo esc_html((string) mysql2date('M j, Y g:i A', $slot->endDt)); ?></td>
<td><?php echo esc_html((string) $slot->durationMinutes . ' min'); ?></td>
<td><?php echo $slot->isBooked ? esc_html__('Booked', 'unsupervised-schedular') : esc_html__('Available', 'unsupervised-schedular'); ?></td>
<td>
<?php if (! $slot->isBooked) : ?>
<?php $deleteForm($slot); ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>