#26 - Loading animation + success message fix
This commit was merged in pull request #31.
This commit is contained in:
@@ -48,6 +48,12 @@ class RsvFormDefinitionController {
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin'],
|
||||
]);
|
||||
|
||||
register_rest_route($this->namespace, '/' . $this->resource_name . '/(?P<id>\d+)/submission/latest', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'latest_submit'],
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin'],
|
||||
]);
|
||||
|
||||
register_rest_route($this->namespace, '/' . $this->resource_name . '/(?P<id>\d+)', [
|
||||
[
|
||||
'methods' => 'GET',
|
||||
@@ -132,6 +138,12 @@ class RsvFormDefinitionController {
|
||||
return new WP_REST_Response(['html' => $html], 200);
|
||||
}
|
||||
|
||||
/** Most recent submission's rendered context, for the success-message preview. */
|
||||
function latest_submit(WP_REST_Request $request): WP_REST_Response {
|
||||
$data = (new RsvFormSubmitRepository())->latest_computed((int) $request->get_param('id'));
|
||||
return new WP_REST_Response(['data' => $data], 200);
|
||||
}
|
||||
|
||||
function update(WP_REST_Request $request): WP_REST_Response {
|
||||
$id = (int) $request->get_param('id');
|
||||
$repo = new RsvFormDefinitionRepository();
|
||||
|
||||
@@ -30,18 +30,6 @@ class RsvReservationController {
|
||||
'callback' => [$this, 'create'],
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin']
|
||||
]);
|
||||
|
||||
register_rest_route($this->namespace, '/' . $this->resource_name . '/(?P<id>\d+)/accept', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'accept_by_id'],
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin'],
|
||||
]);
|
||||
|
||||
register_rest_route($this->namespace, '/' . $this->resource_name . '/(?P<id>\d+)/refuse', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'refuse_by_id'],
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin'],
|
||||
]);
|
||||
}
|
||||
|
||||
function get_all(WP_REST_Request $request) {
|
||||
@@ -66,22 +54,4 @@ class RsvReservationController {
|
||||
$body = $request->get_json_params();
|
||||
return $service->create(RsvReservation::from_array($body));
|
||||
}
|
||||
|
||||
function accept_by_id(WP_REST_Request $request): WP_REST_Response {
|
||||
try {
|
||||
(new RsvTimetableReservationService())->accept_by_reservation_id((int) $request->get_param('id'));
|
||||
return new WP_REST_Response(['status' => 'accepted'], 200);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return new WP_REST_Response(['error' => $e->getMessage()], 404);
|
||||
}
|
||||
}
|
||||
|
||||
function refuse_by_id(WP_REST_Request $request): WP_REST_Response {
|
||||
try {
|
||||
(new RsvTimetableReservationService())->refuse_by_reservation_id((int) $request->get_param('id'));
|
||||
return new WP_REST_Response(['status' => 'refused'], 200);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return new WP_REST_Response(['error' => $e->getMessage()], 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,18 @@ class RsvTimetableReservationController {
|
||||
// refuse() validates against the database before changing state.
|
||||
'permission_callback' => [RsvRestPolicy::class, 'open'],
|
||||
]);
|
||||
|
||||
register_rest_route($this->namespace, '/timetable-reservation/(?P<id>\d+)/accept', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'accept_by_id'],
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin'],
|
||||
]);
|
||||
|
||||
register_rest_route($this->namespace, '/timetable-reservation/(?P<id>\d+)/refuse', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'refuse_by_id'],
|
||||
'permission_callback' => [RsvRestPolicy::class, 'admin'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function by_timetable(WP_REST_Request $request): WP_REST_Response {
|
||||
@@ -59,4 +71,26 @@ class RsvTimetableReservationController {
|
||||
return new WP_REST_Response(['error' => 'Invalid or expired confirmation code.'], 404);
|
||||
}
|
||||
}
|
||||
|
||||
function accept_by_id(WP_REST_Request $request) {
|
||||
try {
|
||||
$service = new RsvTimetableReservationService();
|
||||
$service->accept_by_id(intval($request->get_param('id')));
|
||||
|
||||
return new WP_REST_Response(['status' => 'accepted'], 200);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return new WP_REST_Response(['error' => 'Invalid or expired confirmation code.'], 404);
|
||||
}
|
||||
}
|
||||
|
||||
function refuse_by_id(WP_REST_Request $request) {
|
||||
try {
|
||||
$service = new RsvTimetableReservationService();
|
||||
$service->refuse_by_id(intval($request->get_param('id')));
|
||||
|
||||
return new WP_REST_Response(['status' => 'refused'], 200);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return new WP_REST_Response(['error' => 'Invalid or expired confirmation code.'], 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,27 @@ class RsvFormSubmitRepository {
|
||||
]);
|
||||
}
|
||||
|
||||
/** Store the derived template context (field values, slots, pricing) for a submission. */
|
||||
public function set_computed(int $id, array $computed): void {
|
||||
Db::update($this->table, ['computed' => json_encode($computed)], ['form_submit_id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The derived template context of the most recent submission for a form,
|
||||
* or null when the form has no submission carrying computed data.
|
||||
*
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
public function latest_computed(int $form_id): ?array {
|
||||
$value = Db::get_var(
|
||||
"SELECT computed FROM {$this->table}
|
||||
WHERE form_id = %d AND computed IS NOT NULL
|
||||
ORDER BY form_submit_id DESC LIMIT 1",
|
||||
[$form_id]
|
||||
);
|
||||
return $value === null ? null : json_decode($value, true);
|
||||
}
|
||||
|
||||
public function delete(int $id): void {
|
||||
Db::delete($this->table, ['form_submit_id' => $id]);
|
||||
}
|
||||
|
||||
@@ -88,8 +88,7 @@ class RsvTimetableReservationRepository {
|
||||
public function get_confirmation_code(int $reservation_id): ?string {
|
||||
return Db::get_var(
|
||||
"SELECT c.code FROM {$this->confirmation_table} c
|
||||
JOIN {$this->table} tr ON tr.id = c.timetable_reservation_id
|
||||
WHERE tr.reservation_id = %d
|
||||
WHERE c.timetable_reservation_id = %d
|
||||
LIMIT 1",
|
||||
[$reservation_id]
|
||||
);
|
||||
|
||||
@@ -32,6 +32,9 @@ function rsv_localize_api(string $handle): void {
|
||||
'count_few' => '%d termíny',
|
||||
'count_many' => '%d termínů',
|
||||
'currency' => 'Kč',
|
||||
'subtotal' => 'Mezisoučet',
|
||||
'discount' => 'Sleva',
|
||||
'total' => 'Celkem',
|
||||
],
|
||||
'form' => [
|
||||
'success_title' => 'Rezervace potvrzena!',
|
||||
|
||||
@@ -21,6 +21,7 @@ class RsvInstaller {
|
||||
form_id bigint unsigned NOT NULL,
|
||||
submitted_on_utc TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`values` JSON NOT NULL,
|
||||
computed JSON NULL,
|
||||
PRIMARY KEY (form_submit_id),
|
||||
CONSTRAINT fk_form_submit_definition
|
||||
FOREIGN KEY (form_id) REFERENCES {$wpdb->prefix}rsv_form_definition (form_id)
|
||||
|
||||
@@ -74,6 +74,13 @@ class RsvFormReservationElementHandler implements RsvFormElementHandler {
|
||||
$price_per_block = (float) $def->getAttr('price_per_block', 0);
|
||||
$result->setValue($name . '_price', $price_per_block * count($payload['timetable_reservations']));
|
||||
|
||||
$slots = array_map(fn($t) => [
|
||||
'start_utc' => (new DateTime($t))->format(DateTime::ATOM),
|
||||
'end_utc' => $this->end_from_start(new DateTime($t), $timetable->block_size)->format(DateTime::ATOM),
|
||||
'price' => $price_per_block,
|
||||
], $payload['timetable_reservations']);
|
||||
$result->setValue('slots', array_merge($result->getValue('slots') ?? [], $slots));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+15
-2
@@ -16,13 +16,26 @@ final class RsvFormCalculatedValues {
|
||||
$price_before_discount += (float) $element_calculator($element, $data->getValue($element->getName()));
|
||||
}
|
||||
|
||||
$discount_pct = (new RsvMembershipService())->discount_for($definition, $data);
|
||||
$discount_detail = (new RsvMembershipService())->discount_detail_for($definition, $data);
|
||||
$discount_pct = $discount_detail['percent'];
|
||||
$final_price = $calculator->calculate($definition, $data);
|
||||
$subtotal = $price_before_discount;
|
||||
$discount_amount = $subtotal - $final_price;
|
||||
|
||||
return [
|
||||
'price' => $final_price,
|
||||
'price_before_discount' => $price_before_discount,
|
||||
'discount_percent' => $discount_pct,
|
||||
'pricing' => [
|
||||
'currency' => 'CZK',
|
||||
'subtotal' => $subtotal,
|
||||
'discount' => $discount_pct > 0.0 ? [
|
||||
'percent' => $discount_pct,
|
||||
'amount' => round($discount_amount, 2),
|
||||
'reason' => $discount_detail['reason'],
|
||||
] : null,
|
||||
'total' => $final_price,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -31,6 +44,6 @@ final class RsvFormCalculatedValues {
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function names(): array {
|
||||
return ['price', 'price_before_discount', 'discount_percent'];
|
||||
return ['price', 'price_before_discount', 'discount_percent', 'pricing'];
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Reservair\Templating\RsvTemplateEngine;
|
||||
|
||||
class RsvFormHtmlRenderer {
|
||||
public function draw(RsvFormDefinition $form): bool {
|
||||
if (!$form->hasElements()) {
|
||||
@@ -21,37 +19,12 @@ class RsvFormHtmlRenderer {
|
||||
<?php endforeach; ?>
|
||||
|
||||
</form>
|
||||
<?php $this->draw_success_template($form); ?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the admin-configured success message as an inert <template> that the
|
||||
* client clones once the form is submitted. A <reservation-summary> element
|
||||
* expands to a placeholder div that RsvFormSender fills with the visitor's
|
||||
* selected slots.
|
||||
*/
|
||||
private function draw_success_template(RsvFormDefinition $form): void {
|
||||
$message = trim($form->getSuccessMessage());
|
||||
if ($message === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
global $rsv_template_registry;
|
||||
$engine = new RsvTemplateEngine(registry: $rsv_template_registry);
|
||||
|
||||
// Sanitize admin HTML before rendering, allowing the registered template
|
||||
// custom elements through so the engine can expand them.
|
||||
$allowed = $rsv_template_registry->kses_allowed(wp_kses_allowed_html('post'));
|
||||
$html = $engine->render(wp_kses($message, $allowed));
|
||||
?>
|
||||
<template class="rsv-form-success"><?= $html ?></template>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function draw_element(RsvFormElementDefinition $data): void {
|
||||
global $rsv_form_registry;
|
||||
|
||||
|
||||
@@ -35,7 +35,24 @@ class RsvFormSubmission {
|
||||
return ['success' => false, 'errors' => $result->getErrors()];
|
||||
}
|
||||
|
||||
return ['success' => true, 'submit_id' => $submit_id, 'values' => $result->getValues()];
|
||||
global $rsv_template_registry;
|
||||
$message = trim($definition->getSuccessMessage());
|
||||
if ($message !== '') {
|
||||
$allowed = $rsv_template_registry->kses_allowed(wp_kses_allowed_html('post'));
|
||||
$template = wp_kses($message, $allowed);
|
||||
} else {
|
||||
$template = '';
|
||||
}
|
||||
|
||||
$data = array_merge($result->getValues(), (new RsvFormCalculatedValues())->for($definition, $form_data));
|
||||
|
||||
try {
|
||||
$submit_repo->set_computed($submit_id, $data);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error($e);
|
||||
}
|
||||
|
||||
return ['success' => true, 'submit_id' => $submit_id, 'template' => $template, 'data' => $data];
|
||||
}
|
||||
|
||||
/** Remove a submission whose run failed. */
|
||||
|
||||
@@ -2,18 +2,9 @@
|
||||
|
||||
class RsvMembershipService {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Total membership discount for a submission.
|
||||
*
|
||||
* Each binding names a form field whose submitted value must match a key
|
||||
* in the bound program. Matching bindings' discounts are combined per the
|
||||
* definition's combine mode: the best single discount, or all summed and
|
||||
* capped at 100%.
|
||||
*/
|
||||
public function discount_for(RsvFormDefinition $def, RsvFormData $data): float {
|
||||
public function discount_detail_for(RsvFormDefinition $def, RsvFormData $data): array {
|
||||
$repo = new RsvMembershipProgramRepository();
|
||||
$matched_programs = [];
|
||||
$matched_discounts = [];
|
||||
|
||||
foreach ($def->getMembershipBindings() as $binding) {
|
||||
@@ -33,18 +24,37 @@ class RsvMembershipService {
|
||||
}
|
||||
|
||||
if ($repo->key_exists($program_id, $value)) {
|
||||
$program = $repo->get($program_id);
|
||||
if ($program) {
|
||||
$matched_programs[] = $program['name'];
|
||||
}
|
||||
$matched_discounts[] = $discount;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($matched_discounts)) {
|
||||
return 0.0;
|
||||
return ['percent' => 0.0, 'reason' => ''];
|
||||
}
|
||||
|
||||
if ($def->getMembershipCombine() === 'sum') {
|
||||
return min(100.0, array_sum($matched_discounts));
|
||||
$reason = implode(', ', $matched_programs);
|
||||
return ['percent' => min(100.0, array_sum($matched_discounts)), 'reason' => $reason];
|
||||
}
|
||||
|
||||
return max($matched_discounts);
|
||||
$max_idx = array_search(max($matched_discounts), $matched_discounts, true);
|
||||
$reason = $matched_programs[$max_idx] ?? '';
|
||||
return ['percent' => max($matched_discounts), 'reason' => $reason];
|
||||
}
|
||||
|
||||
/**
|
||||
* Total membership discount for a submission.
|
||||
*
|
||||
* Each binding names a form field whose submitted value must match a key
|
||||
* in the bound program. Matching bindings' discounts are combined per the
|
||||
* definition's combine mode: the best single discount, or all summed and
|
||||
* capped at 100%.
|
||||
*/
|
||||
public function discount_for(RsvFormDefinition $def, RsvFormData $data): float {
|
||||
return $this->discount_detail_for($def, $data)['percent'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ class RsvReservationService {
|
||||
// (maintainer emails, calendar sync) observe the new reservation.
|
||||
foreach($reservation->timetable_reservations as $timetable_reservation) {
|
||||
if($timetable_reservation->is_confirmed === null) {
|
||||
error_log('timetable_reservation->is_confirmed is null: ' . $timetable_reservation->id);
|
||||
$maintainer_email = (new RsvTimetableRepository())->get_maintainer_email($timetable_reservation->timetable_id);
|
||||
RsvEventDispatcher::dispatch(new RsvTimetableReservationPendingEvent(
|
||||
$reservation_id,
|
||||
|
||||
@@ -115,18 +115,18 @@ class RsvTimetableReservationService {
|
||||
return $this->repo->has_pending_confirmation($reservation_id);
|
||||
}
|
||||
|
||||
public function get_confirmation_code(int $reservation_id): ?string {
|
||||
$code = $this->repo->get_confirmation_code($reservation_id);
|
||||
public function get_confirmation_code(int $timetable_reservation_id): ?string {
|
||||
$code = $this->repo->get_confirmation_code($timetable_reservation_id);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
public function accept_by_reservation_id(int $reservation_id): void {
|
||||
$this->set_confirmed_state($this->get_confirmation_code($reservation_id), true);
|
||||
public function accept_by_id(int $timetable_reservation_id): void {
|
||||
$this->set_confirmed_state($this->get_confirmation_code($timetable_reservation_id), true);
|
||||
}
|
||||
|
||||
public function refuse_by_reservation_id(int $reservation_id): void {
|
||||
$this->set_confirmed_state($this->get_confirmation_code($reservation_id), false);
|
||||
public function refuse_by_id(int $timetable_reservation_id): void {
|
||||
$this->set_confirmed_state($this->get_confirmation_code($timetable_reservation_id), false);
|
||||
}
|
||||
|
||||
// TODO: Add requires_confirmation parameter
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Reservair\Forms\RsvFormBuilder;
|
||||
use Reservair\Forms\RsvCodeEditor;
|
||||
use Reservair\Layout\RsvColumnLayout;
|
||||
|
||||
class RsvFormsPage extends RsvAdminPage {
|
||||
@@ -127,7 +128,20 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
echo RsvFormBuilder::create('edit_form_definition', get_rest_url(null, 'reservations/v1/form-definition/' . $id), 'PUT', 'Form definition updated.')
|
||||
->text('name', 'Name', '', true, $form_def['name'])
|
||||
->select('definition.email_key', 'Email Key', $email_key_options, "Form field that holds the submitter's email address.", true, $definition['email_key'] ?? '')
|
||||
->code('definition.success_message', 'Success message', 'Shown to the visitor after a successful submission. HTML is allowed. Use <reservation-summary></reservation-summary> to display the selected reservations. Leave blank for the default message.', $definition['success_message'] ?? '')
|
||||
->custom('Success message', function () use ($definition) {
|
||||
$editor = RsvCodeEditor::render('definition.success_message', [
|
||||
'value' => $definition['success_message'] ?? '',
|
||||
'mode' => 'text/html',
|
||||
'rows' => 8,
|
||||
]);
|
||||
$hint = esc_html('Shown to the visitor after a successful submission. HTML is allowed. Use <reservation-summary></reservation-summary> to display the selected reservations. Leave blank for the default message.');
|
||||
return '<div style="display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap;">'
|
||||
. '<div style="flex:1 1 320px;min-width:0;">' . $editor . '<p class="description">' . $hint . '</p></div>'
|
||||
. '<div style="flex:1 1 320px;min-width:0;">'
|
||||
. '<span style="display:block;font-weight:600;margin-bottom:8px;">Live preview</span>'
|
||||
. '<div id="rsv_success_preview" class="rsv-form-preview rsv-success-msg"></div>'
|
||||
. '</div></div>';
|
||||
})
|
||||
->render();
|
||||
?>
|
||||
|
||||
@@ -169,11 +183,11 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
->output();
|
||||
?>
|
||||
|
||||
<?php $this->elements_table_script($elements_with_ids, $next_id, 'edit_form_definition', $element_types, $timetables, $programs, $bindings); ?>
|
||||
<?php $this->elements_table_script($elements_with_ids, $next_id, 'edit_form_definition', $element_types, $timetables, $programs, $bindings, $id); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
private function elements_table_script(array $elements_with_ids, int $next_id, string $form_id, array $element_types, array $timetables = [], array $programs = [], array $bindings = []): void {
|
||||
private function elements_table_script(array $elements_with_ids, int $next_id, string $form_id, array $element_types, array $timetables = [], array $programs = [], array $bindings = [], int $definition_id = 0): void {
|
||||
$elements_json = json_encode($elements_with_ids);
|
||||
$types_json = json_encode(array_values($element_types));
|
||||
$timetables_json = json_encode(array_values($timetables));
|
||||
@@ -365,6 +379,54 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
// The preview form is inert: block submission (capture so it works after re-render).
|
||||
rsv_preview_el?.addEventListener('submit', (e) => e.preventDefault(), true);
|
||||
|
||||
// --- Success message live preview ----------------------------------
|
||||
// Rendered with the same template engine the front-end uses after a real
|
||||
// submission. The data comes from this form's most recent submission, so
|
||||
// {{ tokens }} and <reservation-summary> mirror a genuine confirmation;
|
||||
// the message text itself updates live as it is edited.
|
||||
const rsv_success_preview_el = document.getElementById('rsv_success_preview');
|
||||
let rsv_success_preview_timer = null;
|
||||
let rsv_success_preview_data = {};
|
||||
|
||||
function rsv_schedule_success_preview() {
|
||||
if (!rsv_success_preview_el) return;
|
||||
clearTimeout(rsv_success_preview_timer);
|
||||
rsv_success_preview_timer = setTimeout(rsv_render_success_preview, 300);
|
||||
}
|
||||
|
||||
function rsv_render_success_preview() {
|
||||
if (!rsv_success_preview_el) return;
|
||||
const form = document.getElementById('<?= $form_id ?>');
|
||||
const tpl = (form?.querySelector('[name="definition.success_message"]')?.value ?? '').trim();
|
||||
if (tpl === '') {
|
||||
rsv_success_preview_el.innerHTML = '<p class="rsv-preview-empty">Leave blank to show the default confirmation message.</p>';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
rsv_success_preview_el.innerHTML = RsvFormSender.render_template(tpl, rsv_success_preview_data);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
rsv_success_preview_el.innerHTML = '<p class="rsv-preview-empty">Preview unavailable.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons rendered into the preview (e.g. <reset-form-button>) live inside
|
||||
// the edit form — keep them from submitting it.
|
||||
rsv_success_preview_el?.addEventListener('click', (e) => {
|
||||
if (e.target.closest('button, input[type="submit"], input[type="image"]')) e.preventDefault();
|
||||
}, true);
|
||||
|
||||
if (rsv_success_preview_el) {
|
||||
rsv_render_success_preview();
|
||||
fetch('<?= get_rest_url(null, 'reservations/v1/form-definition/' . $definition_id . '/submission/latest') ?>', {
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Accept': 'application/json', 'X-WP-Nonce': ReservairServiceAPI.nonce },
|
||||
})
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(res => { rsv_success_preview_data = res?.data ?? {}; rsv_render_success_preview(); })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function rsv_render_element_inline_form(dt, row, data) {
|
||||
const builder = RsvInlineFormBuilder.create(rsv_elements_source)
|
||||
.fieldset('Element', '50%')
|
||||
@@ -620,11 +682,15 @@ class RsvFormsPage extends RsvAdminPage {
|
||||
}
|
||||
|
||||
const rsv_meta_form = document.getElementById('<?= $form_id ?>');
|
||||
['name', 'definition.email_key', 'definition.success_message', 'definition.membership_combine'].forEach((n) => {
|
||||
['name', 'definition.email_key', 'definition.membership_combine'].forEach((n) => {
|
||||
const el = rsv_meta_form?.querySelector(`[name="${n}"]`);
|
||||
el?.addEventListener('input', rsv_schedule_preview);
|
||||
el?.addEventListener('change', rsv_schedule_preview);
|
||||
});
|
||||
// The success message drives its own preview only.
|
||||
const rsv_success_input = rsv_meta_form?.querySelector('[name="definition.success_message"]');
|
||||
rsv_success_input?.addEventListener('input', rsv_schedule_success_preview);
|
||||
rsv_success_input?.addEventListener('change', rsv_schedule_success_preview);
|
||||
|
||||
RsvAdminForm.bind(document.getElementById('<?= $form_id ?>'), {
|
||||
transform: () => rsv_collect_definition(),
|
||||
|
||||
@@ -103,17 +103,17 @@ class RsvTimetablePage extends RsvAdminPage {
|
||||
timetable_reservations_table,
|
||||
RsvTimetableReservationResource(<?= $id ?>),
|
||||
{
|
||||
'id': RsvDataGrid.column('ID', false),
|
||||
'reservation_id': RsvDataGrid.action_column('Reservation', false, {
|
||||
'Accept': RsvDataGrid.func_action(
|
||||
(dt, row, data) => RsvReservationClient.accept(data.reservation_id).then(() => dt.refresh()),
|
||||
(item) => item.pending_confirmation_id !== null
|
||||
),
|
||||
'Refuse': RsvDataGrid.func_action(
|
||||
(dt, row, data) => RsvReservationClient.refuse(data.reservation_id).then(() => dt.refresh()),
|
||||
(item) => item.pending_confirmation_id !== null
|
||||
),
|
||||
'id': RsvDataGrid.action_column('ID', false, {
|
||||
'Accept': RsvDataGrid.func_action(
|
||||
(dt, row, data) => RsvTimetableReservationClient.accept(data.id).then(() => dt.refresh()),
|
||||
(item) => item.pending_confirmation_id !== null
|
||||
),
|
||||
'Refuse': RsvDataGrid.func_action(
|
||||
(dt, row, data) => RsvTimetableReservationClient.refuse(data.id).then(() => dt.refresh()),
|
||||
(item) => item.pending_confirmation_id !== null
|
||||
),
|
||||
}),
|
||||
'reservation_id': RsvDataGrid.action_column('Reservation', false),
|
||||
'start_utc': RsvDataGrid.column('Start', false),
|
||||
'end_utc': RsvDataGrid.column('End', false),
|
||||
'is_confirmed': RsvDataGrid.column('Status', false),
|
||||
|
||||
Reference in New Issue
Block a user