diff --git a/.phpactor.json b/.phpactor.json
index c016e80..d009f91 100644
--- a/.phpactor.json
+++ b/.phpactor.json
@@ -1,4 +1,7 @@
{
"$schema": "/phpactor.schema.json",
- "language_server_psalm.enabled": true
+ "language_server_psalm.enabled": true,
+ "indexer.stub_paths": [
+ "%project_root%/vendor/php-stubs/wordpress-stubs"
+ ]
}
\ No newline at end of file
diff --git a/FORMS.md b/FORMS.md
new file mode 100644
index 0000000..f8ed7dd
--- /dev/null
+++ b/FORMS.md
@@ -0,0 +1,67 @@
+# Forms
+
+The reservation is mostly created by filling in a form. For that reason this plugin has it's own _form definition_ feature.
+
+The form definition is an array of element it contains. Each element has a *type*, for example: `input-text`, `input-reservation`, etc.
+
+Keep the form structure and content separate -> when working with the form, take time to figure, if you are working with structure, or existence of something within.
+
+## Submitting
+
+When user submits the form, the `RsvFormSubmitter.js` is called. It collects the values, which we describe in more detail, and then sends it using `fetch` as POST to `reservations/forms/{form_id}`.
+
+### Collecting
+
+The *collecting* is a process with `
-
- text('name', 'Name', '', true, $form_def['name'])
- ->text('definition.email_key', 'Email Key', "Name of the form field that holds the submitter's email address.", true, $definition['email_key'] ?? '')
- ->render();
- ?>
-
-
- text('name', 'Name', 'Name of the timetable that can be reserved.', true)
- ->number('block_size', 'Block length (minutes)', 'Duration of one reservable time block in minutes.', true, '', 1)
- ->email('maintainer_email', 'Maintainer Email', 'Email address to notify when a reservation requires confirmation.', false, '', 'maintainer_email_suggestions')
- ->submit('Add Timetable')
- ->render();
- ?>
-
-
-
-
-
-
-
-
-
-
+ RsvColumnLayout::split('1:2')
+ ->column(function () use ($existing_emails) {
+ echo RsvFormBuilder::create('add_timetable_form', get_rest_url(null, 'reservations/v1/timetable'), 'POST', 'Timetable created.')
+ ->heading('Add timetable')
+ ->datalist('maintainer_email_suggestions', $existing_emails)
+ ->text('name', 'Name', 'Name of the timetable that can be reserved.', true)
+ ->number('block_size', 'Block length (minutes)', 'Duration of one reservable time block in minutes.', true, '', 1)
+ ->email('maintainer_email', 'Maintainer Email', 'Email address to notify when a reservation requires confirmation.', false, '', 'maintainer_email_suggestions')
+ ->submit('Add Timetable')
+ ->render();
+ ?>
+
+ column(function () { ?>
+
-
-
-
- output();
+ ?>
+ date('date', 'First Date', 'Od kterého datumu platí tato kapacita.', true, new DateTime()->format('Y-m-d'));
- $form->group('Availability Range', fn($g) => $g
- ->time('start_time', 'Start')
- ->time('end_time', 'End')
- );
- $form->number('capacity', 'Capacity', 'How many reservations can overlap on the same time.', true, 1, 1);
- $form->number('min_lead_time_minutes', 'Minimum lead time (minutes)', 'How many minutes in advance must be the reservation created. This is useful if it takes some time to prepare the reservation.', true);
- $form->checkbox('requires_confirmation', 'Requires Confirmation?', 'If checked, all the reservations that overlap this capacity will require confirmation from maintainer. The maintainer will receive an email asking for the confirmation.', true);
- $form->custom('Is Repeating Event', function() {
- return '
-
-
If the capacity is available repeatingly. For example: repeat each monday every week.
- ';
- });
- $form->number('repeat_period_in_days', 'Repeat Period (days)', 'How many days between each repetition.', true);
- $form->custom('Apply to Days', function() {
- return '
-
-
-
-
Monday
-
Tuesday
-
Wednesday
-
Thursday
-
Friday
-
Saturday
-
Sunday
-
-
-
-
-
-
-
-
-
-
-
-
';
- });
- $form->submit('Create Capacity', 'button-primary', 'submit');
+ private function show_view(int $id): void {
+ $timetable = (new RsvTimetableService())->get($id);
+ if ($timetable === null) {
+ echo '
-function rsv_timetable_page() {
- if (isset($_GET['action'])) {
- if ($_GET['action'] === 'view' && isset($_GET['id'])) {
- rsv_timetable_view_page(intval($_GET['id']));
- return;
- } else if($_GET['action'] === 'edit' && isset($_GET['id'])) {
- rsv_timetable_edit_page(intval($_GET['id']));
- return;
- }
- // Deletion is intentionally not handled here: a state-changing GET is
- // CSRF-prone. Timetables are deleted via the nonce-authenticated REST
- // DELETE /timetable/{id} (see the "Trash" action in the list grid).
+
Define capacities for timetable.
+
+ create_capacity_form($id); ?>
+
+
+
+
+
+ date('date', 'First Date', 'Od kterého datumu platí tato kapacita.', true, new DateTime()->format('Y-m-d'));
+ $form->group('Availability Range', fn($g) => $g
+ ->time('start_time', 'Start')
+ ->time('end_time', 'End')
+ );
+ $form->number('capacity', 'Capacity', 'How many reservations can overlap on the same time.', true, 1, 1);
+ $form->number('min_lead_time_minutes', 'Minimum lead time (minutes)', 'How many minutes in advance must be the reservation created. This is useful if it takes some time to prepare the reservation.', true);
+ $form->checkbox('requires_confirmation', 'Requires Confirmation?', 'If checked, all the reservations that overlap this capacity will require confirmation from maintainer. The maintainer will receive an email asking for the confirmation.', true);
+ $form->custom('Is Repeating Event', function() {
+ return '
+
+
If the capacity is available repeatingly. For example: repeat each monday every week.
+ ';
+ });
+ $form->number('repeat_period_in_days', 'Repeat Period (days)', 'How many days between each repetition.', true);
+ $form->custom('Apply to Days', function() {
+ return '
+
with one row per field.
- * Hidden inputs and datalist elements are emitted before the table;
- * notices before, submit button after.
+ * Renders a self-contained "form-wrap" card: an optional heading and a
+ * wrapping a
with one row per field. Hidden inputs
+ * and datalist elements are emitted before the table; notices before the card,
+ * submit button after the fields.
*
* Usage:
- * echo RsvFormBuilder::create()
+ * echo RsvFormBuilder::create('settings', $action, 'PATCH', 'Saved.')
+ * ->heading('Settings')
* ->text('name', 'Name', required: true)
* ->email('email', 'Email')
* ->submit('Save');
*/
class RsvFormBuilder
{
- private string $form_id = "";
+ private string $form_id;
+
+ /** Where the form submits, and how RsvAdminForm should send it. */
+ private string $action;
+ private string $rest_method;
+ private string $success_msg;
+
+ /** Optional heading shown inside the card. */
+ private string $heading = '';
/** @var string[] Rendered before the table (hidden inputs, datalists). */
private array $before = [];
- /** @var string[] WP admin notice banners rendered before the table. */
+ /** @var string[] WP admin notice banners rendered before the card. */
private array $notices = [];
/** @var string[]
elements inside the table. */
@@ -33,9 +43,29 @@ class RsvFormBuilder
private function __construct() {}
- public static function create(string $id): static
+ /**
+ * @param string $rest_method Verb sent via data-method (POST, PUT, PATCH…).
+ * @param string $success_msg Message RsvAdminForm shows on success.
+ */
+ public static function create(
+ string $id,
+ string $action,
+ string $rest_method = 'POST',
+ string $success_msg = ''
+ ): static {
+ $builder = new static();
+ $builder->form_id = $id;
+ $builder->action = $action;
+ $builder->rest_method = $rest_method;
+ $builder->success_msg = $success_msg;
+ return $builder;
+ }
+
+ /** Heading shown inside the card, above the fields. */
+ public function heading(string $text): static
{
- return new static();
+ $this->heading = $text;
+ return $this;
}
// -------------------------------------------------------------------------
@@ -206,6 +236,13 @@ class RsvFormBuilder
return $this;
}
+ /** WordPress nonce field, emitted inside the form before the table. */
+ public function nonce(string $action, string $name): static
+ {
+ $this->before[] = wp_nonce_field($action, $name, true, false);
+ return $this;
+ }
+
/**
*