From 73c360402721094b0907c2a2e786036f6fd64bcc Mon Sep 17 00:00:00 2001 From: James Cole Date: Wed, 29 Jul 2026 07:12:30 +0200 Subject: [PATCH] Fix recurring create and edit. --- .../Controllers/Recurring/EditController.php | 6 +- .../assets/v3/js/form/create-autocomplete.js | 44 +++ .../assets/v3/js/form/create-tag-field.js | 44 +++ .../assets/v3/js/pages/recurring/create.js | 250 +++--------------- .../assets/v3/js/pages/recurring/edit.js | 117 ++++++++ .../shared/create-button-switcher.js | 36 +++ .../shared/respond-to-first-date-change.js | 59 +++++ .../shared/respond-to-repetition-end.js | 41 +++ .../shared/switch-transaction-type.js | 78 ++++++ resources/assets/v3/vite.config.js | 1 + resources/views/recurring/create.blade.php | 10 +- resources/views/recurring/edit.blade.php | 76 ++---- 12 files changed, 485 insertions(+), 277 deletions(-) create mode 100644 resources/assets/v3/js/form/create-autocomplete.js create mode 100644 resources/assets/v3/js/form/create-tag-field.js create mode 100644 resources/assets/v3/js/pages/recurring/edit.js create mode 100644 resources/assets/v3/js/pages/recurring/shared/create-button-switcher.js create mode 100644 resources/assets/v3/js/pages/recurring/shared/respond-to-first-date-change.js create mode 100644 resources/assets/v3/js/pages/recurring/shared/respond-to-repetition-end.js create mode 100644 resources/assets/v3/js/pages/recurring/shared/switch-transaction-type.js diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php index 82b124f1ea..57bb5b18b5 100644 --- a/app/Http/Controllers/Recurring/EditController.php +++ b/app/Http/Controllers/Recurring/EditController.php @@ -150,7 +150,11 @@ final class EditController extends Controller ]; $array['first_date'] = substr((string) $array['first_date'], 0, 10); $array['repeat_until'] = substr((string) $array['repeat_until'], 0, 10); - $array['transactions'][0]['tags'] = implode(',', $array['transactions'][0]['tags'] ?? []); + $tags = []; + foreach($array['transactions'][0]['tags'] as $tag) { + $tags[$tag] = $tag; + } + $array['transactions'][0]['tags'] = $tags; $array['transactions'][0]['amount'] = round((float) $array['transactions'][0]['amount'], $array['transactions'][0]['currency_decimal_places']); if (null !== $array['transactions'][0]['foreign_amount'] && '' !== $array['transactions'][0]['foreign_amount']) { $array['transactions'][0]['foreign_amount'] = round( diff --git a/resources/assets/v3/js/form/create-autocomplete.js b/resources/assets/v3/js/form/create-autocomplete.js new file mode 100644 index 0000000000..32fb4230d0 --- /dev/null +++ b/resources/assets/v3/js/form/create-autocomplete.js @@ -0,0 +1,44 @@ +/* + * create-autocomplete.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import Autocomplete from "bootstrap5-autocomplete"; + + +function createAutocomplete(fieldIdentifier, url) { + let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + Autocomplete.init('#' + fieldIdentifier, { + + server: url + '?_token=' + token, + labelField: 'name', + valueField: 'name', + liveServer: true, + fetchOptions: { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-CSRF-TOKEN': token + } + } + }); +} + +export {createAutocomplete} diff --git a/resources/assets/v3/js/form/create-tag-field.js b/resources/assets/v3/js/form/create-tag-field.js new file mode 100644 index 0000000000..ee27599505 --- /dev/null +++ b/resources/assets/v3/js/form/create-tag-field.js @@ -0,0 +1,44 @@ +/* + * create-tag-field.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import Tags from "bootstrap5-tags"; +function createTagField(fieldIdentifier) { + let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + Tags.init('#' + fieldIdentifier, + { + allowNew: true, + allowClear: true, + server: './api/v1/autocomplete/tags?_token=' + token, + liveServer: true, + labelField: 'name', + valueField: 'name', + fetchOptions: { + method: 'GET', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-CSRF-TOKEN': token + } + } + } + ); +} + +export {createTagField}; diff --git a/resources/assets/v3/js/pages/recurring/create.js b/resources/assets/v3/js/pages/recurring/create.js index fa2096bfe4..eb224dbc73 100644 --- a/resources/assets/v3/js/pages/recurring/create.js +++ b/resources/assets/v3/js/pages/recurring/create.js @@ -21,58 +21,56 @@ import '../../boot/bootstrap.js'; import sidebar from '../../pages/shared/sidebar.js'; import dates from '../shared/dates.js'; -import Autocomplete from "bootstrap5-autocomplete"; -import Tags from "bootstrap5-tags"; + import {Calendar} from 'fullcalendar'; import themePlugin from 'fullcalendar/themes/monarch'; // YOUR THEME import dayGridPlugin from 'fullcalendar/daygrid'; import timeGridPlugin from 'fullcalendar/timegrid'; import listPlugin from 'fullcalendar/list'; - +import {createTagField} from '../../form/create-tag-field.js'; +import {createAutocomplete} from '../../form/create-autocomplete.js'; +import {createButtonSwitcher} from './shared/create-button-switcher.js'; +import {respondToFirstDateChange} from './shared/respond-to-first-date-change.js'; +import {switchTransactionType} from './shared/switch-transaction-type.js'; +import {respondToRepetitionEnd} from './shared/respond-to-repetition-end.js'; // stylesheets import 'fullcalendar/skeleton.css'; // ALWAYS NEED SKELETON import 'fullcalendar/themes/monarch/theme.css'; // YOUR THEME import 'fullcalendar/themes/monarch/palettes/purple.css'; // YOUR THEME'S PALETTE -let calendarEl = document.getElementById('recurring_calendar'); -let calendar = new Calendar(calendarEl, { - plugins: [themePlugin, dayGridPlugin, timeGridPlugin, listPlugin], - initialView: 'dayGridMonth', - headerToolbar: { - left: 'prev,next today', - center: 'title', - right: 'dayGridMonth,timeGridWeek,listWeek' - } -}); -calendar.render(); - let create = function () { return { calendar: null, eventSource: null, init() { - console.log('Init recurring create'); - this.createTagField(); - this.createAutocomplete(); - this.createButtonSwitcher(); - this.switchTransactionType('withdrawal'); - this.respondToFirstDateChange(); - this.respondToRepetitionEnd(); - document.getElementById('ffInput_first_date').addEventListener('change', this.respondToFirstDateChange.bind(this)); - document.getElementById('ffInput_repetition_end').addEventListener('change', this.respondToRepetitionEnd.bind(this)); + createTagField('ffInput_tags'); + createAutocomplete('ffInput_category', './api/v1/autocomplete/categories'); + createButtonSwitcher(); + switchTransactionType('withdrawal'); + let type = document.getElementById('repetitionType').value; + let suggestUrl = document.getElementById('suggestUrl').value; + respondToFirstDateChange(type, suggestUrl); + respondToRepetitionEnd(); + document.getElementById('ffInput_first_date').addEventListener('change', respondToFirstDateChange.bind(this)); + document.getElementById('ffInput_repetition_end').addEventListener('change', respondToRepetitionEnd.bind(this)); - // TODO create calendar + + let calendarEl = document.getElementById('recurring_calendar'); this.calendar = new Calendar(calendarEl, { - plugins: [dayGridPlugin], - initialView: 'dayGridMonth' + plugins: [themePlugin, dayGridPlugin, timeGridPlugin, listPlugin], + initialView: 'dayGridMonth', + headerToolbar: { + left: 'prev,next today', + center: 'title' + } }); - - document.getElementById('calendar-link').addEventListener('click', this.showRepCalendar); + this.calendar.render(); + document.getElementById('calendar-link').addEventListener('click', this.showRepCalendar.bind(this)); }, showRepCalendar() { - + let eventsUrl = document.getElementById('eventsUrl').value; // pre-append URL with repetition info: let newEventsUrl = eventsUrl + '?type=' + document.getElementById('ffInput_repetition_type').value; newEventsUrl += '&skip=' + document.getElementById('ffInput_skip').value; @@ -82,199 +80,13 @@ let create = function () { newEventsUrl += '&first_date=' + document.getElementById('ffInput_first_date').value; newEventsUrl += '&weekend=' + document.getElementById('ffInput_weekend').value; - console.log(newEventsUrl); - - // remove all event sources from calendar: let eventSource = new EventSource(newEventsUrl); - calendar.removeAllEventSources(); - calendar.addEventSource(eventSource); + this.calendar.removeAllEventSources(); + this.calendar.addEventSource(eventSource); $('#calendarModal').modal('show'); return false; - }, - respondToRepetitionEnd() { - var obj = document.getElementById('ffInput_repetition_end'); - var value = obj.value; - switch (value) { - case 'forever': - document.getElementById('repeat_until_holder').style.display = 'none'; - document.getElementById('repetitions_holder').style.display = 'none'; - break; - case 'until_date': - document.getElementById('repeat_until_holder').style.display = 'block'; - document.getElementById('repetitions_holder').style.display = 'none'; - - break; - case 'times': - document.getElementById('repeat_until_holder').style.display = 'none'; - document.getElementById('repetitions_holder').style.display = 'block'; - break; - } - }, - respondToFirstDateChange() { - let obj = document.getElementById('ffInput_first_date'); - let select = document.getElementById('ffInput_repetition_type'); - let date = obj.value; - select.disabled = true; - - // preselected value: - var preSelected = oldRepetitionType; - if (preSelected === '') { - preSelected = select.value; - } - - $.getJSON(suggestUrl, {date: date, pre_select: preSelected, past: 'true'}).fail(function () { - console.error('Could not load repetition suggestions'); - alert('Could not load repetition suggestions. Please enter a valid date.'); - }).done(this.parseRepetitionSuggestions); - }, - parseRepetitionSuggestions(data) { - let select = document.getElementById('ffInput_repetition_type'); - select.innerHTML = ''; - let opt; - for (var k in data) { - if (data.hasOwnProperty(k)) { - console.log('label: ' + data[k].label + ', selected: ' + data[k].selected); - opt = document.createElement('option'); - opt.value = k; - opt.label = data[k].label; - opt.text = data[k].label; - if (data[k].selected) { - opt.selected = true; - } - select.appendChild(opt); - } - } - select.disabled = false; - }, - - - createButtonSwitcher() { - console.log('Now in initializeButtons()'); - let list = document.getElementsByClassName('switch-button'); - for (let i = 0; i < list.length; i++) { - - list[i].addEventListener('click', (event) => { - let transactionType = event.currentTarget.dataset.value; - // console.log('Clicked value is ' + transactionType); - this.switchTransactionType(transactionType); - return false; - }); - } - // $.each($('.switch-button'), function (i, v) { - // //var btn = $(v); - // - // if (btn.data('value') === transactionType) { - // btn.addClass('btn-info disabled').removeClass('btn-default'); - // $('input[name="transaction_type"]').val(transactionType); - // } else { - // btn.removeClass('btn-info disabled').addClass('btn-default'); - // } - // }); - // updateFormFields(); - }, - switchTransactionType(transactionType) { - let list = document.getElementsByClassName('switch-button'); - for (let i = 0; i < list.length; i++) { - let currentType = list[i].dataset.value; - console.log('Selected is ' + currentType + ', form type is ' + transactionType); - if (currentType === transactionType) { - list[i].classList.add('btn-primary'); - list[i].classList.remove('btn-secondary'); - } - if (currentType !== transactionType) { - list[i].classList.remove('btn-primary'); - list[i].classList.add('btn-secondary'); - } - } - if ('withdrawal' === transactionType) { - // hide source account name: - document.getElementById('deposit_source_id_holder').style.display = 'none'; - - // // show source account ID: - document.getElementById('source_id_holder').style.display = 'block'; - - // show destination name: - document.getElementById('withdrawal_destination_id_holder').style.display = 'block'; - - // // hide destination ID: - document.getElementById('destination_id_holder').style.display = 'none'; - - // show budget + bill - document.getElementById('budget_id_holder').style.display = 'block'; - document.getElementById('bill_id_holder').style.display = 'block'; - - // hide piggy bank: - document.getElementById('piggy_bank_id_holder').style.display = 'none'; - } - - if (transactionType === 'deposit') { - document.getElementById('deposit_source_id_holder').style.display = 'block'; - - document.getElementById('source_id_holder').style.display = 'none'; - - document.getElementById('withdrawal_destination_id_holder').style.display = 'none'; - - document.getElementById('destination_id_holder').style.display = 'block'; - document.getElementById('budget_id_holder').style.display = 'none'; - document.getElementById('bill_id_holder').style.display = 'none'; - document.getElementById('piggy_bank_id_holder').style.display = 'none'; - } - - if (transactionType === 'transfer') { - document.getElementById('deposit_source_id_holder').style.display = 'none'; - document.getElementById('source_id_holder').style.display = 'block'; - document.getElementById('withdrawal_destination_id_holder').style.display = 'none'; - document.getElementById('destination_id_holder').style.display = 'block'; - document.getElementById('budget_id_holder').style.display = 'none'; - document.getElementById('bill_id_holder').style.display = 'none'; - document.getElementById('piggy_bank_id_holder').style.display = 'block'; - } - }, - createTagField() { - let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); - Tags.init('#ffInput_tags', - { - allowNew: true, - allowClear: true, - server: './api/v1/autocomplete/tags?_token=' + token, - liveServer: true, - labelField: 'name', - valueField: 'name', - fetchOptions: { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-TOKEN': token - } - } - } - ); - }, - createAutocomplete() { - this.createCategoryAutocomplete(); - }, - createCategoryAutocomplete() { - let token = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); - Autocomplete.init('#ffInput_category', { - - server: './api/v1/autocomplete/categories?_token=' + token, - labelField: 'name', - valueField: 'name', - liveServer: true, - fetchOptions: { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-TOKEN': token - } - } - }); - }, + } } }; diff --git a/resources/assets/v3/js/pages/recurring/edit.js b/resources/assets/v3/js/pages/recurring/edit.js new file mode 100644 index 0000000000..6127ccd97c --- /dev/null +++ b/resources/assets/v3/js/pages/recurring/edit.js @@ -0,0 +1,117 @@ +/* + * edit.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +import '../../boot/bootstrap.js'; +import sidebar from '../../pages/shared/sidebar.js'; +import dates from '../shared/dates.js'; +import {createTagField} from "../../form/create-tag-field.js"; +import {createAutocomplete} from "../../form/create-autocomplete.js"; +import {createButtonSwitcher} from "./shared/create-button-switcher.js"; +import {switchTransactionType} from "./shared/switch-transaction-type.js"; +import {respondToFirstDateChange} from './shared/respond-to-first-date-change.js'; +import {respondToRepetitionEnd} from './shared/respond-to-repetition-end.js'; +import {Calendar} from "fullcalendar"; +import themePlugin from "fullcalendar/themes/monarch"; +import dayGridPlugin from "fullcalendar/daygrid"; +import timeGridPlugin from "fullcalendar/timegrid"; +import listPlugin from "fullcalendar/list"; + +// stylesheets +import 'fullcalendar/skeleton.css'; // ALWAYS NEED SKELETON +import 'fullcalendar/themes/monarch/theme.css'; // YOUR THEME +import 'fullcalendar/themes/monarch/palettes/purple.css'; // YOUR THEME'S PALETTE + +let edit = function () { + return { + init() { + createTagField('ffInput_tags'); + createAutocomplete('ffInput_category','./api/v1/autocomplete/categories'); + createButtonSwitcher(); + switchTransactionType(document.getElementsByName('transaction_type')[0].value); + + let type = document.getElementById('repetitionType').value; + let suggestUrl = document.getElementById('suggestUrl').value; + respondToFirstDateChange(type, suggestUrl); + respondToRepetitionEnd(); + document.getElementById('ffInput_first_date').addEventListener('change', respondToFirstDateChange.bind(this)); + document.getElementById('ffInput_repetition_end').addEventListener('change', respondToRepetitionEnd.bind(this)); + + let calendarEl = document.getElementById('recurring_calendar'); + this.calendar = new Calendar(calendarEl, { + plugins: [themePlugin, dayGridPlugin, timeGridPlugin, listPlugin], + initialView: 'dayGridMonth', + headerToolbar: { + left: 'prev,next today', + center: 'title' + } + }); + this.calendar.render(); + document.getElementById('calendar-link').addEventListener('click', this.showRepCalendar.bind(this)); + }, + + showRepCalendar() { + let eventsUrl = document.getElementById('eventsUrl').value; + // pre-append URL with repetition info: + let newEventsUrl = eventsUrl + '?type=' + document.getElementById('ffInput_repetition_type').value; + newEventsUrl += '&skip=' + document.getElementById('ffInput_skip').value; + newEventsUrl += '&ends=' + document.getElementById('ffInput_repetition_end').value; + newEventsUrl += '&end_date=' + document.getElementById('ffInput_repeat_until').value; + newEventsUrl += '&reps=' + document.getElementById('ffInput_repetitions').value; + newEventsUrl += '&first_date=' + document.getElementById('ffInput_first_date').value; + newEventsUrl += '&weekend=' + document.getElementById('ffInput_weekend').value; + + let eventSource = new EventSource(newEventsUrl); + this.calendar.removeAllEventSources(); + this.calendar.addEventSource(eventSource); + $('#calendarModal').modal('show'); + + return false; + } + } +}; + + +const comps = { + edit, + sidebar, + dates +}; + +function loadPage(comps) { + // console.log('loadPage'); + Object.keys(comps).forEach(comp => { + let data = comps[comp](); + Alpine.data(comp, () => data); + // console.log(comp); + }); + Alpine.start(); +} + +// wait for load until bootstrapped event is received. +document.addEventListener('firefly-iii-bootstrapped', () => { + // console.log('Loaded through event listener.'); + loadPage(comps); +}); +// or is bootstrapped before event is triggered. +if (window.bootstrapped) { + // console.log('Loaded through window variable.'); + loadPage(comps); +} diff --git a/resources/assets/v3/js/pages/recurring/shared/create-button-switcher.js b/resources/assets/v3/js/pages/recurring/shared/create-button-switcher.js new file mode 100644 index 0000000000..052584a16b --- /dev/null +++ b/resources/assets/v3/js/pages/recurring/shared/create-button-switcher.js @@ -0,0 +1,36 @@ +/* + * create-button-switcher.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import {switchTransactionType} from "./switch-transaction-type.js"; + +function createButtonSwitcher() { + let list = document.getElementsByClassName('switch-button'); + for (let i = 0; i < list.length; i++) { + + list[i].addEventListener('click', (event) => { + let transactionType = event.currentTarget.dataset.value; + // console.log('Clicked value is ' + transactionType); + switchTransactionType(transactionType); + return false; + }); + } +} + +export {createButtonSwitcher}; diff --git a/resources/assets/v3/js/pages/recurring/shared/respond-to-first-date-change.js b/resources/assets/v3/js/pages/recurring/shared/respond-to-first-date-change.js new file mode 100644 index 0000000000..8d4725e1ed --- /dev/null +++ b/resources/assets/v3/js/pages/recurring/shared/respond-to-first-date-change.js @@ -0,0 +1,59 @@ +/* + * respond-to-first-date-change.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +function parseRepetitionSuggestions(data) { + let select = document.getElementById('ffInput_repetition_type'); + select.innerHTML = ''; + let opt; + for (var k in data) { + if (data.hasOwnProperty(k)) { + console.log('label: ' + data[k].label + ', selected: ' + data[k].selected); + opt = document.createElement('option'); + opt.value = k; + opt.label = data[k].label; + opt.text = data[k].label; + if (data[k].selected) { + opt.selected = true; + } + select.appendChild(opt); + } + } + select.disabled = false; +} + +function respondToFirstDateChange(oldRepetitionType, suggestUrl) { + let obj = document.getElementById('ffInput_first_date'); + let select = document.getElementById('ffInput_repetition_type'); + let date = obj.value; + select.disabled = true; + + // preselected value: + var preSelected = oldRepetitionType; + if (preSelected === '') { + preSelected = select.value; + } + + $.getJSON(suggestUrl, {date: date, pre_select: preSelected, past: 'true'}).fail(function () { + console.error('Could not load repetition suggestions'); + alert('Could not load repetition suggestions. Please enter a valid date.'); + }).done(parseRepetitionSuggestions); +} + +export {respondToFirstDateChange} diff --git a/resources/assets/v3/js/pages/recurring/shared/respond-to-repetition-end.js b/resources/assets/v3/js/pages/recurring/shared/respond-to-repetition-end.js new file mode 100644 index 0000000000..0841209c01 --- /dev/null +++ b/resources/assets/v3/js/pages/recurring/shared/respond-to-repetition-end.js @@ -0,0 +1,41 @@ +/* + * respond-to-repetition-end.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +function respondToRepetitionEnd() { + var obj = document.getElementById('ffInput_repetition_end'); + var value = obj.value; + switch (value) { + case 'forever': + document.getElementById('repeat_until_holder').style.display = 'none'; + document.getElementById('repetitions_holder').style.display = 'none'; + break; + case 'until_date': + document.getElementById('repeat_until_holder').style.display = 'block'; + document.getElementById('repetitions_holder').style.display = 'none'; + + break; + case 'times': + document.getElementById('repeat_until_holder').style.display = 'none'; + document.getElementById('repetitions_holder').style.display = 'block'; + break; + } +} + +export {respondToRepetitionEnd} diff --git a/resources/assets/v3/js/pages/recurring/shared/switch-transaction-type.js b/resources/assets/v3/js/pages/recurring/shared/switch-transaction-type.js new file mode 100644 index 0000000000..fe9743c41b --- /dev/null +++ b/resources/assets/v3/js/pages/recurring/shared/switch-transaction-type.js @@ -0,0 +1,78 @@ +/* + * switch-transaction-type.js + * Copyright (c) 2026 james@firefly-iii.org + * + * This file is part of Firefly III (https://github.com/firefly-iii). + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +function switchTransactionType(transactionType) { + let list = document.getElementsByClassName('switch-button'); + for (let i = 0; i < list.length; i++) { + let currentType = list[i].dataset.value; + if (currentType === transactionType) { + list[i].classList.add('btn-primary'); + list[i].classList.remove('btn-secondary'); + } + if (currentType !== transactionType) { + list[i].classList.remove('btn-primary'); + list[i].classList.add('btn-secondary'); + } + } + if ('withdrawal' === transactionType) { + // hide source account name: + document.getElementById('deposit_source_id_holder').style.display = 'none'; + + // // show source account ID: + document.getElementById('source_id_holder').style.display = 'block'; + + // show destination name: + document.getElementById('withdrawal_destination_id_holder').style.display = 'block'; + + // // hide destination ID: + document.getElementById('destination_id_holder').style.display = 'none'; + + // show budget + bill + document.getElementById('budget_id_holder').style.display = 'block'; + document.getElementById('bill_id_holder').style.display = 'block'; + + // hide piggy bank: + document.getElementById('piggy_bank_id_holder').style.display = 'none'; + } + + if (transactionType === 'deposit') { + document.getElementById('deposit_source_id_holder').style.display = 'block'; + + document.getElementById('source_id_holder').style.display = 'none'; + + document.getElementById('withdrawal_destination_id_holder').style.display = 'none'; + + document.getElementById('destination_id_holder').style.display = 'block'; + document.getElementById('budget_id_holder').style.display = 'none'; + document.getElementById('bill_id_holder').style.display = 'none'; + document.getElementById('piggy_bank_id_holder').style.display = 'none'; + } + + if (transactionType === 'transfer') { + document.getElementById('deposit_source_id_holder').style.display = 'none'; + document.getElementById('source_id_holder').style.display = 'block'; + document.getElementById('withdrawal_destination_id_holder').style.display = 'none'; + document.getElementById('destination_id_holder').style.display = 'block'; + document.getElementById('budget_id_holder').style.display = 'none'; + document.getElementById('bill_id_holder').style.display = 'none'; + document.getElementById('piggy_bank_id_holder').style.display = 'block'; + } +} +export {switchTransactionType}; diff --git a/resources/assets/v3/vite.config.js b/resources/assets/v3/vite.config.js index be6571865a..ff4d009ef3 100644 --- a/resources/assets/v3/vite.config.js +++ b/resources/assets/v3/vite.config.js @@ -72,6 +72,7 @@ export default defineConfig(({command, mode, isSsrBuild, isPreview}) => { // recurring transactions 'js/pages/recurring/create.js', + 'js/pages/recurring/edit.js', // subscriptions diff --git a/resources/views/recurring/create.blade.php b/resources/views/recurring/create.blade.php index 005760b324..0d69697d46 100644 --- a/resources/views/recurring/create.blade.php +++ b/resources/views/recurring/create.blade.php @@ -146,7 +146,7 @@ {{-- TAGS --}} {{-- ExpandedForm::text('tags') --}} - {!! ExpandedForm::multiSelect('tags', [],null, ['multiple'=> 'multiple']) !!} + {!! ExpandedForm::multiSelect('tags', [], null) !!} {{-- RELATE THIS TRANSFER TO A PIGGY BANK --}} {!! PiggyBankForm::piggyBankList('piggy_bank_id') !!} @@ -208,13 +208,11 @@ + + + @endsection @section('scripts') - @vite(['js/pages/recurring/create.js'])