Compare commits

...

9 Commits

56 changed files with 1265 additions and 984 deletions
+28 -69
View File
@@ -48,7 +48,7 @@ class AccountForm {
/** /**
* The control of the base account * The control of the base account
* @type {HTMLDivElement} * @type {HTMLButtonElement}
*/ */
#baseControl; #baseControl;
@@ -60,7 +60,7 @@ class AccountForm {
/** /**
* The base account * The base account
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#base; #base;
@@ -225,15 +225,16 @@ class AccountForm {
/** /**
* The base account selector. * The base account selector.
* *
* @extends {BaseCombobox<BaseAccountOption>}
* @private * @private
*/ */
class BaseAccountSelector { class BaseAccountSelector extends BaseCombobox {
/** /**
* The account form * The account form
* @type {AccountForm} * @type {AccountForm}
*/ */
form; #form;
/** /**
* The selector modal * The selector modal
@@ -241,12 +242,6 @@ class BaseAccountSelector {
*/ */
#modal; #modal;
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/** /**
* The error message when the query has no result * The error message when the query has no result
* @type {HTMLParagraphElement} * @type {HTMLParagraphElement}
@@ -259,12 +254,6 @@ class BaseAccountSelector {
*/ */
#optionList; #optionList;
/**
* The options
* @type {BaseAccountOption[]}
*/
#options;
/** /**
* The button to clear the base account value * The button to clear the base account value
* @type {HTMLButtonElement} * @type {HTMLButtonElement}
@@ -277,29 +266,32 @@ class BaseAccountSelector {
* @param form {AccountForm} the form * @param form {AccountForm} the form
*/ */
constructor(form) { constructor(form) {
this.form = form;
const prefix = "accounting-base-selector"; const prefix = "accounting-base-selector";
const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new BaseAccountOption(element, form.saveBaseAccount.bind(form)));
super(query, options);
this.#form = form;
this.#modal = document.getElementById(`${prefix}-modal`); this.#modal = document.getElementById(`${prefix}-modal`);
this.#query = document.getElementById(`${prefix}-query`); this.#modal.addEventListener("hidden.bs.modal", () => this.#form.onBaseAccountSelectorClosed());
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`); this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`); this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new BaseAccountOption(this, element));
this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#modal.addEventListener("hidden.bs.modal", () => this.form.onBaseAccountSelectorClosed()); this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#query.oninput = () => this.#filterOptions(); this.#clearButton.onclick = () => this.#form.clearBaseAccount();
this.#clearButton.onclick = () => this.form.clearBaseAccount();
} }
/** /**
* Filters the options. * Filters the options.
* *
* @override
*/ */
#filterOptions() { filterOptions() {
this.shownOptions = [];
let isAnyMatched = false; let isAnyMatched = false;
for (const option of this.#options) { for (const option of this.options) {
if (option.isMatched(this.#query.value)) { if (option.isMatched(this.query.value)) {
option.setShown(true); option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true; isAnyMatched = true;
} else { } else {
option.setShown(false); option.setShown(false);
@@ -319,12 +311,11 @@ class BaseAccountSelector {
* *
*/ */
onOpen() { onOpen() {
this.#query.value = ""; this.query.value = "";
this.#filterOptions(); this.filterOptions();
for (const option of this.#options) { this.query.removeAttribute("aria-activedescendant");
option.setActive(option.code === this.form.baseCode); this.selectOption(this.shownOptions.find((option) => option.code === this.#form.baseCode));
} if (this.#form.baseCode === null) {
if (this.form.baseCode === null) {
this.#clearButton.classList.add("btn-secondary") this.#clearButton.classList.add("btn-secondary")
this.#clearButton.classList.remove("btn-danger"); this.#clearButton.classList.remove("btn-danger");
this.#clearButton.disabled = true; this.#clearButton.disabled = true;
@@ -341,13 +332,7 @@ class BaseAccountSelector {
* *
* @private * @private
*/ */
class BaseAccountOption { class BaseAccountOption extends BaseOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/** /**
* The account code * The account code
@@ -370,16 +355,16 @@ class BaseAccountOption {
/** /**
* Constructs the account in the base account selector. * Constructs the account in the base account selector.
* *
* @param selector {BaseAccountSelector} the base account selector
* @param element {HTMLLIElement} the element * @param element {HTMLLIElement} the element
* @param save {function(BaseAccountOption): void} the callback to save the option
*/ */
constructor(selector, element) { constructor(element, save) {
this.#element = element; super(element);
this.code = element.dataset.code; this.code = element.dataset.code;
this.text = element.dataset.text; this.text = element.dataset.text;
this.#queryValues = JSON.parse(element.dataset.queryValues); this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.form.saveBaseAccount(this); element.onclick = () => save(this);
} }
/** /**
@@ -399,30 +384,4 @@ class BaseAccountOption {
} }
return false; return false;
} }
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
} }
+237
View File
@@ -0,0 +1,237 @@
/* The Mia! Accounting Project
* base-combobox.js: The JavaScript for the base abstract combobox
*/
/* Copyright (c) 2026 imacat.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Author: imacat@mail.imacat.idv.tw (imacat)
* First written: 2026/4/16
*/
"use strict";
/**
* The base abstract combobox.
*
* @abstract
* @template {BaseOption} T
*/
class BaseCombobox {
/**
* The query input
* @type {HTMLInputElement}
*/
query;
/**
* The options
* @type {T[]}
*/
options;
/**
* The options that are shown
* @type {T[]}
*/
shownOptions;
/**
* Constructs a base abstract combobox.
*
* @param query {HTMLInputElement} the query input
* @param options {T[]} the options
*/
constructor(query, options) {
this.query = query;
this.query.oninput = () => this.filterOptions();
this.query.onkeydown = this.onQueryKeyDown.bind(this);
this.options = options;
this.shownOptions = [];
}
/**
* Actions when keys are pressed on the query input.
*
* @param event {KeyboardEvent} the key event
*/
onQueryKeyDown(event) {
if (this.shownOptions.length === 0) {
return;
}
const currentID = this.query.getAttribute("aria-activedescendant");
const currentIndex = this.shownOptions.findIndex((option) => option.elementID === currentID);
let newIndex;
switch (event.key) {
case "ArrowUp":
if (currentIndex === -1) {
newIndex = this.shownOptions.length - 1;
} else {
newIndex = (currentIndex - 1 + this.shownOptions.length) % this.shownOptions.length;
}
break;
case "ArrowDown":
if (currentIndex === -1) {
newIndex = 0;
} else {
newIndex = (currentIndex + 1) % this.shownOptions.length;
}
break;
case "Home":
if (this.query.value !== "") {
return;
}
newIndex = 0;
break;
case "End":
if (this.query.value !== "") {
return;
}
newIndex = this.shownOptions.length - 1;
break;
case "PageUp":
if (currentIndex === -1) {
newIndex = this.shownOptions.length - 1;
} else {
newIndex = Math.max(currentIndex - 10, 0);
}
break;
case "PageDown":
if (currentIndex === -1) {
newIndex = 0;
} else {
newIndex = Math.min(currentIndex + 10, this.shownOptions.length - 1);
}
break;
case "Enter":
event.preventDefault();
if (currentIndex !== -1) {
this.shownOptions[currentIndex].click();
}
return;
case "Escape":
if (this.query.value !== "") {
event.preventDefault();
event.stopPropagation();
this.query.value = "";
this.filterOptions();
}
return;
default:
return;
}
event.preventDefault();
this.selectOption(this.shownOptions[newIndex]);
}
/**
* Filters the options.
*
* @abstract
*/
filterOptions() {
throw new Error("Method not implemented");
}
/**
* Selects an option.
*
* @param option {T|undefined} the option.
*/
selectOption(option) {
this.options.forEach((opt) => opt.setActive(false));
if (option === undefined) {
return;
}
option.setActive(true);
this.query.setAttribute("aria-activedescendant", option.elementID);
option.scrollIntoView();
}
}
/**
* The base abstract option
*
* @abstract
*/
class BaseOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/**
* The element ID
* @type {string}
*/
elementID;
/**
* Constructs the base abstract option.
*
* @param element {HTMLLIElement} the element
*/
constructor(element) {
this.#element = element;
this.elementID = element.id;
}
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
this.#element.ariaSelected = "true";
} else {
this.#element.classList.remove("active");
this.#element.ariaSelected = "false";
}
}
/**
* Clicks the option.
*
*/
click() {
this.#element.click();
}
/**
* Scrolls the option into view.
*
*/
scrollIntoView() {
this.#element.scrollIntoView({block: "nearest"});
}
}
+177
View File
@@ -0,0 +1,177 @@
/* The Mia! Accounting Project
* base-tablist.js: The JavaScript for base abstract tablist
*/
/* Copyright (c) 2026 imacat.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Author: imacat@mail.imacat.idv.tw (imacat)
* First written: 2026/4/16
*/
"use strict";
/**
* The base abstract tablist.
*
* @abstract
* @template {BaseTab} T
*/
class BaseTablist {
/**
* The tabs.
* @type {T[]}
*/
tabs;
/**
* The current tab.
* @type {T}
*/
currentTab;
/**
* Constructs a new base abstract tablist.
*
* @param tablist {HTMLUListElement} the tab list
*/
constructor(tablist) {
tablist.onkeydown = this.onTabKeyDown.bind(this);
}
/**
* Actions when keys are pressed on the tabs.
*
* @param event {KeyboardEvent} the key event
*/
onTabKeyDown(event) {
const currentIndex = this.tabs.indexOf(this.currentTab);
if (currentIndex === -1) {
return;
}
let newIndex = currentIndex;
switch (event.key) {
case "ArrowRight":
newIndex = (newIndex + 1) % this.tabs.length;
break;
case "ArrowLeft":
newIndex = (newIndex - 1 + this.tabs.length) % this.tabs.length;
break;
case "Home":
newIndex = 0;
break;
case "End":
newIndex = this.tabs.length - 1;
break;
default:
return;
}
event.preventDefault();
this.tabs[newIndex].focus();
this.onTabFocus(this.tabs[newIndex]);
}
/**
* Actions when a tab is focused.
*
* @param tab {T} the tab
*/
onTabFocus(tab) { /* Do nothing */ }
/**
* Switches to a tab.
*
* @param tab {T} the tab
*/
switchTo(tab) {
this.tabs.forEach(t => t.setActive(t === tab));
this.currentTab = tab;
this.currentTab.onActivated();
}
}
/**
* The base abstract tab.
*
* @abstract
*/
class BaseTab {
/**
* The tab element.
* @type {HTMLButtonElement}
*/
#tab;
/**
* The panel element.
* @type {HTMLDivElement}
*/
#panel;
/**
* Constructs a new base abstract tab.
*
* @param tab {HTMLButtonElement} The tab element.
* @param panel {HTMLDivElement} The panel element.
* @param switchTo {function(BaseTab): void} The function to switch to the tab.
*/
constructor(tab, panel, switchTo) {
this.#tab = tab;
this.#panel = panel;
this.#tab.onclick = () => switchTo(this);
}
/**
* Sets the active state of the tab.
*
* @param isActive {boolean} true if the tab is active, false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#tab.classList.add("active");
this.#tab.tabIndex = 0;
this.#tab.ariaSelected = "true";
this.#panel.classList.remove("d-none");
} else {
this.#tab.classList.remove("active");
this.#tab.tabIndex = -1;
this.#tab.ariaSelected = "false";
this.#panel.classList.add("d-none");
}
}
/**
* Returns whether the tab is active.
*
* @returns {boolean} true if the tab is active, false otherwise
*/
isActive() {
return this.#tab.classList.contains("active");
}
/**
* Actions when the tab is activated.
*/
onActivated() { /* Do nothing */ }
/**
* Focuses the tab.
*/
focus() {
this.#tab.focus();
}
}
+167 -191
View File
@@ -25,8 +25,9 @@
/** /**
* A description editor. * A description editor.
* *
* @extends {BaseTablist<BaseDescriptionEditorTab>}
*/ */
class DescriptionEditor { class DescriptionEditor extends BaseTablist {
/** /**
* The line item editor * The line item editor
@@ -58,12 +59,6 @@ class DescriptionEditor {
*/ */
debitCredit; debitCredit;
/**
* The current tab
* @type {DescriptionEditorTabPlane}
*/
currentTab;
/** /**
* The description input * The description input
* @type {HTMLInputElement} * @type {HTMLInputElement}
@@ -125,10 +120,10 @@ class DescriptionEditor {
selectedAccount = null; selectedAccount = null;
/** /**
* The tab planes * The tabs by their ID.
* @type {{general: DescriptionEditorGeneralTagTab, travel: DescriptionEditorGeneralTripTab, bus: DescriptionEditorBusTripTab, recurring: DescriptionEditorRecurringTab, annotation: DescriptionEditorAnnotationTab}} * @type {DescriptionEditorTabFactory}
*/ */
tabPlanes = {}; #tabsByID;
/** /**
* Constructs a description editor. * Constructs a description editor.
@@ -137,31 +132,38 @@ class DescriptionEditor {
* @param debitCredit {string} either "debit" or "credit" * @param debitCredit {string} either "debit" or "credit"
*/ */
constructor(lineItemEditor, debitCredit) { constructor(lineItemEditor, debitCredit) {
const prefix = `accounting-description-editor-${debitCredit}`;
super(document.getElementById(`${prefix}-tab-list`));
this.prefix = prefix;
this.lineItemEditor = lineItemEditor; this.lineItemEditor = lineItemEditor;
this.debitCredit = debitCredit; this.debitCredit = debitCredit;
this.prefix = `accounting-description-editor-${debitCredit}`; this.#form = document.getElementById(prefix);
this.#form = document.getElementById(this.prefix); this.#modal = document.getElementById(`${prefix}-modal`);
this.#modal = document.getElementById(`${this.prefix}-modal`); this.#descriptionInput = document.getElementById(`${prefix}-description`);
this.#descriptionInput = document.getElementById(`${this.prefix}-description`); this.#offsetButton = document.getElementById(`${prefix}-offset`);
this.#offsetButton = document.getElementById(`${this.prefix}-offset`); this.number = document.getElementById(`${prefix}-annotation-number`);
this.number = document.getElementById(`${this.prefix}-annotation-number`); this.note = document.getElementById(`${prefix}-annotation-note`);
this.note = document.getElementById(`${this.prefix}-annotation-note`); this.#confirmedAccountPlaceholder = new DescriptionEditorConfirmedAccount(this, document.getElementById(`${prefix}-account-confirmed`));
this.#confirmedAccountPlaceholder = new DescriptionEditorConfirmedAccount(this, document.getElementById(`${this.prefix}-account-confirmed`)); this.#allSuggestedAccounts = Array.from(document.getElementsByClassName(`${prefix}-account`)).map((button) => new DescriptionEditorSuggestedAccount(this, button));
this.#allSuggestedAccounts = Array.from(document.getElementsByClassName(`${this.prefix}-account`)).map((button) => new DescriptionEditorSuggestedAccount(this, button));
for (const cls of [DescriptionEditorGeneralTagTab, DescriptionEditorGeneralTripTab, DescriptionEditorBusTripTab, DescriptionEditorRecurringTab, DescriptionEditorAnnotationTab]) { this.#tabsByID = new DescriptionEditorTabFactory(this);
const tab = new cls(this); this.tabs = [this.#tabsByID.general, this.#tabsByID.travel, this.#tabsByID.bus, this.#tabsByID.recurring, this.#tabsByID.annotation];
this.tabPlanes[tab.tabId()] = tab; this.currentTab = this.tabs[0];
}
this.currentTab = this.tabPlanes.general;
this.#descriptionInput.onchange = () => this.#onDescriptionChange(); this.#descriptionInput.onchange = () => this.#onDescriptionChange();
this.#offsetButton.onclick = () => this.lineItemEditor.originalLineItemSelector.onOpen(); this.#offsetButton.onclick = () => this.lineItemEditor.originalLineItemSelector.onOpen(this.#modal.id);
this.#form.onsubmit = () => { this.#form.onsubmit = () => {
if (this.currentTab.validate()) { if (this.currentTab.validate()) {
this.#submit(); this.#submit();
} }
return false; return false;
}; };
const closeButton = document.getElementById(`${prefix}-close`);
this.#modal.onkeydown = (event) => {
if (event.key === "Escape") {
closeButton.click();
}
};
} }
/** /**
@@ -199,26 +201,26 @@ class DescriptionEditor {
* *
*/ */
#onDescriptionChange() { #onDescriptionChange() {
this.#resetTabPlanes(); this.#resetTabs();
this.selectedAccount = null; this.selectedAccount = null;
this.description = this.description.trim(); this.description = this.description.trim();
for (const tabPlane of [this.tabPlanes.recurring, this.tabPlanes.bus, this.tabPlanes.travel, this.tabPlanes.general]) { for (const tab of [this.#tabsByID.recurring, this.#tabsByID.bus, this.#tabsByID.travel, this.#tabsByID.general]) {
if (tabPlane.populate()) { if (tab.populate()) {
break; break;
} }
} }
this.tabPlanes.annotation.populate(); this.#tabsByID.annotation.populate();
} }
/** /**
* Resets the tab planes. * Resets the tabs.
* *
*/ */
#resetTabPlanes() { #resetTabs() {
for (const tabPlane of Object.values(this.tabPlanes)) { for (const tab of this.tabs) {
tabPlane.reset(); tab.reset();
} }
this.tabPlanes.general.switchToMe(); this.switchTo(this.tabs[0]);
} }
/** /**
@@ -463,12 +465,63 @@ class DescriptionEditorConfirmedAccount extends DescriptionEditorAccount {
} }
/** /**
* A tab plane. * The tab factory.
*
* @private
*/
class DescriptionEditorTabFactory {
/**
* The general tag tab
* @type {GeneralTagTab}
*/
general;
/**
* The general trip tab
* @type {GeneralTripTab}
*/
travel;
/**
* The bus trip tab
* @type {BusTripTab}
*/
bus;
/**
* The recurring transactions tab
* @type {RecurringTab}
*/
recurring;
/**
* The annotation tab
* @type {AnnotationTab}
*/
annotation;
/**
* Constructs the tab factory
*
* @param editor {DescriptionEditor} the parent description editor
*/
constructor(editor) {
this.general = new GeneralTagTab(editor);
this.travel = new GeneralTripTab(editor);
this.bus = new BusTripTab(editor);
this.recurring = new RecurringTab(editor);
this.annotation = new AnnotationTab(editor);
}
}
/**
* The base abstract tab in the description editor.
* *
* @abstract * @abstract
* @private * @private
*/ */
class DescriptionEditorTabPlane { class BaseDescriptionEditorTab extends BaseTab {
/** /**
* The parent description editor * The parent description editor
@@ -483,47 +536,29 @@ class DescriptionEditorTabPlane {
prefix; prefix;
/** /**
* The tab * Constructs a base abstract tab in the description editor.
* @type {HTMLSpanElement}
*/
#tab;
/**
* The page
* @type {HTMLDivElement}
*/
#page;
/**
* Constructs a tab plane.
* *
* @param tabID {string} the tab ID
* @param editor {DescriptionEditor} the parent description editor * @param editor {DescriptionEditor} the parent description editor
*/ */
constructor(editor) { constructor(tabID, editor) {
const prefix = `${editor.prefix}-${tabID}`;
const tab = document.getElementById(`${prefix}-tab`);
const panel = document.getElementById(`${prefix}-panel`);
super(tab, panel, editor.switchTo.bind(editor));
this.editor = editor; this.editor = editor;
this.prefix = `${this.editor.prefix}-${this.tabId()}`; this.prefix = prefix;
this.#tab = document.getElementById(`${this.prefix}-tab`);
this.#page = document.getElementById(`${this.prefix}-page`);
this.#tab.onclick = () => this.switchToMe();
} }
/** /**
* The tab ID * Resets the tab panel input.
*
* @return {string}
* @abstract
*/
tabId() { throw new Error("Method not implemented.") };
/**
* Resets the tab plane input.
* *
* @abstract * @abstract
*/ */
reset() { throw new Error("Method not implemented."); } reset() { throw new Error("Method not implemented."); }
/** /**
* Populates the tab plane with the description input. * Populates the tab panel with the description input.
* *
* @return {boolean} true if the description input matches this tab, or false otherwise * @return {boolean} true if the description input matches this tab, or false otherwise
* @abstract * @abstract
@@ -531,39 +566,21 @@ class DescriptionEditorTabPlane {
populate() { throw new Error("Method not implemented."); } populate() { throw new Error("Method not implemented."); }
/** /**
* Validates the input in the tab plane. * Validates the input in the tab panel.
* *
* @return {boolean} true if valid, or false otherwise * @return {boolean} true if valid, or false otherwise
* @abstract * @abstract
*/ */
validate() { throw new Error("Method not implemented."); } validate() { throw new Error("Method not implemented."); }
/**
* Switches to the tab plane.
*
*/
switchToMe() {
for (const tabPlane of Object.values(this.editor.tabPlanes)) {
tabPlane.#tab.classList.remove("active")
tabPlane.#tab.ariaCurrent = "false";
tabPlane.#page.classList.add("d-none");
tabPlane.#page.ariaCurrent = "false";
}
this.#tab.classList.add("active");
this.#tab.ariaCurrent = "page";
this.#page.classList.remove("d-none");
this.#page.ariaCurrent = "page";
this.editor.currentTab = this;
}
} }
/** /**
* A tag plane with selectable tags. * The base abstract tab with selectable tags.
* *
* @abstract * @abstract
* @private * @private
*/ */
class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane { class BaseTagTab extends BaseDescriptionEditorTab {
/** /**
* The tag input * The tag input
@@ -581,20 +598,21 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
* The tag buttons * The tag buttons
* @type {HTMLButtonElement[]} * @type {HTMLButtonElement[]}
*/ */
tagButtons; #tagButtons;
/** /**
* Constructs a tab plane. * Constructs a base abstract tab with selectable tags.
* *
* @param tabID {string} the tab ID
* @param editor {DescriptionEditor} the parent description editor * @param editor {DescriptionEditor} the parent description editor
* @override * @override
*/ */
constructor(editor) { constructor(tabID, editor) {
super(editor); super(tabID, editor);
this.tag = document.getElementById(`${this.prefix}-tag`); this.tag = document.getElementById(`${this.prefix}-tag`);
this.tagError = document.getElementById(`${this.prefix}-tag-error`); this.tagError = document.getElementById(`${this.prefix}-tag-error`);
// noinspection JSValidateTypes // noinspection JSValidateTypes
this.tagButtons = Array.from(document.getElementsByClassName(`${this.prefix}-btn-tag`)); this.#tagButtons = Array.from(document.getElementsByClassName(`${this.prefix}-btn-tag`));
this.initializeTagButtons(); this.initializeTagButtons();
this.tag.onchange = () => { this.tag.onchange = () => {
this.onTagChange(); this.onTagChange();
@@ -609,7 +627,7 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
onTagChange() { onTagChange() {
this.tag.value = this.tag.value.trim(); this.tag.value = this.tag.value.trim();
let isMatched = false; let isMatched = false;
for (const tagButton of this.tagButtons) { for (const tagButton of this.#tagButtons) {
if (tagButton.dataset.value === this.tag.value) { if (tagButton.dataset.value === this.tag.value) {
tagButton.classList.remove("btn-outline-primary"); tagButton.classList.remove("btn-outline-primary");
tagButton.classList.add("btn-primary"); tagButton.classList.add("btn-primary");
@@ -627,19 +645,18 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
} }
/** /**
* Updates the description according to the input in the tab plane. * Updates the description according to the input in the tab panel.
* *
* @abstract * @abstract
*/ */
updateDescription() { throw new Error("Method not implemented."); } updateDescription() { throw new Error("Method not implemented."); }
/** /**
* Switches to the tab plane. * @inheritDoc
* * @override
*/ */
switchToMe() { onActivated() {
super.switchToMe(); for (const tagButton of this.#tagButtons) {
for (const tagButton of this.tagButtons) {
if (tagButton.classList.contains("btn-primary")) { if (tagButton.classList.contains("btn-primary")) {
this.editor.updateCurrentSuggestedAccounts(tagButton); this.editor.updateCurrentSuggestedAccounts(tagButton);
return; return;
@@ -653,9 +670,9 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
* *
*/ */
initializeTagButtons() { initializeTagButtons() {
for (const tagButton of this.tagButtons) { for (const tagButton of this.#tagButtons) {
tagButton.onclick = () => { tagButton.onclick = () => {
for (const otherButton of this.tagButtons) { for (const otherButton of this.#tagButtons) {
otherButton.classList.remove("btn-primary"); otherButton.classList.remove("btn-primary");
otherButton.classList.add("btn-outline-primary"); otherButton.classList.add("btn-outline-primary");
} }
@@ -701,7 +718,7 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
} }
/** /**
* Resets the tab plane input. * Resets the tab panel input.
* *
* @override * @override
*/ */
@@ -709,7 +726,7 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
this.tag.value = ""; this.tag.value = "";
this.tag.classList.remove("is-invalid"); this.tag.classList.remove("is-invalid");
this.tagError.innerText = ""; this.tagError.innerText = "";
for (const tagButton of this.tagButtons) { for (const tagButton of this.#tagButtons) {
tagButton.classList.remove("btn-primary"); tagButton.classList.remove("btn-primary");
tagButton.classList.add("btn-outline-primary"); tagButton.classList.add("btn-outline-primary");
} }
@@ -717,24 +734,24 @@ class DescriptionEditorTagTabPlane extends DescriptionEditorTabPlane {
} }
/** /**
* The general tag tab plane. * The general tag tab.
* *
* @private * @private
*/ */
class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane { class GeneralTagTab extends BaseTagTab {
/** /**
* The tab ID * Constructs a general tag tab.
* *
* @return {string} * @param editor {DescriptionEditor} the parent description editor
* @abstract * @override
*/ */
tabId() { constructor(editor) {
return "general"; super("general", editor);
}; }
/** /**
* Updates the description according to the input in the tab plane. * Updates the description according to the input in the tab panel.
* *
* @override * @override
*/ */
@@ -749,7 +766,7 @@ class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* Populates the tab plane with the description input. * Populates the tab panel with the description input.
* *
* @return {boolean} true if the description input matches this tab, or false otherwise * @return {boolean} true if the description input matches this tab, or false otherwise
* @override * @override
@@ -763,12 +780,12 @@ class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
this.tag.value = found[1]; this.tag.value = found[1];
this.onTagChange(); this.onTagChange();
} }
this.switchToMe(); this.editor.switchTo(this);
return true; return true;
} }
/** /**
* Validates the input in the tab plane. * Validates the input in the tab panel.
* *
* @return {boolean} true if valid, or false otherwise * @return {boolean} true if valid, or false otherwise
*/ */
@@ -778,11 +795,11 @@ class DescriptionEditorGeneralTagTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* The general trip tab plane. * The general trip tab.
* *
* @private * @private
*/ */
class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane { class GeneralTripTab extends BaseTagTab {
/** /**
* The origin * The origin
@@ -815,13 +832,13 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
#directionButtons; #directionButtons;
/** /**
* Constructs a tab plane. * Constructs a general trip tab.
* *
* @param editor {DescriptionEditor} the parent description editor * @param editor {DescriptionEditor} the parent description editor
* @override * @override
*/ */
constructor(editor) { constructor(editor) {
super(editor); super("travel", editor);
this.#from = document.getElementById(`${this.prefix}-from`); this.#from = document.getElementById(`${this.prefix}-from`);
this.#fromError = document.getElementById(`${this.prefix}-from-error`); this.#fromError = document.getElementById(`${this.prefix}-from-error`);
this.#to = document.getElementById(`${this.prefix}-to`); this.#to = document.getElementById(`${this.prefix}-to`);
@@ -852,17 +869,7 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* The tab ID * Updates the description according to the input in the tab panel.
*
* @return {string}
* @abstract
*/
tabId() {
return "travel";
};
/**
* Updates the description according to the input in the tab plane.
* *
* @override * @override
*/ */
@@ -878,7 +885,7 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* Resets the tab plane input. * Resets the tab panel input.
* *
* @override * @override
*/ */
@@ -902,7 +909,7 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* Populates the tab plane with the description input. * Populates the tab panel with the description input.
* *
* @return {boolean} true if the description input matches this tab, or false otherwise * @return {boolean} true if the description input matches this tab, or false otherwise
* @override * @override
@@ -927,12 +934,12 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
} }
} }
this.#to.value = found[4]; this.#to.value = found[4];
this.switchToMe(); this.editor.switchTo(this);
return true; return true;
} }
/** /**
* Validates the input in the tab plane. * Validates the input in the tab panel.
* *
* @return {boolean} true if valid, or false otherwise * @return {boolean} true if valid, or false otherwise
* @override * @override
@@ -977,11 +984,11 @@ class DescriptionEditorGeneralTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* The bus trip tab plane. * The bus trip tab.
* *
* @private * @private
*/ */
class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane { class BusTripTab extends BaseTagTab {
/** /**
* The route * The route
@@ -1020,13 +1027,13 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
#toError; #toError;
/** /**
* Constructs a tab plane. * Constructs a bus trip tab.
* *
* @param editor {DescriptionEditor} the parent description editor * @param editor {DescriptionEditor} the parent description editor
* @override * @override
*/ */
constructor(editor) { constructor(editor) {
super(editor); super("bus", editor);
this.#route = document.getElementById(`${this.prefix}-route`); this.#route = document.getElementById(`${this.prefix}-route`);
this.#routeError = document.getElementById(`${this.prefix}-route-error`); this.#routeError = document.getElementById(`${this.prefix}-route-error`);
this.#from = document.getElementById(`${this.prefix}-from`); this.#from = document.getElementById(`${this.prefix}-from`);
@@ -1051,17 +1058,7 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* The tab ID * Updates the description according to the input in the tab panel.
*
* @return {string}
* @abstract
*/
tabId() {
return "bus";
};
/**
* Updates the description according to the input in the tab plane.
* *
* @override * @override
*/ */
@@ -1070,7 +1067,7 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* Resets the tab plane input. * Resets the tab panel input.
* *
* @override * @override
*/ */
@@ -1088,7 +1085,7 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* Populates the tab plane with the description input. * Populates the tab panel with the description input.
* *
* @return {boolean} true if the description input matches this tab, or false otherwise * @return {boolean} true if the description input matches this tab, or false otherwise
* @override * @override
@@ -1105,12 +1102,12 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
this.#route.value = found[2]; this.#route.value = found[2];
this.#from.value = found[3]; this.#from.value = found[3];
this.#to.value = found[4]; this.#to.value = found[4];
this.switchToMe(); this.editor.switchTo(this);
return true; return true;
} }
/** /**
* Validates the input in the tab plane. * Validates the input in the tab panel.
* *
* @return {boolean} true if valid, or false otherwise * @return {boolean} true if valid, or false otherwise
*/ */
@@ -1165,11 +1162,11 @@ class DescriptionEditorBusTripTab extends DescriptionEditorTagTabPlane {
} }
/** /**
* The recurring transaction tab plane. * The recurring transaction tab.
* *
* @private * @private
*/ */
class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane { class RecurringTab extends BaseDescriptionEditorTab {
/** /**
* The month names * The month names
@@ -1184,13 +1181,13 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
#itemButtons; #itemButtons;
/** /**
* Constructs a tab plane. * Constructs a recurring transaction tab.
* *
* @param editor {DescriptionEditor} the parent description editor * @param editor {DescriptionEditor} the parent description editor
* @override * @override
*/ */
constructor(editor) { constructor(editor) {
super(editor); super("recurring", editor);
this.#monthNames = [ this.#monthNames = [
"", "",
A_("January"), A_("February"), A_("March"), A_("April"), A_("January"), A_("February"), A_("March"), A_("April"),
@@ -1232,17 +1229,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
} }
/** /**
* The tab ID * Resets the tab panel input.
*
* @return {string}
* @abstract
*/
tabId() {
return "recurring";
};
/**
* Resets the tab plane input.
* *
* @override * @override
*/ */
@@ -1254,7 +1241,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
} }
/** /**
* Populates the tab plane with the description input. * Populates the tab panel with the description input.
* *
* @return {boolean} true if the description input matches this tab, or false otherwise * @return {boolean} true if the description input matches this tab, or false otherwise
* @override * @override
@@ -1264,7 +1251,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
if (this.#getDescription(itemButton) === this.editor.description) { if (this.#getDescription(itemButton) === this.editor.description) {
itemButton.classList.add("btn-primary"); itemButton.classList.add("btn-primary");
itemButton.classList.remove("btn-outline-primary"); itemButton.classList.remove("btn-outline-primary");
this.switchToMe(); this.editor.switchTo(this);
return true; return true;
} }
} }
@@ -1272,11 +1259,10 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
} }
/** /**
* Switches to the tab plane. * @inheritDoc
* * @override
*/ */
switchToMe() { onActivated() {
super.switchToMe();
for (const itemButton of this.#itemButtons) { for (const itemButton of this.#itemButtons) {
if (itemButton.classList.contains("btn-primary")) { if (itemButton.classList.contains("btn-primary")) {
this.editor.updateCurrentSuggestedAccounts(itemButton); this.editor.updateCurrentSuggestedAccounts(itemButton);
@@ -1287,7 +1273,7 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
} }
/** /**
* Validates the input in the tab plane. * Validates the input in the tab panel.
* *
* @return {boolean} true if valid, or false otherwise * @return {boolean} true if valid, or false otherwise
* @override * @override
@@ -1298,20 +1284,20 @@ class DescriptionEditorRecurringTab extends DescriptionEditorTabPlane {
} }
/** /**
* The annotation tab plane. * The annotation tab.
* *
* @private * @private
*/ */
class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane { class AnnotationTab extends BaseDescriptionEditorTab {
/** /**
* Constructs a tab plane. * Constructs an annotation tab.
* *
* @param editor {DescriptionEditor} the parent description editor * @param editor {DescriptionEditor} the parent description editor
* @override * @override
*/ */
constructor(editor) { constructor(editor) {
super(editor); super("annotation", editor);
this.editor.number.onchange = () => this.updateDescription(); this.editor.number.onchange = () => this.updateDescription();
this.editor.note.onchange = () => { this.editor.note.onchange = () => {
this.editor.note.value = this.editor.note.value.trim(); this.editor.note.value = this.editor.note.value.trim();
@@ -1320,17 +1306,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
} }
/** /**
* The tab ID * Updates the description according to the input in the tab panel.
*
* @return {string}
* @abstract
*/
tabId() {
return "annotation";
};
/**
* Updates the description according to the input in the tab plane.
* *
* @override * @override
*/ */
@@ -1348,7 +1324,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
} }
/** /**
* Resets the tab plane input. * Resets the tab panel input.
* *
* @override * @override
*/ */
@@ -1358,7 +1334,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
} }
/** /**
* Populates the tab plane with the description input. * Populates the tab panel with the description input.
* *
* @return {boolean} true if the description input matches this tab, or false otherwise * @return {boolean} true if the description input matches this tab, or false otherwise
* @override * @override
@@ -1382,7 +1358,7 @@ class DescriptionEditorAnnotationTab extends DescriptionEditorTabPlane {
} }
/** /**
* Validates the input in the tab plane. * Validates the input in the tab panel.
* *
* @return {boolean} true if valid, or false otherwise * @return {boolean} true if valid, or false otherwise
* @override * @override
@@ -25,15 +25,16 @@
/** /**
* The account selector. * The account selector.
* *
* @extends {BaseCombobox<BaseJournalEntryAccountOption>}
* @private * @private
*/ */
class JournalEntryAccountSelector { class JournalEntryAccountSelector extends BaseCombobox {
/** /**
* The line item editor * The line item editor
* @type {JournalEntryLineItemEditor} * @type {JournalEntryLineItemEditor}
*/ */
lineItemEditor; #lineItemEditor;
/** /**
* Either "debit" or "credit" * Either "debit" or "credit"
@@ -47,12 +48,6 @@ class JournalEntryAccountSelector {
*/ */
#clearButton #clearButton
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/** /**
* The error message when the query has no result * The error message when the query has no result
* @type {HTMLParagraphElement} * @type {HTMLParagraphElement}
@@ -65,15 +60,9 @@ class JournalEntryAccountSelector {
*/ */
#optionList; #optionList;
/**
* The options
* @type {JournalEntryAccountOption[]}
*/
#options;
/** /**
* The more item to show all accounts * The more item to show all accounts
* @type {HTMLLIElement} * @type {MoreItems}
*/ */
#more; #more;
@@ -90,40 +79,55 @@ class JournalEntryAccountSelector {
* @param debitCredit {string} either "debit" or "credit" * @param debitCredit {string} either "debit" or "credit"
*/ */
constructor(lineItemEditor, debitCredit) { constructor(lineItemEditor, debitCredit) {
this.lineItemEditor = lineItemEditor;
this.#debitCredit = debitCredit;
const prefix = `accounting-account-selector-${debitCredit}`; const prefix = `accounting-account-selector-${debitCredit}`;
this.#query = document.getElementById(`${prefix}-query`); const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new JournalEntryAccountOption(element, lineItemEditor.saveAccount.bind(lineItemEditor)));
super(query, options);
this.#lineItemEditor = lineItemEditor;
this.#debitCredit = debitCredit;
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`); this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`); this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new JournalEntryAccountOption(this, element)); const moreElement = document.getElementById(`${prefix}-more`);
this.#more = document.getElementById(`${prefix}-more`); this.#more = new MoreItems(moreElement);
this.#clearButton = document.getElementById(`${prefix}-btn-clear`); moreElement.onclick = () => {
this.#more.onclick = () => {
this.#isShowMore = true; this.#isShowMore = true;
this.#more.classList.add("d-none"); this.#more.setShown(false);
this.#filterOptions(); this.filterOptions();
};
this.#clearButton = document.getElementById(`${prefix}-btn-clear`);
this.#clearButton.onclick = () => this.#lineItemEditor.clearAccount();
const modal = document.getElementById(`${prefix}-modal`);
const closeButton = document.getElementById(`${prefix}-close`);
modal.onkeydown = (event) => {
if (event.key === "Escape") {
closeButton.click();
}
}; };
this.#query.oninput = () => this.#filterOptions();
this.#clearButton.onclick = () => this.lineItemEditor.clearAccount();
} }
/** /**
* Filters the options. * Filters the options.
* *
* @override
*/ */
#filterOptions() { filterOptions() {
this.shownOptions = [];
const codesInUse = this.#getCodesUsedInForm(); const codesInUse = this.#getCodesUsedInForm();
let isAnyMatched = false; let isAnyMatched = false;
for (const option of this.#options) { for (const option of this.options) {
if (option.isMatched(this.#isShowMore, codesInUse, this.#query.value)) { if (option.isMatched(this.#isShowMore, codesInUse, this.query.value)) {
option.setShown(true); option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true; isAnyMatched = true;
} else { } else {
option.setShown(false); option.setShown(false);
} }
} }
if (!this.#isShowMore) {
this.shownOptions.push(this.#more);
}
if (!isAnyMatched && this.#isShowMore) { if (!isAnyMatched && this.#isShowMore) {
this.#optionList.classList.add("d-none"); this.#optionList.classList.add("d-none");
this.#queryNoResult.classList.remove("d-none"); this.#queryNoResult.classList.remove("d-none");
@@ -139,9 +143,9 @@ class JournalEntryAccountSelector {
* @return {string[]} the account codes that are used in the form * @return {string[]} the account codes that are used in the form
*/ */
#getCodesUsedInForm() { #getCodesUsedInForm() {
const inUse = this.lineItemEditor.form.getAccountCodesUsed(this.#debitCredit); const inUse = this.#lineItemEditor.form.getAccountCodesUsed(this.#debitCredit);
if (this.lineItemEditor.account !== null) { if (this.#lineItemEditor.account !== null) {
inUse.push(this.lineItemEditor.account.code); inUse.push(this.#lineItemEditor.account.code);
} }
return inUse return inUse
} }
@@ -151,14 +155,13 @@ class JournalEntryAccountSelector {
* *
*/ */
onOpen() { onOpen() {
this.#query.value = ""; this.query.value = "";
this.#isShowMore = false; this.#isShowMore = false;
this.#more.classList.remove("d-none"); this.#more.setShown(true);
this.#filterOptions(); this.filterOptions();
for (const option of this.#options) { this.query.removeAttribute("aria-activedescendant");
option.setActive(this.lineItemEditor.account !== null && option.code === this.lineItemEditor.account.code); this.selectOption(this.shownOptions.find((option) => this.#lineItemEditor.account !== null && option.code === this.#lineItemEditor.account.code));
} if (this.#lineItemEditor.account === null) {
if (this.lineItemEditor.account === null) {
this.#clearButton.classList.add("btn-secondary"); this.#clearButton.classList.add("btn-secondary");
this.#clearButton.classList.remove("btn-danger"); this.#clearButton.classList.remove("btn-danger");
this.#clearButton.disabled = true; this.#clearButton.disabled = true;
@@ -169,6 +172,17 @@ class JournalEntryAccountSelector {
} }
} }
/**
* Selects an option.
*
* @param option {BaseJournalEntryAccountOption|undefined} the option.
* @override
*/
selectOption(option) {
this.#more.setActive(false);
super.selectOption(option);
}
/** /**
* Returns the account selector instances. * Returns the account selector instances.
* *
@@ -186,23 +200,37 @@ class JournalEntryAccountSelector {
} }
/** /**
* An account option * The base abstract account option
* *
* @private * @private
*/ */
class JournalEntryAccountOption { class BaseJournalEntryAccountOption extends BaseOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/** /**
* The account code * The account code
* @type {string} * @type {string}
*/ */
code; code = "";
/**
* Returns whether the account matches the query.
*
* @param isShowMore {boolean} true to show all accounts, or false to show only those in use
* @param codesInUse {string[]} the account codes that are used in the form
* @param query {string} the query term
* @return {boolean} true if the option matches, or false otherwise
*/
isMatched(isShowMore, codesInUse, query) {
return false;
}
}
/**
* An account option
*
* @private
*/
class JournalEntryAccountOption extends BaseJournalEntryAccountOption {
/** /**
* The account title * The account title
@@ -237,11 +265,11 @@ class JournalEntryAccountOption {
/** /**
* Constructs the account in the account selector. * Constructs the account in the account selector.
* *
* @param selector {JournalEntryAccountSelector} the account selector
* @param element {HTMLLIElement} the element * @param element {HTMLLIElement} the element
* @param save {function(JournalEntryAccountOption): void} the callback to save the option
*/ */
constructor(selector, element) { constructor(element, save) {
this.#element = element; super(element);
this.code = element.dataset.code; this.code = element.dataset.code;
this.title = element.dataset.title; this.title = element.dataset.title;
this.text = element.dataset.text; this.text = element.dataset.text;
@@ -249,7 +277,7 @@ class JournalEntryAccountOption {
this.isNeedOffset = element.classList.contains("accounting-account-is-need-offset"); this.isNeedOffset = element.classList.contains("accounting-account-is-need-offset");
this.#queryValues = JSON.parse(element.dataset.queryValues); this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.lineItemEditor.saveAccount(this); element.onclick = () => save(this);
} }
/** /**
@@ -259,6 +287,7 @@ class JournalEntryAccountOption {
* @param codesInUse {string[]} the account codes that are used in the form * @param codesInUse {string[]} the account codes that are used in the form
* @param query {string} the query term * @param query {string} the query term
* @return {boolean} true if the option matches, or false otherwise * @return {boolean} true if the option matches, or false otherwise
* @override
*/ */
isMatched(isShowMore, codesInUse, query) { isMatched(isShowMore, codesInUse, query) {
return this.#isInUseMatched(isShowMore, codesInUse) && this.#isQueryMatched(query); return this.#isInUseMatched(isShowMore, codesInUse) && this.#isQueryMatched(query);
@@ -292,30 +321,12 @@ class JournalEntryAccountOption {
} }
return false; return false;
} }
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
} }
/** /**
* Sets whether the option is active. * The more item to show all accounts.
* *
* @param isActive {boolean} true if active, or false otherwise * @private
*/ */
setActive(isActive) { class MoreItems extends BaseJournalEntryAccountOption {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
} }
@@ -701,12 +701,23 @@ class DebitCreditSubForm {
this.#element.classList.add("accounting-not-empty"); this.#element.classList.add("accounting-not-empty");
this.currency.form.lineItemEditor.onAddNew(this); this.currency.form.lineItemEditor.onAddNew(this);
}; };
this.#element.role = "button";
this.#element.tabIndex = 0;
this.#element.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.#element.click();
}
};
} else { } else {
this.#element.classList.add("accounting-not-empty"); this.#element.classList.add("accounting-not-empty");
this.#element.classList.remove("accounting-clickable"); this.#element.classList.remove("accounting-clickable");
delete this.#element.dataset.bsToggle; delete this.#element.dataset.bsToggle;
delete this.#element.dataset.bsTarget; delete this.#element.dataset.bsTarget;
this.#element.onclick = null; this.#element.onclick = null;
this.#element.removeAttribute("role");
this.#element.tabIndex = -1;
this.#element.onkeydown = null;
} }
setElementShown(this.#content, this.lineItems.length !== 0); setElementShown(this.#content, this.lineItems.length !== 0);
} }
@@ -986,6 +997,12 @@ class LineItemSubForm {
this.#element.parentElement.removeChild(this.#element); this.#element.parentElement.removeChild(this.#element);
this.debitCreditSubForm.deleteLineItem(this); this.debitCreditSubForm.deleteLineItem(this);
}; };
this.#control.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.#control.click();
}
};
} }
/** /**
@@ -66,13 +66,13 @@ class JournalEntryLineItemEditor {
/** /**
* The control of the original line item * The control of the original line item
* @type {HTMLDivElement} * @type {HTMLButtonElement}
*/ */
#originalLineItemControl; #originalLineItemControl;
/** /**
* The original line item * The original line item
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#originalLineItemText; #originalLineItemText;
@@ -90,13 +90,13 @@ class JournalEntryLineItemEditor {
/** /**
* The control of the description * The control of the description
* @type {HTMLDivElement} * @type {HTMLButtonElement}
*/ */
#descriptionControl; #descriptionControl;
/** /**
* The description * The description
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#descriptionText; #descriptionText;
@@ -108,13 +108,13 @@ class JournalEntryLineItemEditor {
/** /**
* The control of the account * The control of the account
* @type {HTMLDivElement} * @type {HTMLButtonElement}
*/ */
#accountControl; #accountControl;
/** /**
* The account * The account
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#accountText; #accountText;
@@ -228,7 +228,7 @@ class JournalEntryLineItemEditor {
this.#accountSelectors = JournalEntryAccountSelector.getInstances(this); this.#accountSelectors = JournalEntryAccountSelector.getInstances(this);
this.originalLineItemSelector = new OriginalLineItemSelector(this); this.originalLineItemSelector = new OriginalLineItemSelector(this);
this.#originalLineItemControl.onclick = () => this.originalLineItemSelector.onOpen() this.#originalLineItemControl.onclick = () => this.originalLineItemSelector.onOpen(this.modal.id)
this.#originalLineItemDelete.onclick = () => this.clearOriginalLineItem(); this.#originalLineItemDelete.onclick = () => this.clearOriginalLineItem();
this.#descriptionControl.onclick = () => this.#descriptionEditors[this.debitCredit].onOpen(); this.#descriptionControl.onclick = () => this.#descriptionEditors[this.debitCredit].onOpen();
this.#accountControl.onclick = () => this.#accountSelectors[this.debitCredit].onOpen(); this.#accountControl.onclick = () => this.#accountSelectors[this.debitCredit].onOpen();
+51 -73
View File
@@ -298,6 +298,14 @@ class RecurringExpenseIncomeSubForm {
this.#element.dataset.bsTarget = `#${this.editor.modal.id}`; this.#element.dataset.bsTarget = `#${this.editor.modal.id}`;
this.#element.onclick = () => this.editor.onAddNew(); this.#element.onclick = () => this.editor.onAddNew();
this.#content.classList.add("d-none"); this.#content.classList.add("d-none");
this.#element.role = "button";
this.#element.tabIndex = 0;
this.#element.onkeydown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
this.#element.click();
}
};
} else { } else {
this.#element.classList.add("accounting-not-empty"); this.#element.classList.add("accounting-not-empty");
this.#element.classList.remove("accounting-clickable"); this.#element.classList.remove("accounting-clickable");
@@ -305,6 +313,9 @@ class RecurringExpenseIncomeSubForm {
delete this.#element.dataset.bsTarget; delete this.#element.dataset.bsTarget;
this.#element.onclick = null; this.#element.onclick = null;
this.#content.classList.remove("d-none"); this.#content.classList.remove("d-none");
this.#element.removeAttribute("role");
this.#element.tabIndex = -1;
this.#element.onkeydown = null;
} }
} }
@@ -375,7 +386,7 @@ class RecurringItemSubForm {
/** /**
* The control * The control
* @type {HTMLDivElement} * @type {HTMLButtonElement}
*/ */
#control; #control;
@@ -399,7 +410,7 @@ class RecurringItemSubForm {
/** /**
* The text display of the name * The text display of the name
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#nameText; #nameText;
@@ -411,7 +422,7 @@ class RecurringItemSubForm {
/** /**
* The text display of the account * The text display of the account
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#accountText; #accountText;
@@ -423,7 +434,7 @@ class RecurringItemSubForm {
/** /**
* The text display of the description template * The text display of the description template
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#descriptionTemplateText; #descriptionTemplateText;
@@ -595,13 +606,13 @@ class RecurringItemEditor {
/** /**
* The control of the account * The control of the account
* @type {HTMLDivElement} * @type {HTMLButtonElement}
*/ */
#accountControl; #accountControl;
/** /**
* The text display of the account * The text display of the account
* @type {HTMLDivElement} * @type {HTMLSpanElement}
*/ */
#accountContainer; #accountContainer;
@@ -832,15 +843,16 @@ class RecurringItemEditor {
/** /**
* The account selector for the recurring item editor. * The account selector for the recurring item editor.
* *
* @extends {BaseCombobox<RecurringAccount>}
* @private * @private
*/ */
class RecurringAccountSelector { class RecurringAccountSelector extends BaseCombobox {
/** /**
* The recurring item editor * The recurring item editor
* @type {RecurringItemEditor} * @type {RecurringItemEditor}
*/ */
editor; #editor;
/** /**
* Either "expense" or "income" * Either "expense" or "income"
@@ -848,12 +860,6 @@ class RecurringAccountSelector {
*/ */
#expenseIncome; #expenseIncome;
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/** /**
* The error message when the query has no result * The error message when the query has no result
* @type {HTMLParagraphElement} * @type {HTMLParagraphElement}
@@ -866,12 +872,6 @@ class RecurringAccountSelector {
*/ */
#optionList; #optionList;
/**
* The account options
* @type {RecurringAccount[]}
*/
#options;
/** /**
* The button to clear the account * The button to clear the account
* @type {HTMLButtonElement} * @type {HTMLButtonElement}
@@ -884,28 +884,39 @@ class RecurringAccountSelector {
* @param editor {RecurringItemEditor} the recurring item editor * @param editor {RecurringItemEditor} the recurring item editor
*/ */
constructor(editor) { constructor(editor) {
this.editor = editor;
this.#expenseIncome = editor.expenseIncome;
const prefix = `accounting-recurring-accounting-selector-${editor.expenseIncome}`; const prefix = `accounting-recurring-accounting-selector-${editor.expenseIncome}`;
this.#query = document.getElementById(`${prefix}-query`); const query = document.getElementById(`${prefix}-query`);
const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new RecurringAccount(element, editor.saveAccount.bind(editor)));
super(query, options);
this.#editor = editor;
this.#expenseIncome = editor.expenseIncome;
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`); this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`); this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new RecurringAccount(this, element));
this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#query.oninput = () => this.#filterOptions(); this.#clearButton = document.getElementById(`${prefix}-clear`);
this.#clearButton.onclick = () => this.editor.clearAccount(); this.#clearButton.onclick = () => this.#editor.clearAccount();
const modal = document.getElementById(`${prefix}-modal`);
const closeButton = document.getElementById(`${prefix}-close`);
modal.onkeydown = (event) => {
if (event.key === "Escape") {
closeButton.click();
}
};
} }
/** /**
* Filters the options. * Filters the options.
* *
* @override
*/ */
#filterOptions() { filterOptions() {
this.shownOptions = [];
let isAnyMatched = false; let isAnyMatched = false;
for (const option of this.#options) { for (const option of this.options) {
if (option.isMatched(this.#query.value)) { if (option.isMatched(this.query.value)) {
option.setShown(true); option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true; isAnyMatched = true;
} else { } else {
option.setShown(false); option.setShown(false);
@@ -925,12 +936,11 @@ class RecurringAccountSelector {
* *
*/ */
onOpen() { onOpen() {
this.#query.value = ""; this.query.value = "";
this.#filterOptions(); this.filterOptions();
for (const option of this.#options) { this.query.removeAttribute("aria-activedescendant");
option.setActive(option.code === this.editor.accountCode); this.selectOption(this.shownOptions.find((option) => option.code === this.#editor.accountCode));
} if (this.#editor.accountCode === null) {
if (this.editor.accountCode === null) {
this.#clearButton.classList.add("btn-secondary"); this.#clearButton.classList.add("btn-secondary");
this.#clearButton.classList.remove("btn-danger"); this.#clearButton.classList.remove("btn-danger");
this.#clearButton.disabled = true; this.#clearButton.disabled = true;
@@ -947,13 +957,7 @@ class RecurringAccountSelector {
* *
* @private * @private
*/ */
class RecurringAccount { class RecurringAccount extends BaseOption {
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/** /**
* The account code * The account code
@@ -976,16 +980,16 @@ class RecurringAccount {
/** /**
* Constructs the account in the account selector for the recurring item editor. * Constructs the account in the account selector for the recurring item editor.
* *
* @param selector {RecurringAccountSelector} the account selector
* @param element {HTMLLIElement} the element * @param element {HTMLLIElement} the element
* @param save {function(RecurringAccount): void} the callback to save the option
*/ */
constructor(selector, element) { constructor(element, save) {
this.#element = element; super(element);
this.code = element.dataset.code; this.code = element.dataset.code;
this.text = element.dataset.text; this.text = element.dataset.text;
this.#queryValues = JSON.parse(element.dataset.queryValues); this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.editor.saveAccount(this); element.onclick = () => save(this);
} }
/** /**
@@ -1005,30 +1009,4 @@ class RecurringAccount {
} }
return false; return false;
} }
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
} }
@@ -25,27 +25,16 @@
/** /**
* The original line item selector. * The original line item selector.
* *
* @extends {BaseCombobox<OriginalLineItem>}
* @private * @private
*/ */
class OriginalLineItemSelector { class OriginalLineItemSelector extends BaseCombobox {
/** /**
* The line item editor * The line item editor
* @type {JournalEntryLineItemEditor} * @type {JournalEntryLineItemEditor}
*/ */
lineItemEditor; #lineItemEditor;
/**
* The prefix of the HTML ID and class names
* @type {string}
*/
#prefix = "accounting-original-line-item-selector";
/**
* The query input
* @type {HTMLInputElement}
*/
#query;
/** /**
* The error message when the query has no result * The error message when the query has no result
@@ -59,12 +48,6 @@ class OriginalLineItemSelector {
*/ */
#optionList; #optionList;
/**
* The options
* @type {OriginalLineItem[]}
*/
#options;
/** /**
* The options by their ID * The options by their ID
* @type {Object.<string, OriginalLineItem>} * @type {Object.<string, OriginalLineItem>}
@@ -82,22 +65,37 @@ class OriginalLineItemSelector {
*/ */
#debitCredit; #debitCredit;
/**
* The close button.
* @type {HTMLButtonElement}
*/
#closeButton;
/** /**
* Constructs an original line item selector. * Constructs an original line item selector.
* *
* @param lineItemEditor {JournalEntryLineItemEditor} the line item editor * @param lineItemEditor {JournalEntryLineItemEditor} the line item editor
*/ */
constructor(lineItemEditor) { constructor(lineItemEditor) {
this.lineItemEditor = lineItemEditor; const prefix = "accounting-original-line-item-selector";
this.#query = document.getElementById(`${this.#prefix}-query`); const query = document.getElementById(`${prefix}-query`);
this.#queryNoResult = document.getElementById(`${this.#prefix}-option-no-result`); const options = Array.from(document.getElementsByClassName(`${prefix}-option`)).map((element) => new OriginalLineItem(element, lineItemEditor.saveOriginalLineItem.bind(lineItemEditor), lineItemEditor.form));
this.#optionList = document.getElementById(`${this.#prefix}-option-list`); super(query, options);
this.#options = Array.from(document.getElementsByClassName(`${this.#prefix}-option`)).map((element) => new OriginalLineItem(this, element)); this.#lineItemEditor = lineItemEditor;
this.#queryNoResult = document.getElementById(`${prefix}-option-no-result`);
this.#optionList = document.getElementById(`${prefix}-option-list`);
this.#optionById = {}; this.#optionById = {};
for (const option of this.#options) { for (const option of this.options) {
this.#optionById[option.id] = option; this.#optionById[option.id] = option;
} }
this.#query.oninput = () => this.#filterOptions(); this.#closeButton = document.getElementById(`${prefix}-close`);
const modal = document.getElementById(`${prefix}-modal`);
modal.onkeydown = (event) => {
if (event.key === "Escape") {
this.#closeButton.click();
}
};
} }
/** /**
@@ -127,7 +125,7 @@ class OriginalLineItemSelector {
* *
*/ */
#updateNetBalances() { #updateNetBalances() {
const otherLineItems = this.lineItemEditor.form.getLineItems().filter((lineItem) => lineItem !== this.lineItemEditor.lineItem); const otherLineItems = this.#lineItemEditor.form.getLineItems().filter((lineItem) => lineItem !== this.#lineItemEditor.lineItem);
const otherOffsets = {} const otherOffsets = {}
for (const otherLineItem of otherLineItems) { for (const otherLineItem of otherLineItems) {
const otherOriginalLineItemId = otherLineItem.originalLineItemId; const otherOriginalLineItemId = otherLineItem.originalLineItemId;
@@ -140,7 +138,7 @@ class OriginalLineItemSelector {
} }
otherOffsets[otherOriginalLineItemId] = otherOffsets[otherOriginalLineItemId].plus(amount); otherOffsets[otherOriginalLineItemId] = otherOffsets[otherOriginalLineItemId].plus(amount);
} }
for (const option of this.#options) { for (const option of this.options) {
if (option.id in otherOffsets) { if (option.id in otherOffsets) {
option.updateNetBalance(otherOffsets[option.id]); option.updateNetBalance(otherOffsets[option.id]);
} else { } else {
@@ -152,12 +150,15 @@ class OriginalLineItemSelector {
/** /**
* Filters the options. * Filters the options.
* *
* @override
*/ */
#filterOptions() { filterOptions() {
this.shownOptions = [];
let isAnyMatched = false; let isAnyMatched = false;
for (const option of this.#options) { for (const option of this.options) {
if (option.isMatched(this.#debitCredit, this.#currencyCode, this.#query.value)) { if (option.isMatched(this.#debitCredit, this.#currencyCode, this.query.value)) {
option.setShown(true); option.setShown(true);
this.shownOptions.push(option);
isAnyMatched = true; isAnyMatched = true;
} else { } else {
option.setShown(false); option.setShown(false);
@@ -175,16 +176,17 @@ class OriginalLineItemSelector {
/** /**
* The callback when the original line item selector is shown. * The callback when the original line item selector is shown.
* *
* @param parentID {string} the ID of the parent element
*/ */
onOpen() { onOpen(parentID) {
this.#currencyCode = this.lineItemEditor.currencyCode; this.#closeButton.dataset.bsTarget = `#${parentID}`;
this.#debitCredit = this.lineItemEditor.debitCredit; this.#currencyCode = this.#lineItemEditor.currencyCode;
for (const option of this.#options) { this.#debitCredit = this.#lineItemEditor.debitCredit;
option.setActive(option.id === this.lineItemEditor.originalLineItemId); this.query.value = "";
}
this.#query.value = "";
this.#updateNetBalances(); this.#updateNetBalances();
this.#filterOptions(); this.filterOptions();
this.query.removeAttribute("aria-activedescendant");
this.selectOption(this.shownOptions.find((option) => option.id === this.#lineItemEditor.originalLineItemId));
} }
} }
@@ -193,7 +195,7 @@ class OriginalLineItemSelector {
* *
* @private * @private
*/ */
class OriginalLineItem { class OriginalLineItem extends BaseOption {
/** /**
* The journal entry form * The journal entry form
@@ -201,12 +203,6 @@ class OriginalLineItem {
*/ */
#form; #form;
/**
* The element
* @type {HTMLLIElement}
*/
#element;
/** /**
* The ID * The ID
* @type {string} * @type {string}
@@ -276,12 +272,13 @@ class OriginalLineItem {
/** /**
* Constructs an original line item. * Constructs an original line item.
* *
* @param selector {OriginalLineItemSelector} the original line item selector
* @param element {HTMLLIElement} the element * @param element {HTMLLIElement} the element
* @param save {function(OriginalLineItem): void} the callback to save the option
* @param form {JournalEntryForm} the journal entry form
*/ */
constructor(selector, element) { constructor(element, save, form) {
this.#form = selector.lineItemEditor.form; super(element);
this.#element = element; this.#form = form;
this.id = element.dataset.id; this.id = element.dataset.id;
this.date = element.dataset.date; this.date = element.dataset.date;
this.#debitCredit = element.dataset.debitCredit; this.#debitCredit = element.dataset.debitCredit;
@@ -293,7 +290,8 @@ class OriginalLineItem {
this.netBalanceText = document.getElementById(`accounting-original-line-item-selector-option-${this.id}-net-balance`); this.netBalanceText = document.getElementById(`accounting-original-line-item-selector-option-${this.id}-net-balance`);
this.text = element.dataset.text; this.text = element.dataset.text;
this.#queryValues = JSON.parse(element.dataset.queryValues); this.#queryValues = JSON.parse(element.dataset.queryValues);
this.#element.onclick = () => selector.lineItemEditor.saveOriginalLineItem(this);
element.onclick = () => save(this);
} }
/** /**
@@ -382,30 +380,4 @@ class OriginalLineItem {
const whole = Number(this.netBalance.minus(frac)); const whole = Number(this.netBalance.minus(frac));
return String(whole) + String(frac).substring(1); return String(whole) + String(frac).substring(1);
} }
/**
* Sets whether the option is shown.
*
* @param isShown {boolean} true to show, or false otherwise
*/
setShown(isShown) {
if (isShown) {
this.#element.classList.remove("d-none");
} else {
this.#element.classList.add("d-none");
}
}
/**
* Sets whether the option is active.
*
* @param isActive {boolean} true if active, or false otherwise
*/
setActive(isActive) {
if (isActive) {
this.#element.classList.add("active");
} else {
this.#element.classList.remove("active");
}
}
} }
+51 -107
View File
@@ -30,21 +30,16 @@ document.addEventListener("DOMContentLoaded", () => {
/** /**
* The period chooser. * The period chooser.
* *
* @extends {BaseTablist<BasePeriodTab>}
* @private * @private
*/ */
class PeriodChooser { class PeriodChooser extends BaseTablist {
/** /**
* The modal of the period chooser * The URL template for different periods.
* @type {HTMLDivElement} * @type {string}
*/ */
modal; urlTemplate;
/**
* The tab planes
* @type {{month: MonthTab, year: YearTab, day: DayTab, custom: CustomTab}}
*/
tabPlanes = {};
/** /**
* Constructs the period chooser. * Constructs the period chooser.
@@ -52,12 +47,23 @@ class PeriodChooser {
*/ */
constructor() { constructor() {
const prefix = "accounting-period-chooser"; const prefix = "accounting-period-chooser";
this.modal = document.getElementById(`${prefix}-modal`); super(document.getElementById(`${prefix}-tab-list`));
for (const cls of [MonthTab, YearTab, DayTab, CustomTab]) { this.tabs = [new MonthTab(this), new YearTab(this), new DayTab(this), new CustomTab(this)];
const tab = new cls(this); const modal = document.getElementById(`${prefix}-modal`);
this.tabPlanes[tab.tabId()] = tab; this.urlTemplate = modal.dataset.urlTemplate;
for (const tab of this.tabs) {
if (tab.isActive()) {
this.currentTab = tab;
break;
} }
} }
}
/**
* @inheritDoc
* @override
*/
onTabFocus(tab) { this.switchTo(tab); }
/** /**
* The period chooser. * The period chooser.
@@ -75,12 +81,12 @@ class PeriodChooser {
} }
/** /**
* A tab plane. * A base abstract period tab.
* *
* @abstract * @abstract
* @private * @private
*/ */
class TabPlane { class BasePeriodTab extends BaseTab {
/** /**
* The period chooser * The period chooser
@@ -95,62 +101,27 @@ class TabPlane {
prefix; prefix;
/** /**
* The tab * Constructs a base abstract period tab.
* @type {HTMLSpanElement}
*/
#tab;
/**
* The page
* @type {HTMLDivElement}
*/
#page;
/**
* Constructs a tab plane.
* *
* @param tabID {string} the tab ID
* @param chooser {PeriodChooser} the period chooser * @param chooser {PeriodChooser} the period chooser
*/ */
constructor(chooser) { constructor(tabID, chooser) {
const prefix = `accounting-period-chooser-${tabID}`;
const tab = document.getElementById(`${prefix}-tab`);
const panel = document.getElementById(`${prefix}-panel`);
super(tab, panel, chooser.switchTo.bind(chooser));
this.chooser = chooser; this.chooser = chooser;
this.prefix = `accounting-period-chooser-${this.tabId()}`; this.prefix = prefix;
this.#tab = document.getElementById(`${this.prefix}-tab`);
this.#page = document.getElementById(`${this.prefix}-page`);
this.#tab.onclick = () => this.#switchToMe();
}
/**
* The tab ID
*
* @return {string}
* @abstract
*/
tabId() { throw new Error("Method not implemented.") };
/**
* Switches to the tab plane.
*
*/
#switchToMe() {
for (const tabPlane of Object.values(this.chooser.tabPlanes)) {
tabPlane.#tab.classList.remove("active")
tabPlane.#tab.ariaCurrent = "false";
tabPlane.#page.classList.add("d-none");
tabPlane.#page.ariaCurrent = "false";
}
this.#tab.classList.add("active");
this.#tab.ariaCurrent = "page";
this.#page.classList.remove("d-none");
this.#page.ariaCurrent = "page";
} }
} }
/** /**
* The month tab plane. * The month tab.
* *
* @private * @private
*/ */
class MonthTab extends TabPlane { class MonthTab extends BasePeriodTab {
/** /**
* The month chooser. * The month chooser.
@@ -159,12 +130,12 @@ class MonthTab extends TabPlane {
#monthChooser #monthChooser
/** /**
* Constructs a tab plane. * Constructs a month tab.
* *
* @param chooser {PeriodChooser} the period chooser * @param chooser {PeriodChooser} the period chooser
*/ */
constructor(chooser) { constructor(chooser) {
super(chooser); super("month", chooser);
const monthChooser = document.getElementById(`${this.prefix}-chooser`); const monthChooser = document.getElementById(`${this.prefix}-chooser`);
if (monthChooser !== null) { if (monthChooser !== null) {
this.#monthChooser = new tempusDominus.TempusDominus(monthChooser, { this.#monthChooser = new tempusDominus.TempusDominus(monthChooser, {
@@ -184,45 +155,36 @@ class MonthTab extends TabPlane {
const date = e.detail.date; const date = e.detail.date;
const zeroPaddedMonth = `0${date.month + 1}`.slice(-2) const zeroPaddedMonth = `0${date.month + 1}`.slice(-2)
const period = `${date.year}-${zeroPaddedMonth}`; const period = `${date.year}-${zeroPaddedMonth}`;
window.location = chooser.modal.dataset.urlTemplate window.location = chooser.urlTemplate
.replaceAll("PERIOD", period); .replaceAll("PERIOD", period);
}); });
} }
} }
/**
* The tab ID
*
* @return {string}
*/
tabId() {
return "month";
}
} }
/** /**
* The year tab plane. * The year tab.
* *
* @private * @private
*/ */
class YearTab extends TabPlane { class YearTab extends BasePeriodTab {
/** /**
* The tab ID * Constructs a year tab.
* *
* @return {string} * @param chooser {PeriodChooser} the period chooser
*/ */
tabId() { constructor(chooser) {
return "year"; super("year", chooser);
} }
} }
/** /**
* The day tab plane. * The day tab.
* *
* @private * @private
*/ */
class DayTab extends TabPlane { class DayTab extends BasePeriodTab {
/** /**
* The day input * The day input
@@ -237,18 +199,18 @@ class DayTab extends TabPlane {
#dateError; #dateError;
/** /**
* Constructs a tab plane. * Constructs a day tab.
* *
* @param chooser {PeriodChooser} the period chooser * @param chooser {PeriodChooser} the period chooser
*/ */
constructor(chooser) { constructor(chooser) {
super(chooser); super("day", chooser);
this.#date = document.getElementById(`${this.prefix}-date`); this.#date = document.getElementById(`${this.prefix}-date`);
this.#dateError = document.getElementById(`${this.prefix}-date-error`); this.#dateError = document.getElementById(`${this.prefix}-date-error`);
if (this.#date !== null) { if (this.#date !== null) {
this.#date.onchange = () => { this.#date.onchange = () => {
if (this.#validateDate()) { if (this.#validateDate()) {
window.location = chooser.modal.dataset.urlTemplate window.location = chooser.urlTemplate
.replaceAll("PERIOD", this.#date.value); .replaceAll("PERIOD", this.#date.value);
} }
}; };
@@ -275,23 +237,14 @@ class DayTab extends TabPlane {
this.#dateError.innerText = ""; this.#dateError.innerText = "";
return true; return true;
} }
/**
* The tab ID
*
* @return {string}
*/
tabId() {
return "day";
}
} }
/** /**
* The custom tab plane. * The custom tab.
* *
* @private * @private
*/ */
class CustomTab extends TabPlane { class CustomTab extends BasePeriodTab {
/** /**
* The start of the period * The start of the period
@@ -324,12 +277,12 @@ class CustomTab extends TabPlane {
#confirm; #confirm;
/** /**
* Constructs a tab plane. * Constructs a custom tab.
* *
* @param chooser {PeriodChooser} the period chooser * @param chooser {PeriodChooser} the period chooser
*/ */
constructor(chooser) { constructor(chooser) {
super(chooser); super("custom", chooser);
this.#start = document.getElementById(`${this.prefix}-start`); this.#start = document.getElementById(`${this.prefix}-start`);
this.#startError = document.getElementById(`${this.prefix}-start-error`); this.#startError = document.getElementById(`${this.prefix}-start-error`);
this.#end = document.getElementById(`${this.prefix}-end`); this.#end = document.getElementById(`${this.prefix}-end`);
@@ -351,7 +304,7 @@ class CustomTab extends TabPlane {
isValid = this.#validateStart() && isValid; isValid = this.#validateStart() && isValid;
isValid = this.#validateEnd() && isValid; isValid = this.#validateEnd() && isValid;
if (isValid) { if (isValid) {
window.location = chooser.modal.dataset.urlTemplate window.location = chooser.urlTemplate
.replaceAll("PERIOD", `${this.#start.value}-${this.#end.value}`); .replaceAll("PERIOD", `${this.#start.value}-${this.#end.value}`);
} }
}; };
@@ -407,13 +360,4 @@ class CustomTab extends TabPlane {
this.#endError.innerText = ""; this.#endError.innerText = "";
return true; return true;
} }
/**
* The tab ID
*
* @return {string}
*/
tabId() {
return "custom";
}
} }
@@ -27,28 +27,28 @@ First written: 2023/1/31
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}"> <a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }} {{ A_("Edit") }}
</a> </a>
{% endif %} {% endif %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.order", base=obj.base)|accounting_append_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.order", base=obj.base)|accounting_append_next }}">
<i class="fa-solid fa-bars-staggered"></i> <i class="fa-solid fa-bars-staggered" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Order") }}</span> <span class="d-none d-md-inline">{{ A_("Order") }}</span>
</a> </a>
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
{% if obj.can_delete %} {% if obj.can_delete %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal"> <button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span> <span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button> </button>
{% else %} {% else %}
<button class="btn btn-secondary" type="button" disabled="disabled"> <button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span> <span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button> </button>
{% endif %} {% endif %}
@@ -57,8 +57,8 @@ First written: 2023/1/31
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.edit", account=obj)|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a> </a>
</div> </div>
{% endif %} {% endif %}
@@ -22,6 +22,7 @@ First written: 2023/2/1
{% extends "accounting/base.html" %} {% extends "accounting/base.html" %}
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/base-combobox.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/account-form.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/account-form.js") }}"></script>
{% endblock %} {% endblock %}
@@ -29,7 +30,7 @@ First written: 2023/2/1
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}"> <a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -41,9 +42,9 @@ First written: 2023/2/1
{% endif %} {% endif %}
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-base-code" type="hidden" name="base_code" value="{{ form.base_code.data|accounting_default }}"> <input id="accounting-base-code" type="hidden" name="base_code" value="{{ form.base_code.data|accounting_default }}">
<div id="accounting-base-control" class="form-control accounting-clickable accounting-material-text-field {% if form.base_code.data %} accounting-not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-base-selector-modal"> <button id="accounting-base-control" class="form-control text-start accounting-material-text-field {% if form.base_code.data %} accounting-not-empty {% endif %} {% if form.base_code.errors %} is-invalid {% endif %}" type="button" data-bs-toggle="modal" data-bs-target="#accounting-base-selector-modal">
<label class="form-label" for="accounting-base">{{ A_("Base account") }}</label> <span class="form-label">{{ A_("Base account") }}</span>
<div id="accounting-base"> <span id="accounting-base">
{% if form.base_code.data %} {% if form.base_code.data %}
{% if form.base_code.errors %} {% if form.base_code.errors %}
{{ A_("(Unknown)") }} {{ A_("(Unknown)") }}
@@ -51,15 +52,15 @@ First written: 2023/2/1
{{ form.selected_base }} {{ form.selected_base }}
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </span>
</div> </button>
<div id="accounting-base-error" class="invalid-feedback">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div> <div id="accounting-base-error" class="invalid-feedback" role="alert">{% if form.base_code.errors %}{{ form.base_code.errors[0] }}{% endif %}</div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-title" class="form-control {% if form.title.errors %} is-invalid {% endif %}" type="text" name="title" value="{{ form.title.data|accounting_default }}" placeholder=" " required="required"> <input id="accounting-title" class="form-control {% if form.title.errors %} is-invalid {% endif %}" type="text" name="title" value="{{ form.title.data|accounting_default }}" placeholder=" " required="required">
<label class="form-label" for="accounting-title">{{ A_("Title") }}</label> <label class="form-label" for="accounting-title">{{ A_("Title") }}</label>
<div id="accounting-title-error" class="invalid-feedback">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div> <div id="accounting-title-error" class="invalid-feedback" role="alert">{% if form.title.errors %}{{ form.title.errors[0] }}{% endif %}</div>
</div> </div>
<div id="accounting-is-need-offset-control" class="form-check form-switch mb-3 {% if form.base_code.data[0] not in ["1", "2", "3"] %} d-none {% endif %}"> <div id="accounting-is-need-offset-control" class="form-check form-switch mb-3 {% if form.base_code.data[0] not in ["1", "2", "3"] %} d-none {% endif %}">
@@ -71,14 +72,14 @@ First written: 2023/2/1
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }} {{ A_("Save") }}
</button> </button>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </form>
@@ -92,21 +93,21 @@ First written: 2023/2/1
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="input-group mb-2"> <div class="input-group mb-2">
<input id="accounting-base-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required"> <input id="accounting-base-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-base-selector-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-base-selector-query"> <label class="input-group-text" for="accounting-base-selector-query">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }} {{ A_("Search") }}
</label> </label>
</div> </div>
<ul id="accounting-base-selector-option-list" class="list-group accounting-selector-list"> <ul id="accounting-base-selector-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-base-selector-modal-label">
{% for base in form.base_options %} {% for base in form.base_options %}
<li class="list-group-item accounting-clickable accounting-base-selector-option" data-code="{{ base.code }}" data-text="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}" data-bs-dismiss="modal"> <li id="accounting-base-selector-option-{{ base.code }}" class="list-group-item accounting-clickable accounting-base-selector-option" role="option" aria-selected="false" data-code="{{ base.code }}" data-text="{{ base }}" data-query-values="{{ base.query_values|tojson|forceescape }}" data-bs-dismiss="modal">
{{ base }} {{ base }}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<p id="accounting-base-selector-option-no-result" class="d-none">{{ A_("There is no data.") }}</p> <p id="accounting-base-selector-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ A_("Cancel") }}</button>
@@ -28,7 +28,7 @@ First written: 2023/1/30
<div class="mb-2 accounting-toolbar"> <div class="mb-2 accounting-toolbar">
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<a class="btn btn-primary text-nowrap d-none d-md-block" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}"> <a class="btn btn-primary text-nowrap d-none d-md-block" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus" aria-hidden="true"></i>
{{ A_("New") }} {{ A_("New") }}
</a> </a>
{% endif %} {% endif %}
@@ -36,7 +36,7 @@ First written: 2023/1/30
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required"> <input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text"> <label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit"> <button type="submit">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span> <span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button> </button>
</label> </label>
@@ -45,8 +45,8 @@ First written: 2023/1/30
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.create")|accounting_append_next }}" aria-label="{{ A_("New") }}">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus" aria-hidden="true"></i>
</a> </a>
</div> </div>
{% endif %} {% endif %}
@@ -32,7 +32,7 @@ First written: 2023/2/2
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -51,21 +51,21 @@ First written: 2023/2/2
<span id="accounting-order-{{ account.id }}-code">{{ account.code }}</span> <span id="accounting-order-{{ account.id }}-code">{{ account.code }}</span>
{{ account.title }} {{ account.title }}
</div> </div>
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars" aria-hidden="true"></i>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }} {{ A_("Save") }}
</button> </button>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </form>
@@ -27,7 +27,7 @@ First written: 2023/2/1
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.account.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -30,7 +30,7 @@ First written: 2023/1/26
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required"> <input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text"> <label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit"> <button type="submit">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span> <span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button> </button>
</label> </label>
@@ -27,24 +27,24 @@ First written: 2023/2/6
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.list")|accounting_or_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.list")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}"> <a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }} {{ A_("Edit") }}
</a> </a>
{% endif %} {% endif %}
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
{% if obj.can_delete %} {% if obj.can_delete %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal"> <button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span> <span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button> </button>
{% else %} {% else %}
<button class="btn btn-secondary" type="button" disabled="disabled"> <button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span> <span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button> </button>
{% endif %} {% endif %}
@@ -53,8 +53,8 @@ First written: 2023/2/6
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.edit", currency=obj)|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a> </a>
</div> </div>
{% endif %} {% endif %}
@@ -29,7 +29,7 @@ First written: 2023/2/6
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}"> <a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -42,25 +42,25 @@ First written: 2023/2/6
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-code" class="form-control {% if form.code.errors %} is-invalid {% endif %}" type="text" name="code" value="{{ form.code.data|accounting_default }}" placeholder=" " required="required" data-exists-url="{{ url_for("accounting.currency-api.exists") }}" data-original="{% block original_code %}{% endblock %}" data-blocklist="{{ form.CODE_BLOCKLIST|tojson|forceescape }}"> <input id="accounting-code" class="form-control {% if form.code.errors %} is-invalid {% endif %}" type="text" name="code" value="{{ form.code.data|accounting_default }}" placeholder=" " required="required" data-exists-url="{{ url_for("accounting.currency-api.exists") }}" data-original="{% block original_code %}{% endblock %}" data-blocklist="{{ form.CODE_BLOCKLIST|tojson|forceescape }}">
<label class="form-label" for="accounting-code">{{ A_("Code") }}</label> <label class="form-label" for="accounting-code">{{ A_("Code") }}</label>
<div id="accounting-code-error" class="invalid-feedback">{% if form.code.errors %}{{ form.code.errors[0] }}{% endif %}</div> <div id="accounting-code-error" class="invalid-feedback" role="alert">{% if form.code.errors %}{{ form.code.errors[0] }}{% endif %}</div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-name" class="form-control {% if form.name.errors %} is-invalid {% endif %}" type="text" name="name" value="{{ form.name.data|accounting_default }}" placeholder=" " required="required"> <input id="accounting-name" class="form-control {% if form.name.errors %} is-invalid {% endif %}" type="text" name="name" value="{{ form.name.data|accounting_default }}" placeholder=" " required="required">
<label class="form-label" for="accounting-name">{{ A_("Name") }}</label> <label class="form-label" for="accounting-name">{{ A_("Name") }}</label>
<div id="accounting-name-error" class="invalid-feedback">{% if form.name.errors %}{{ form.name.errors[0] }}{% endif %}</div> <div id="accounting-name-error" class="invalid-feedback" role="alert">{% if form.name.errors %}{{ form.name.errors[0] }}{% endif %}</div>
</div> </div>
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }} {{ A_("Save") }}
</button> </button>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </form>
@@ -28,7 +28,7 @@ First written: 2023/2/6
<div class="mb-2 accounting-toolbar"> <div class="mb-2 accounting-toolbar">
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<a class="btn btn-primary text-nowrap d-none d-md-block" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}"> <a class="btn btn-primary text-nowrap d-none d-md-block" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus" aria-hidden="true"></i>
{{ A_("New") }} {{ A_("New") }}
</a> </a>
{% endif %} {% endif %}
@@ -36,7 +36,7 @@ First written: 2023/2/6
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required"> <input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text"> <label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit"> <button type="submit">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span> <span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button> </button>
</label> </label>
@@ -45,8 +45,8 @@ First written: 2023/2/6
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.currency.create")|accounting_append_next }}" aria-label="{{ A_("New") }}">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus" aria-hidden="true"></i>
</a> </a>
</div> </div>
{% endif %} {% endif %}
@@ -22,39 +22,39 @@ First written: 2023/1/26
{# <ul> For SonarQube not to complain about incorrect HTML #} {# <ul> For SonarQube not to complain about incorrect HTML #}
{% if accounting_can_view() %} {% if accounting_can_view() %}
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" data-bs-toggle="dropdown"> <button class="nav-link dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-file-invoice-dollar"></i> <i class="fa-solid fa-file-invoice-dollar" aria-hidden="true"></i>
{{ A_("Accounting") }} {{ A_("Accounting") }}
</span> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li> <li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting-report.") %} active {% endif %}" href="{{ url_for("accounting-report.default") }}"> <a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting-report.") %} active {% endif %}" href="{{ url_for("accounting-report.default") }}">
<i class="fa-solid fa-book"></i> <i class="fa-solid fa-book" aria-hidden="true"></i>
{{ A_("Reports") }} {{ A_("Reports") }}
</a> </a>
</li> </li>
<li> <li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.account.") %} active {% endif %}" href="{{ url_for("accounting.account.list") }}"> <a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.account.") %} active {% endif %}" href="{{ url_for("accounting.account.list") }}">
<i class="fa-solid fa-clipboard"></i> <i class="fa-solid fa-clipboard" aria-hidden="true"></i>
{{ A_("Accounts") }} {{ A_("Accounts") }}
</a> </a>
</li> </li>
<li> <li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.base-account.") %} active {% endif %}" href="{{ url_for("accounting.base-account.list") }}"> <a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.base-account.") %} active {% endif %}" href="{{ url_for("accounting.base-account.list") }}">
<i class="fa-solid fa-list"></i> <i class="fa-solid fa-list" aria-hidden="true"></i>
{{ A_("Base Accounts") }} {{ A_("Base Accounts") }}
</a> </a>
</li> </li>
<li> <li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.currency.") %} active {% endif %}" href="{{ url_for("accounting.currency.list") }}"> <a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.currency.") %} active {% endif %}" href="{{ url_for("accounting.currency.list") }}">
<i class="fa-solid fa-money-bill-wave"></i> <i class="fa-solid fa-money-bill-wave" aria-hidden="true"></i>
{{ A_("Currencies") }} {{ A_("Currencies") }}
</a> </a>
</li> </li>
{% if accounting_can_admin() %} {% if accounting_can_admin() %}
<li> <li>
<a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.option.") %} active {% endif %}" href="{{ url_for("accounting.option.detail") }}"> <a class="dropdown-item {% if request.endpoint and request.endpoint.startswith("accounting.option.") %} active {% endif %}" href="{{ url_for("accounting.option.detail") }}">
<i class="fa-solid fa-gear"></i> <i class="fa-solid fa-gear" aria-hidden="true"></i>
{{ A_("Settings") }} {{ A_("Settings") }}
</a> </a>
</li> </li>
@@ -38,7 +38,7 @@ First written: 2023/1/26
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<li class="page-item d-none d-md-inline active dropdown"> <li class="page-item d-none d-md-inline active dropdown">
<div class="page-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <div class="page-link dropdown-toggle" role="button" tabindex="0" data-bs-toggle="dropdown" aria-expanded="false">
{{ pagination.page_size }} {{ pagination.page_size }}
</div> </div>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@@ -23,7 +23,7 @@ First written: 2023/2/26
{% block as_transfer %} {% block as_transfer %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}">
<i class="fa-solid fa-table-columns"></i> <i class="fa-solid fa-table-columns" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("As Transfer") }}</span> <span class="d-none d-md-inline">{{ A_("As Transfer") }}</span>
</a> </a>
{% endblock %} {% endblock %}
@@ -19,31 +19,31 @@ account-selector-modal.html: The modal for the account selector
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25 First written: 2023/2/25
#} #}
<div id="accounting-account-selector-{{ debit_credit }}-modal" class="modal fade accounting-account-selector" data-debit-credit="{{ debit_credit }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ debit_credit }}-modal-label" aria-hidden="true"> <div id="accounting-account-selector-{{ debit_credit }}-modal" class="modal fade accounting-account-selector" data-debit-credit="{{ debit_credit }}" tabindex="-1" aria-labelledby="accounting-account-selector-{{ debit_credit }}-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-account-selector-{{ debit_credit }}-modal-label">{{ A_("Select Account") }}</h1> <h1 class="modal-title fs-5" id="accounting-account-selector-{{ debit_credit }}-modal-label">{{ A_("Select Account") }}</h1>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button> <button id="accounting-account-selector-{{ debit_credit }}-close" type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="input-group mb-2"> <div class="input-group mb-2">
<input id="accounting-account-selector-{{ debit_credit }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required"> <input id="accounting-account-selector-{{ debit_credit }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-account-selector-{{ debit_credit }}-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-account-selector-{{ debit_credit }}-query"> <label class="input-group-text" for="accounting-account-selector-{{ debit_credit }}-query">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }} {{ A_("Search") }}
</label> </label>
</div> </div>
<ul id="accounting-account-selector-{{ debit_credit }}-option-list" class="list-group accounting-selector-list"> <ul id="accounting-account-selector-{{ debit_credit }}-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-account-selector-{{ debit_credit }}-modal-label">
{% for account in account_options %} {% for account in account_options %}
<li id="accounting-account-selector-{{ debit_credit }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ debit_credit }}-option {% if account.is_in_use %} accounting-account-is-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" data-code="{{ account.code }}" data-title="{{ account.title }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> <li id="accounting-account-selector-{{ debit_credit }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-account-selector-{{ debit_credit }}-option {% if account.is_in_use %} accounting-account-is-in-use {% endif %} {% if account.is_need_offset %} accounting-account-is-need-offset {% endif %}" role="option" aria-selected="false" data-code="{{ account.code }}" data-title="{{ account.title }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
{{ account }} {{ account }}
</li> </li>
{% endfor %} {% endfor %}
<li id="accounting-account-selector-{{ debit_credit }}-more" class="list-group-item accounting-clickable">{{ A_("More…") }}</li> <li id="accounting-account-selector-{{ debit_credit }}-more" class="list-group-item accounting-clickable" role="option" aria-selected="false">{{ A_("More…") }}</li>
</ul> </ul>
<p id="accounting-account-selector-{{ debit_credit }}-option-no-result" class="d-none">{{ A_("There is no data.") }}</p> <p id="accounting-account-selector-{{ debit_credit }}-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">{{ A_("Cancel") }}</button> <button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">{{ A_("Cancel") }}</button>
@@ -21,14 +21,14 @@ First written: 2023/2/28
#} #}
<form id="accounting-description-editor-{{ description_editor.debit_credit }}" class="accounting-description-editor" data-debit-credit="{{ description_editor.debit_credit }}"> <form id="accounting-description-editor-{{ description_editor.debit_credit }}" class="accounting-description-editor" data-debit-credit="{{ description_editor.debit_credit }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label" aria-hidden="true"> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label"> <h1 class="modal-title fs-5" id="accounting-description-editor-{{ description_editor.debit_credit }}-modal-label">
<label for="accounting-description-editor-{{ description_editor.debit_credit }}-description">{{ A_("Description") }}</label> <label for="accounting-description-editor-{{ description_editor.debit_credit }}-description">{{ A_("Description") }}</label>
</h1> </h1>
<button class="btn-close" type="button" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button> <button id="accounting-description-editor-{{ description_editor.debit_credit }}-close" class="btn-close" type="button" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="d-flex justify-content-between mb-3"> <div class="d-flex justify-content-between mb-3">
@@ -39,40 +39,40 @@ First written: 2023/2/28
</div> </div>
{# Tab navigation #} {# Tab navigation #}
<ul class="nav nav-tabs mb-2"> <ul id="accounting-description-editor-{{ description_editor.debit_credit }}-tab-list" class="nav nav-tabs mb-2" role="tablist" aria-label="{{ A_("Description Type") }}">
<li class="nav-item"> <li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab" class="nav-link active accounting-clickable" aria-current="page"> <button id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab" class="nav-link active accounting-clickable" type="button" tabindex="0" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-general-panel" aria-selected="true">
{{ A_("General") }} {{ A_("General") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab" class="nav-link accounting-clickable" aria-current="false"> <button id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-travel-panel" aria-selected="false">
{{ A_("Travel") }} {{ A_("Travel") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab" class="nav-link accounting-clickable" aria-current="false"> <button id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-bus-panel" aria-selected="false">
{{ A_("Bus") }} {{ A_("Bus") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab" class="nav-link accounting-clickable" aria-current="false"> <button id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-panel" aria-selected="false">
{{ A_("Recurring") }} {{ A_("Recurring") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab" class="nav-link accounting-clickable" aria-current="false"> <button id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab" class="nav-link accounting-clickable" type="button" tabindex="-1" role="tab" aria-controls="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-panel" aria-selected="false">
{{ A_("Annotation") }} {{ A_("Annotation") }}
</span> </button>
</li> </li>
</ul> </ul>
{# A general description with a tag #} {# A general description with a tag #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-page" aria-current="page" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab"> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-panel" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-general-tab">
<div class="form-floating mb-2"> <div class="form-floating mb-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag">{{ A_("Tag") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag">{{ A_("Tag") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-general-tag-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="accounting-description-editor-buttons"> <div class="accounting-description-editor-buttons">
@@ -85,11 +85,11 @@ First written: 2023/2/28
</div> </div>
{# A general trip with the origin and distination #} {# A general trip with the origin and distination #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab"> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tab">
<div class="form-floating mb-2"> <div class="form-floating mb-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag">{{ A_("Tag") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag">{{ A_("Tag") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-tag-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="accounting-description-editor-buttons"> <div class="accounting-description-editor-buttons">
@@ -104,7 +104,7 @@ First written: 2023/2/28
<div class="form-floating"> <div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from">{{ A_("From") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from">{{ A_("From") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-from-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="btn-group-vertical ms-1 me-1"> <div class="btn-group-vertical ms-1 me-1">
<button class="btn btn-primary accounting-description-editor-{{ description_editor.debit_credit }}-travel-direction accounting-default" type="button" tabindex="-1" data-arrow="&rarr;">&rarr;</button> <button class="btn btn-primary accounting-description-editor-{{ description_editor.debit_credit }}-travel-direction accounting-default" type="button" tabindex="-1" data-arrow="&rarr;">&rarr;</button>
@@ -113,23 +113,23 @@ First written: 2023/2/28
<div class="form-floating"> <div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to">{{ A_("To") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to">{{ A_("To") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-travel-to-error" class="invalid-feedback" role="alert"></div>
</div> </div>
</div> </div>
</div> </div>
{# A bus trip with the route name or route number, the origin and distination #} {# A bus trip with the route name or route number, the origin and distination #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab"> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tab">
<div class="d-flex justify-content-between mb-2"> <div class="d-flex justify-content-between mb-2">
<div class="form-floating me-2"> <div class="form-floating me-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag">{{ A_("Tag") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag">{{ A_("Tag") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-tag-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="form-floating"> <div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route">{{ A_("Route") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route">{{ A_("Route") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-route-error" class="invalid-feedback" role="alert"></div>
</div> </div>
</div> </div>
@@ -145,18 +145,18 @@ First written: 2023/2/28
<div class="form-floating me-2"> <div class="form-floating me-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from">{{ A_("From") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from">{{ A_("From") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-from-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="form-floating"> <div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to">{{ A_("To") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to">{{ A_("To") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-bus-to-error" class="invalid-feedback" role="alert"></div>
</div> </div>
</div> </div>
</div> </div>
{# A recurring transaction #} {# A recurring transaction #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab"> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-recurring-tab">
<div class="accounting-description-editor-buttons"> <div class="accounting-description-editor-buttons">
{% for recurring in description_editor.recurring %} {% for recurring in description_editor.recurring %}
<button class="btn btn-outline-primary accounting-description-editor-{{ description_editor.debit_credit }}-recurring-item" type="button" tabindex="-1" data-template="{{ recurring.description_template }}" data-accounts="{{ recurring.account_codes|tojson|forceescape }}"> <button class="btn btn-outline-primary accounting-description-editor-{{ description_editor.debit_credit }}-recurring-item" type="button" tabindex="-1" data-template="{{ recurring.description_template }}" data-accounts="{{ recurring.account_codes|tojson|forceescape }}">
@@ -167,17 +167,17 @@ First written: 2023/2/28
</div> </div>
{# The annotation #} {# The annotation #}
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-page" class="d-none" aria-current="false" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab"> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-panel" class="d-none" role="tabpanel" aria-labelledby="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-tab">
<div class="form-floating"> <div class="form-floating">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number" class="form-control" type="number" min="1" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number" class="form-control" type="number" min="1" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number">{{ A_("The Number of Items") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number">{{ A_("The Number of Items") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-number-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="form-floating mt-2"> <div class="form-floating mt-2">
<input id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note" class="form-control" type="text" value="" placeholder=" "> <input id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note" class="form-control" type="text" value="" placeholder=" ">
<label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note">{{ A_("Note") }}</label> <label class="form-label" for="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note">{{ A_("Note") }}</label>
<div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note-error" class="invalid-feedback"></div> <div id="accounting-description-editor-{{ description_editor.debit_credit }}-annotation-note-error" class="invalid-feedback" role="alert"></div>
</div> </div>
</div> </div>
@@ -27,29 +27,29 @@ First written: 2023/2/26
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting-report.default")|accounting_or_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting-report.default")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}"> <a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }} {{ A_("Edit") }}
</a> </a>
{% endif %} {% endif %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.order", date=obj.date)|accounting_append_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.order", date=obj.date)|accounting_append_next }}">
<i class="fa-solid fa-bars-staggered"></i> <i class="fa-solid fa-bars-staggered" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Order") }}</span> <span class="d-none d-md-inline">{{ A_("Order") }}</span>
</a> </a>
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
{% block as_transfer %}{% endblock %} {% block as_transfer %}{% endblock %}
{% if obj.can_delete %} {% if obj.can_delete %}
<button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal"> <button class="btn btn-danger" type="button" data-bs-toggle="modal" data-bs-target="#accounting-delete-modal">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span> <span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button> </button>
{% else %} {% else %}
<button class="btn btn-secondary" type="button" disabled="disabled"> <button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-trash"></i> <i class="fa-solid fa-trash" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Delete") }}</span> <span class="d-none d-md-inline">{{ A_("Delete") }}</span>
</button> </button>
{% endif %} {% endif %}
@@ -58,8 +58,8 @@ First written: 2023/2/26
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a> </a>
</div> </div>
{% endif %} {% endif %}
@@ -104,7 +104,7 @@ First written: 2023/2/26
{% if obj.note %} {% if obj.note %}
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body"> <div class="card-body">
<i class="far fa-comment-dots"></i> <i class="far fa-comment-dots" aria-hidden="true"></i>
{{ obj.note|accounting_journal_entry_text2html|safe }} {{ obj.note|accounting_journal_entry_text2html|safe }}
</div> </div>
</div> </div>
@@ -31,17 +31,17 @@ First written: 2023/3/21
{% endfor %} {% endfor %}
</select> </select>
<label class="form-label" for="accounting-currency-{{ currency_index }}-code-select">{{ A_("Currency") }}</label> <label class="form-label" for="accounting-currency-{{ currency_index }}-code-select">{{ A_("Currency") }}</label>
<div id="accounting-currency-{{ currency_index }}-code-error" class="invalid-feedback">{% if currency_code_errors %}{{ currency_code_errors[0] }}{% endif %}</div> <div id="accounting-currency-{{ currency_index }}-code-error" class="invalid-feedback" role="alert">{% if currency_code_errors %}{{ currency_code_errors[0] }}{% endif %}</div>
</div> </div>
<div> <div>
<button id="accounting-currency-{{ currency_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_currency_form %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}"> <button id="accounting-currency-{{ currency_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_currency_form %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus"></i> <i class="fas fa-minus" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
{% block line_items %}{% endblock %} {% block line_items %}{% endblock %}
</div> </div>
<div id="accounting-currency-{{ currency_index }}-error" class="invalid-feedback">{% if currency_errors %}{{ currency_errors[0] }}{% endif %}</div> <div id="accounting-currency-{{ currency_index }}-error" class="invalid-feedback" role="alert">{% if currency_errors %}{{ currency_errors[0] }}{% endif %}</div>
</div> </div>
@@ -20,8 +20,8 @@ Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/21 First written: 2023/3/21
#} #}
<div class="mb-2"> <div class="mb-2">
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}" class="form-control accounting-material-text-field {% if line_item_forms %} accounting-not-empty {% else %} accounting-clickable {% endif %} {% if debit_errors %} is-invalid {% endif %}"> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}" class="form-control accounting-material-text-field {% if line_item_forms %} accounting-not-empty {% else %} accounting-clickable {% endif %} {% if debit_errors %} is-invalid {% endif %}" {% if line_item_forms %} tabindex="-1" {% else %} role="button" tabindex="0" {% endif %}>
<label class="form-label" for="accounting-currency-{{ currency_index }}-{{ debit_credit }}">{{ header }}</label> <span class="form-label">{{ header }}</span>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-content" class="mt-2 {% if not line_item_forms %} d-none {% endif %}"> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-content" class="mt-2 {% if not line_item_forms %} d-none {% endif %}">
<ul id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-list" class="list-group accounting-line-item-list"> <ul id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-list" class="list-group accounting-line-item-list">
{% for line_item_form in line_item_forms %} {% for line_item_form in line_item_forms %}
@@ -36,16 +36,16 @@ First written: 2023/3/21
<div class="d-flex justify-content-between mt-2 mb-2"> <div class="d-flex justify-content-between mt-2 mb-2">
<div>{{ A_("Total") }}</div> <div>{{ A_("Total") }}</div>
<div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-total" class="badge rounded-pill bg-primary">{{ debit_credit_total }}</span></div> <div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-total" class="badge rounded-pill bg-primary" aria-live="polite">{{ debit_credit_total }}</span></div>
</div> </div>
<div> <div>
<button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-add-line-item" class="btn btn-primary" type="button" data-currency-index="{{ currency_index }}" data-debit-credit="{{ debit_credit }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> <button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-add-line-item" class="btn btn-primary" type="button" data-currency-index="{{ currency_index }}" data-debit-credit="{{ debit_credit }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus" aria-hidden="true"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-error" class="invalid-feedback">{% if debit_credit_errors %}{{ debit_credit_errors[0] }}{% endif %}</div> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-error" class="invalid-feedback" role="alert">{% if debit_credit_errors %}{{ debit_credit_errors[0] }}{% endif %}</div>
</div> </div>
@@ -30,7 +30,7 @@ First written: 2023/2/25
<input id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-description" type="hidden" name="currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-description" value="{{ form.description.data|accounting_default }}"> <input id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-description" type="hidden" name="currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-description" value="{{ form.description.data|accounting_default }}">
<input id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount" type="hidden" name="currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount" value="{{ form.amount.data|accounting_journal_entry_format_amount_input }}" data-min="{{ form.offset_total|accounting_default("0") }}"> <input id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount" type="hidden" name="currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount" value="{{ form.amount.data|accounting_journal_entry_format_amount_input }}" data-min="{{ form.offset_total|accounting_default("0") }}">
<div class="accounting-line-item-content"> <div class="accounting-line-item-content">
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-control" class="form-control clickable d-flex justify-content-between {% if form.all_errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-control" class="form-control clickable d-flex justify-content-between {% if form.all_errors %} is-invalid {% endif %}" role="button" tabindex="0" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<div> <div>
<div class="small"> <div class="small">
<span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-account-text-code" class="d-none d-md-inline">{{ form.account_code.data|accounting_default }}</span> <span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-account-text-code" class="d-none d-md-inline">{{ form.account_code.data|accounting_default }}</span>
@@ -65,12 +65,12 @@ First written: 2023/2/25
</div> </div>
<div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount-text" class="badge rounded-pill bg-primary">{{ form.amount.data|accounting_format_amount }}</span></div> <div><span id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-amount-text" class="badge rounded-pill bg-primary">{{ form.amount.data|accounting_format_amount }}</span></div>
</div> </div>
<div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-error" class="invalid-feedback">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div> <div id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-error" class="invalid-feedback" role="alert">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div>
</div> </div>
<div> <div>
<button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_form or form.offsets %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}"> <button id="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}-delete" class="btn btn-danger rounded-circle {% if only_one_form or form.offsets %} d-none {% endif %}" type="button" data-target="accounting-currency-{{ currency_index }}-{{ debit_credit }}-{{ line_item_index }}" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus"></i> <i class="fas fa-minus" aria-hidden="true"></i>
</button> </button>
</div> </div>
</li> </li>
@@ -23,10 +23,12 @@ First written: 2023/2/26
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-combobox.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/journal-entry-form.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/journal-entry-form.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/journal-entry-line-item-editor.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/journal-entry-line-item-editor.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/journal-entry-account-selector.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/journal-entry-account-selector.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/original-line-item-selector.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/original-line-item-selector.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/description-editor.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/description-editor.js") }}"></script>
{% endblock %} {% endblock %}
@@ -34,7 +36,7 @@ First written: 2023/2/26
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}"> <a class="btn btn-primary" role="button" href="{% block back_url %}{% endblock %}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -48,7 +50,7 @@ First written: 2023/2/26
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-date" class="form-control {% if form.date.errors %} is-invalid {% endif %}" type="date" name="date" value="{{ form.date.data|accounting_default }}" max="{{ form.max_date|accounting_default }}" min="{{ form.min_date|accounting_default }}" placeholder=" " required="required"> <input id="accounting-date" class="form-control {% if form.date.errors %} is-invalid {% endif %}" type="date" name="date" value="{{ form.date.data|accounting_default }}" max="{{ form.max_date|accounting_default }}" min="{{ form.min_date|accounting_default }}" placeholder=" " required="required">
<label class="form-label" for="accounting-date">{{ A_("Date") }}</label> <label class="form-label" for="accounting-date">{{ A_("Date") }}</label>
<div id="accounting-date-error" class="invalid-feedback">{% if form.date.errors %}{{ form.date.errors[0] }}{% endif %}</div> <div id="accounting-date-error" class="invalid-feedback" role="alert">{% if form.date.errors %}{{ form.date.errors[0] }}{% endif %}</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
@@ -60,30 +62,30 @@ First written: 2023/2/26
<div> <div>
<button id="accounting-add-currency" class="btn btn-primary" type="button"> <button id="accounting-add-currency" class="btn btn-primary" type="button">
<i class="fas fa-plus"></i> <i class="fas fa-plus" aria-hidden="true"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>
</div> </div>
</div> </div>
<div id="accounting-currencies-error" class="invalid-feedback">{% if form.currencies_errors %}{{ form.currencies_errors[0] }}{% endif %}</div> <div id="accounting-currencies-error" class="invalid-feedback" role="alert">{% if form.currencies_errors %}{{ form.currencies_errors[0] }}{% endif %}</div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<textarea id="accounting-note" class="form-control form-control-lg {% if form.note.errors %} is-invalid {% endif %}" name="note" rows="5" placeholder=" ">{{ form.note.data|accounting_default }}</textarea> <textarea id="accounting-note" class="form-control form-control-lg {% if form.note.errors %} is-invalid {% endif %}" name="note" rows="5" placeholder=" ">{{ form.note.data|accounting_default }}</textarea>
<label class="form-label" for="accounting-note">{{ A_("Note") }}</label> <label class="form-label" for="accounting-note">{{ A_("Note") }}</label>
<div id="accounting-note-error" class="invalid-feedback">{% if form.note.errors %}{{ form.note.errors[0] }}{% endif %}</div> <div id="accounting-note-error" class="invalid-feedback" role="alert">{% if form.note.errors %}{{ form.note.errors[0] }}{% endif %}</div>
</div> </div>
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }} {{ A_("Save") }}
</button> </button>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </form>
@@ -31,40 +31,40 @@ First written: 2023/2/25
<div class="modal-body"> <div class="modal-body">
<div id="accounting-line-item-editor-original-line-item-container" class="d-flex justify-content-between mb-3"> <div id="accounting-line-item-editor-original-line-item-container" class="d-flex justify-content-between mb-3">
<div class="accounting-line-item-editor-original-line-item-content"> <div class="accounting-line-item-editor-original-line-item-content">
<div id="accounting-line-item-editor-original-line-item-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target="#accounting-original-line-item-selector-modal"> <button id="accounting-line-item-editor-original-line-item-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="#accounting-original-line-item-selector-modal">
<label class="form-label" for="accounting-line-item-editor-original-line-item">{{ A_("Original Line Item") }}</label> <span class="form-label">{{ A_("Original Line Item") }}</span>
<div id="accounting-line-item-editor-original-line-item"></div> <span id="accounting-line-item-editor-original-line-item"></span>
</div> </button>
<div id="accounting-line-item-editor-original-line-item-error" class="invalid-feedback"></div> <div id="accounting-line-item-editor-original-line-item-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div> <div>
<button id="accounting-line-item-editor-original-line-item-delete" class="btn btn-danger rounded-circle" type="button"> <button id="accounting-line-item-editor-original-line-item-delete" class="btn btn-danger rounded-circle" type="button" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus"></i> <i class="fas fa-minus" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<div id="accounting-line-item-editor-description-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target=""> <button id="accounting-line-item-editor-description-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="">
<label class="form-label" for="accounting-line-item-editor-description">{{ A_("Description") }}</label> <span class="form-label">{{ A_("Description") }}</span>
<div id="accounting-line-item-editor-description"></div> <span id="accounting-line-item-editor-description"></span>
</div> </button>
<div id="accounting-line-item-editor-description-error" class="invalid-feedback"></div> <div id="accounting-line-item-editor-description-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<div id="accounting-line-item-editor-account-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target=""> <button id="accounting-line-item-editor-account-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="">
<label class="form-label" for="accounting-line-item-editor-account">{{ A_("Account") }}</label> <span class="form-label">{{ A_("Account") }}</span>
<div id="accounting-line-item-editor-account"></div> <span id="accounting-line-item-editor-account"></span>
</div> </button>
<div id="accounting-line-item-editor-account-error" class="invalid-feedback"></div> <div id="accounting-line-item-editor-account-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-line-item-editor-amount" class="form-control" type="number" value="" min="0" max="" step="0.01" placeholder=" " required="required"> <input id="accounting-line-item-editor-amount" class="form-control" type="number" value="" min="0" max="" step="0.01" placeholder=" " required="required">
<label for="accounting-line-item-editor-amount">{{ A_("Amount") }}</label> <label for="accounting-line-item-editor-amount">{{ A_("Amount") }}</label>
<div id="accounting-line-item-editor-amount-error" class="invalid-feedback"></div> <div id="accounting-line-item-editor-amount-error" class="invalid-feedback" role="alert"></div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
@@ -19,25 +19,25 @@ original-line-item-selector-modal.html: The modal of the original line item sele
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/2/25 First written: 2023/2/25
#} #}
<div id="accounting-original-line-item-selector-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-original-line-item-selector-modal-label" aria-hidden="true"> <div id="accounting-original-line-item-selector-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-original-line-item-selector-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-original-line-item-selector-modal-label">{{ A_("Select Original Line Item") }}</h1> <h1 class="modal-title fs-5" id="accounting-original-line-item-selector-modal-label">{{ A_("Select Original Line Item") }}</h1>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button> <button id="accounting-original-line-item-selector-close" type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal" aria-label="{{ A_("Close") }}"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="input-group mb-2"> <div class="input-group mb-2">
<input id="accounting-original-line-item-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required"> <input id="accounting-original-line-item-selector-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-original-line-item-selector-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-original-line-item-selector-query"> <label class="input-group-text" for="accounting-original-line-item-selector-query">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }} {{ A_("Search") }}
</label> </label>
</div> </div>
<ul id="accounting-original-line-item-selector-option-list" class="list-group accounting-selector-list"> <ul id="accounting-original-line-item-selector-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-original-line-item-selector-modal-label">
{% for line_item in form.original_line_item_options %} {% for line_item in form.original_line_item_options %}
<li id="accounting-original-line-item-selector-option-{{ line_item.id }}" class="list-group-item d-flex justify-content-between accounting-clickable accounting-original-line-item-selector-option" data-id="{{ line_item.id }}" data-date="{{ line_item.journal_entry.date }}" data-debit-credit="{{ "debit" if line_item.is_debit else "credit" }}" data-currency-code="{{ line_item.currency.code }}" data-account-code="{{ line_item.account_code }}" data-account-title="{{ line_item.account.title }}" data-account-text="{{ line_item.account }}" data-description="{{ line_item.description|accounting_default }}" data-net-balance="{{ line_item.net_balance|accounting_journal_entry_format_amount_input }}" data-text="{{ line_item }}" data-query-values="{{ line_item.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal"> <li id="accounting-original-line-item-selector-option-{{ line_item.id }}" class="list-group-item d-flex justify-content-between accounting-clickable accounting-original-line-item-selector-option" data-id="{{ line_item.id }}" data-date="{{ line_item.journal_entry.date }}" data-debit-credit="{{ "debit" if line_item.is_debit else "credit" }}" data-currency-code="{{ line_item.currency.code }}" data-account-code="{{ line_item.account_code }}" data-account-title="{{ line_item.account.title }}" data-account-text="{{ line_item.account }}" data-description="{{ line_item.description|accounting_default }}" data-net-balance="{{ line_item.net_balance|accounting_journal_entry_format_amount_input }}" data-text="{{ line_item }}" data-query-values="{{ line_item.query_values|tojson|forceescape }}" role="option" aria-selected="false" data-bs-toggle="modal" data-bs-target="#accounting-line-item-editor-modal">
<div> <div>
<div class="small"> <div class="small">
{{ line_item.journal_entry.date|accounting_format_date }} {{ line_item.journal_entry.date|accounting_format_date }}
@@ -55,7 +55,7 @@ First written: 2023/2/25
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<p id="accounting-original-line-item-selector-option-no-result" class="d-none">{{ A_("There is no data.") }}</p> <p id="accounting-original-line-item-selector-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -32,7 +32,7 @@ First written: 2023/2/26
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting-report.default")|accounting_or_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting-report.default")|accounting_or_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -50,21 +50,21 @@ First written: 2023/2/26
{% with journal_entry = item %} {% with journal_entry = item %}
{% include "accounting/journal-entry/include/order-journal-entry.html" %} {% include "accounting/journal-entry/include/order-journal-entry.html" %}
{% endwith %} {% endwith %}
<i class="fa-solid fa-bars"></i> <i class="fa-solid fa-bars" aria-hidden="true"></i>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }} {{ A_("Save") }}
</button> </button>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </form>
@@ -23,7 +23,7 @@ First written: 2023/2/26
{% block as_transfer %} {% block as_transfer %}
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.journal-entry.edit", journal_entry=obj)|accounting_journal_entry_to_transfer|accounting_inherit_next }}">
<i class="fa-solid fa-table-columns"></i> <i class="fa-solid fa-table-columns" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("As Transfer") }}</span> <span class="d-none d-md-inline">{{ A_("As Transfer") }}</span>
</a> </a>
{% endblock %} {% endblock %}
@@ -27,14 +27,14 @@ First written: 2023/3/22
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}"> <a class="btn btn-primary d-none d-md-inline" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
{{ A_("Edit") }} {{ A_("Edit") }}
</a> </a>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.edit")|accounting_inherit_next }}" aria-label="{{ A_("Edit") }}">
<i class="fa-solid fa-pen-to-square"></i> <i class="fa-solid fa-pen-to-square" aria-hidden="true"></i>
</a> </a>
</div> </div>
@@ -23,6 +23,7 @@ First written: 2023/3/22
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/drag-and-drop-reorder.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-combobox.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/option-form.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/option-form.js") }}"></script>
{% endblock %} {% endblock %}
@@ -32,7 +33,7 @@ First written: 2023/3/22
<div class="mb-3 accounting-toolbar"> <div class="mb-3 accounting-toolbar">
<a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.detail")|accounting_inherit_next }}"> <a class="btn btn-primary" role="button" href="{{ url_for("accounting.option.detail")|accounting_inherit_next }}">
<i class="fa-solid fa-circle-chevron-left"></i> <i class="fa-solid fa-circle-chevron-left" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Back") }}</span> <span class="d-none d-md-inline">{{ A_("Back") }}</span>
</a> </a>
</div> </div>
@@ -49,7 +50,7 @@ First written: 2023/3/22
{% endfor %} {% endfor %}
</select> </select>
<label class="form-label" for="accounting-default-currency">{{ A_("Default Currency") }}</label> <label class="form-label" for="accounting-default-currency">{{ A_("Default Currency") }}</label>
<div id="accounting-default-currency-error" class="invalid-feedback">{% if form.default_currency_code.errors %}{{ form.default_currency_code.errors[0] }}{% endif %}</div> <div id="accounting-default-currency-error" class="invalid-feedback" role="alert">{% if form.default_currency_code.errors %}{{ form.default_currency_code.errors[0] }}{% endif %}</div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
@@ -59,7 +60,7 @@ First written: 2023/3/22
{% endfor %} {% endfor %}
</select> </select>
<label class="form-label" for="accounting-default-ie-account">{{ A_("Default Account for the Income and Expenses Log") }}</label> <label class="form-label" for="accounting-default-ie-account">{{ A_("Default Account for the Income and Expenses Log") }}</label>
<div id="accounting-default-ie-account-error" class="invalid-feedback">{% if form.default_ie_account_code.errors %}{{ form.default_ie_account_code.errors[0] }}{% endif %}</div> <div id="accounting-default-ie-account-error" class="invalid-feedback" role="alert">{% if form.default_ie_account_code.errors %}{{ form.default_ie_account_code.errors[0] }}{% endif %}</div>
</div> </div>
{% with expense_income = "expense", {% with expense_income = "expense",
@@ -76,14 +77,14 @@ First written: 2023/3/22
<div class="d-none d-md-block"> <div class="d-none d-md-block">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
{{ A_("Save") }} {{ A_("Save") }}
</button> </button>
</div> </div>
<div class="d-md-none accounting-material-fab"> <div class="d-md-none accounting-material-fab">
<button class="btn btn-primary" type="submit"> <button class="btn btn-primary" type="submit" aria-label="{{ A_("Save") }}">
<i class="fa-solid fa-floppy-disk"></i> <i class="fa-solid fa-floppy-disk" aria-hidden="true"></i>
</button> </button>
</div> </div>
</form> </form>
@@ -19,8 +19,8 @@ form-recurring-expense-income.html: The recurring expense or income sub-form in
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/22 First written: 2023/3/22
#} #}
<div id="accounting-recurring-{{ expense_income }}" class="form-control mb-3 accounting-material-text-field {% if recurring_items %} accounting-not-empty {% else %} accounting-clickable {% endif %}"> <div id="accounting-recurring-{{ expense_income }}" class="form-control mb-3 accounting-material-text-field {% if recurring_items %} accounting-not-empty {% else %} accounting-clickable {% endif %}" {% if recurring_items %} tabindex="-1" {% else %} role="button" tabindex="0" {% endif %}>
<label class="form-label" for="accounting-recurring-{{ expense_income }}">{{ label }}</label> <span class="form-label">{{ label }}</span>
<div id="accounting-recurring-{{ expense_income }}-content" class="{% if not recurring_items %} d-none {% endif %}"> <div id="accounting-recurring-{{ expense_income }}-content" class="{% if not recurring_items %} d-none {% endif %}">
<ul id="accounting-recurring-{{ expense_income }}-list" class="list-group mb-2 mt-2"> <ul id="accounting-recurring-{{ expense_income }}-list" class="list-group mb-2 mt-2">
{% for recurring_item in recurring_items %} {% for recurring_item in recurring_items %}
@@ -33,7 +33,7 @@ First written: 2023/3/22
<div> <div>
<button id="accounting-recurring-{{ expense_income }}-add" class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal"> <button id="accounting-recurring-{{ expense_income }}-add" class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
<i class="fas fa-plus"></i> <i class="fas fa-plus" aria-hidden="true"></i>
{{ A_("New") }} {{ A_("New") }}
</button> </button>
</div> </div>
@@ -27,17 +27,17 @@ First written: 2023/3/22
<input id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template" type="hidden" name="recurring-{{ expense_income }}-{{ item_index }}-description_template" value="{{ form.description_template.data|accounting_default }}"> <input id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template" type="hidden" name="recurring-{{ expense_income }}-{{ item_index }}-description_template" value="{{ form.description_template.data|accounting_default }}">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="w-100"> <div class="w-100">
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-control" class="form-control accounting-clickable {% if form.all_errors %} is-invalid {% endif %}" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal"> <button id="accounting-recurring-{{ expense_income }}-{{ item_index }}-control" class="form-control text-start {% if form.all_errors %} is-invalid {% endif %}" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-account-text" class="small">{{ form.account_text|accounting_default }}</div> <span id="accounting-recurring-{{ expense_income }}-{{ item_index }}-account-text" class="d-block small">{{ form.account_text|accounting_default }}</span>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-name-text">{{ form.name.data|accounting_default }}</div> <span id="accounting-recurring-{{ expense_income }}-{{ item_index }}-name-text" class="d-block">{{ form.name.data|accounting_default }}</span>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template-text" class="small">{{ form.description_template.data|accounting_default }}</div> <span id="accounting-recurring-{{ expense_income }}-{{ item_index }}-description-template-text" class="d-block small">{{ form.description_template.data|accounting_default }}</span>
</div> </button>
<div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-error" class="invalid-feedback">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div> <div id="accounting-recurring-{{ expense_income }}-{{ item_index }}-error" class="invalid-feedback" role="alert">{% if form.all_errors %}{{ form.all_errors[0] }}{% endif %}</div>
</div> </div>
<div class="ms-2"> <div class="ms-2">
<button id="accounting-recurring-{{ expense_income }}-{{ item_index }}-delete" class="btn btn-danger rounded-circle" type="button"> <button id="accounting-recurring-{{ expense_income }}-{{ item_index }}-delete" class="btn btn-danger rounded-circle" type="button" aria-label="{{ A_("Remove") }}">
<i class="fas fa-minus"></i> <i class="fas fa-minus" aria-hidden="true"></i>
</button> </button>
</div> </div>
</div> </div>
@@ -19,30 +19,30 @@ recurring-account-selector-modal.html: The modal of the account selector for the
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/22 First written: 2023/3/22
#} #}
<div id="accounting-recurring-accounting-selector-{{ expense_income }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label" aria-hidden="true"> <div id="accounting-recurring-accounting-selector-{{ expense_income }}-modal" class="modal fade" tabindex="-1" aria-labelledby="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label" aria-hidden="true" data-bs-keyboard="false">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label">{{ A_("Select Account") }}</h1> <h1 class="modal-title fs-5" id="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label">{{ A_("Select Account") }}</h1>
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal" aria-label="{{ A_("Close") }}"></button> <button id="accounting-recurring-accounting-selector-{{ expense_income }}-close" type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal" aria-label="{{ A_("Close") }}"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="input-group mb-2"> <div class="input-group mb-2">
<input id="accounting-recurring-accounting-selector-{{ expense_income }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required"> <input id="accounting-recurring-accounting-selector-{{ expense_income }}-query" class="form-control form-control-sm" type="search" placeholder=" " required="required" role="combobox" aria-expanded="true" aria-controls="accounting-recurring-accounting-selector-{{ expense_income }}-option-list" aria-autocomplete="list" aria-activedescendant="">
<label class="input-group-text" for="accounting-recurring-accounting-selector-{{ expense_income }}-query"> <label class="input-group-text" for="accounting-recurring-accounting-selector-{{ expense_income }}-query">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }} {{ A_("Search") }}
</label> </label>
</div> </div>
<ul id="accounting-recurring-accounting-selector-{{ expense_income }}-option-list" class="list-group accounting-selector-list"> <ul id="accounting-recurring-accounting-selector-{{ expense_income }}-option-list" class="list-group accounting-selector-list" role="listbox" tabindex="-1" aria-labelledby="accounting-recurring-accounting-selector-{{ expense_income }}-modal-label">
{% for account in accounts %} {% for account in accounts %}
<li id="accounting-recurring-accounting-selector-{{ expense_income }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-recurring-accounting-selector-{{ expense_income }}-option" data-code="{{ account.code }}" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal"> <li id="accounting-recurring-accounting-selector-{{ expense_income }}-option-{{ account.code }}" class="list-group-item accounting-clickable accounting-recurring-accounting-selector-{{ expense_income }}-option" data-code="{{ account.code }}" role="option" aria-selected="false" data-text="{{ account }}" data-query-values="{{ account.query_values|tojson|forceescape }}" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">
{{ account }} {{ account }}
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
<p id="accounting-recurring-accounting-selector-{{ expense_income }}-option-no-result" class="d-none">{{ A_("There is no data.") }}</p> <p id="accounting-recurring-accounting-selector-{{ expense_income }}-option-no-result" class="d-none" role="status">{{ A_("There is no data.") }}</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">{{ A_("Cancel") }}</button> <button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#accounting-recurring-item-editor-{{ expense_income }}-modal">{{ A_("Cancel") }}</button>
@@ -32,21 +32,21 @@ First written: 2023/3/22
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-recurring-item-editor-{{ expense_income }}-name" class="form-control" type="text" value="" placeholder=" " required="required"> <input id="accounting-recurring-item-editor-{{ expense_income }}-name" class="form-control" type="text" value="" placeholder=" " required="required">
<label for="accounting-recurring-item-editor-{{ expense_income }}-name">{{ A_("Name") }}</label> <label for="accounting-recurring-item-editor-{{ expense_income }}-name">{{ A_("Name") }}</label>
<div id="accounting-recurring-item-editor-{{ expense_income }}-name-error" class="invalid-feedback"></div> <div id="accounting-recurring-item-editor-{{ expense_income }}-name-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<div id="accounting-recurring-item-editor-{{ expense_income }}-account-control" class="form-control accounting-clickable accounting-material-text-field" data-bs-toggle="modal" data-bs-target="#accounting-recurring-accounting-selector-{{ expense_income }}-modal"> <button id="accounting-recurring-item-editor-{{ expense_income }}-account-control" class="form-control text-start accounting-material-text-field" type="button" data-bs-toggle="modal" data-bs-target="#accounting-recurring-accounting-selector-{{ expense_income }}-modal">
<label class="form-label" for="accounting-recurring-item-editor-{{ expense_income }}-account">{{ A_("Account") }}</label> <span class="form-label">{{ A_("Account") }}</span>
<div id="accounting-recurring-item-editor-{{ expense_income }}-account"></div> <span id="accounting-recurring-item-editor-{{ expense_income }}-account"></span>
</div> </button>
<div id="accounting-recurring-item-editor-{{ expense_income }}-account-error" class="invalid-feedback"></div> <div id="accounting-recurring-item-editor-{{ expense_income }}-account-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-recurring-item-editor-{{ expense_income }}-description-template" class="form-control" type="text" value="" placeholder=" " required="required"> <input id="accounting-recurring-item-editor-{{ expense_income }}-description-template" class="form-control" type="text" value="" placeholder=" " required="required">
<label for="accounting-recurring-item-editor-{{ expense_income }}-description-template">{{ A_("Description Template") }}</label> <label for="accounting-recurring-item-editor-{{ expense_income }}-description-template">{{ A_("Description Template") }}</label>
<div id="accounting-recurring-item-editor-{{ expense_income }}-description-template-error" class="invalid-feedback"></div> <div id="accounting-recurring-item-editor-{{ expense_income }}-description-template-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="mb-3 border-top accounting-recurring-description-template-illustration"> <div class="mb-3 border-top accounting-recurring-description-template-illustration">
@@ -23,6 +23,7 @@ First written: 2023/3/7
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %} {% endblock %}
@@ -55,15 +56,15 @@ First written: 2023/3/7
</h2> </h2>
</div> </div>
<div class="row accounting-report-table accounting-balance-sheet-table"> <div class="row accounting-report-table accounting-balance-sheet-table" role="table">
<div class="col-sm-6"> <div class="col-sm-6">
{% if report.assets.subsections %} {% if report.assets.subsections %}
{% with section = report.assets %} {% with section = report.assets %}
{% include "accounting/report/include/balance-sheet-section.html" %} {% include "accounting/report/include/balance-sheet-section.html" %}
{% endwith %} {% endwith %}
<div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total"> <div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.assets.total < 0 %} text-danger {% endif %}">{{ report.assets.total|accounting_report_format_amount }}</div> <div class="accounting-amount {% if report.assets.total < 0 %} text-danger {% endif %}" role="cell">{{ report.assets.total|accounting_report_format_amount }}</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@@ -73,9 +74,9 @@ First written: 2023/3/7
{% with section = report.liabilities %} {% with section = report.liabilities %}
{% include "accounting/report/include/balance-sheet-section.html" %} {% include "accounting/report/include/balance-sheet-section.html" %}
{% endwith %} {% endwith %}
<div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal"> <div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}">{{ report.liabilities.total|accounting_report_format_amount }}</div> <div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}" role="cell">{{ report.liabilities.total|accounting_report_format_amount }}</div>
</div> </div>
{% endif %} {% endif %}
@@ -83,15 +84,15 @@ First written: 2023/3/7
{% with section = report.owner_s_equity %} {% with section = report.owner_s_equity %}
{% include "accounting/report/include/balance-sheet-section.html" %} {% include "accounting/report/include/balance-sheet-section.html" %}
{% endwith %} {% endwith %}
<div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal"> <div class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-subtotal" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.owner_s_equity.total < 0 %} text-danger {% endif %}">{{ report.owner_s_equity.total|accounting_report_format_amount }}</div> <div class="accounting-amount {% if report.owner_s_equity.total < 0 %} text-danger {% endif %}" role="cell">{{ report.owner_s_equity.total|accounting_report_format_amount }}</div>
</div> </div>
{% endif %} {% endif %}
<div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total"> <div class="d-md-none d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-total" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}">{{ (report.liabilities.total + report.owner_s_equity.total)|accounting_report_format_amount }}</div> <div class="accounting-amount {% if report.liabilities.total < 0 %} text-danger {% endif %}" role="cell">{{ (report.liabilities.total + report.owner_s_equity.total)|accounting_report_format_amount }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -32,8 +32,8 @@ First written: 2023/2/25
{{ A_("Transfer") }} {{ A_("Transfer") }}
</a> </a>
</div> </div>
<button id="accounting-btn-material-fab-speed-dial" class="btn btn-primary rounded-circle accounting-btn-material-fab" type="button" data-target="accounting-material-fab-speed-dial"> <button id="accounting-btn-material-fab-speed-dial" class="btn btn-primary rounded-circle accounting-btn-material-fab" type="button" data-target="accounting-material-fab-speed-dial" aria-label="{{ A_("New Journal Entry") }}">
<i class="fas fa-plus"></i> <i class="fas fa-plus" aria-hidden="true"></i>
</button> </button>
</div> </div>
{% endif %} {% endif %}
@@ -19,24 +19,24 @@ balance-sheet-section.html: A section in the balance sheet.
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/8 First written: 2023/3/8
#} #}
<div class="accounting-report-table-row accounting-balance-sheet-section"> <div class="accounting-report-table-row accounting-balance-sheet-section" role="row">
<div>{{ section.title.title }}</div> <div role="cell">{{ section.title.title }}</div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for subsection in section.subsections %} {% for subsection in section.subsections %}
<div class="accounting-report-table-row accounting-balance-sheet-subsection"> <div class="accounting-report-table-row accounting-balance-sheet-subsection" role="row">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ subsection.title.code }}</span> <span class="d-none d-md-inline">{{ subsection.title.code }}</span>
{{ subsection.title.title }} {{ subsection.title.title }}
</div> </div>
</div> </div>
{% for account in subsection.accounts %} {% for account in subsection.accounts %}
<a class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-account" href="{{ account.url }}"> <a class="d-flex justify-content-between accounting-report-table-row accounting-balance-sheet-account" role="row" href="{{ account.url }}">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ account.account.code }}</span> <span class="d-none d-md-inline">{{ account.account.code }}</span>
{{ account.account.title }} {{ account.account.title }}
</div> </div>
<div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}">{{ account.amount|accounting_report_format_amount }}</div> <div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}" role="cell">{{ account.amount|accounting_report_format_amount }}</div>
</a> </a>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
@@ -19,9 +19,9 @@ income-expenses-row-desktop.html: The row in the income and expenses log for the
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/8 First written: 2023/3/8
#} #}
<div>{{ line_item.date|accounting_format_date }}</div> <div role="cell">{{ line_item.date|accounting_format_date }}</div>
<div>{{ line_item.account.title }}</div> <div role="cell">{{ line_item.account.title }}</div>
<div>{{ line_item.description|accounting_default }}</div> <div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.income|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.income|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.expense|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.expense|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div> <div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
@@ -19,10 +19,10 @@ ledger-row-desktop.html: The row in the ledger for the desktop computers
Author: imacat@mail.imacat.idv.tw (imacat) Author: imacat@mail.imacat.idv.tw (imacat)
First written: 2023/3/8 First written: 2023/3/8
#} #}
<div>{{ line_item.date|accounting_format_date }}</div> <div role="cell">{{ line_item.date|accounting_format_date }}</div>
<div>{{ line_item.description|accounting_default }}</div> <div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
{% if report.account.is_real %} {% if report.account.is_real %}
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div> <div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
{% endif %} {% endif %}
@@ -28,31 +28,31 @@ First written: 2023/3/4
</div> </div>
<div class="modal-body"> <div class="modal-body">
{# Tab navigation #} {# Tab navigation #}
<ul class="nav nav-tabs mb-2"> <ul id="accounting-period-chooser-tab-list" class="nav nav-tabs mb-2" role="tablist" aria-label="{{ A_("Period Type") }}">
<li class="nav-item"> <li class="nav-item">
<span id="accounting-period-chooser-month-tab" class="nav-link {% if report.period.is_type_month %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_type_month %} page {% else %} false {% endif %}" data-tab-id="month"> <button id="accounting-period-chooser-month-tab" class="nav-link {% if report.period.is_type_month %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_type_month %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-month-panel" aria-selected="{% if report.period.is_type_month %}true{% else %}false{% endif %}">
{{ A_("Month") }} {{ A_("Month") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-period-chooser-year-tab" class="nav-link {% if report.period.is_a_year %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_a_year %} page {% else %} false {% endif %}" data-tab-id="year"> <button id="accounting-period-chooser-year-tab" class="nav-link {% if report.period.is_a_year %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_a_year %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-year-panel" aria-selected="{% if report.period.is_a_year %}true{% else %}false{% endif %}">
{{ A_("Year") }} {{ A_("Year") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-period-chooser-day-tab" class="nav-link {% if report.period.is_a_day %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_a_day %} page {% else %} false {% endif %}" data-tab-id="day"> <button id="accounting-period-chooser-day-tab" class="nav-link {% if report.period.is_a_day %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_a_day %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-day-panel" aria-selected="{% if report.period.is_a_day %}true{% else %}false{% endif %}">
{{ A_("Day") }} {{ A_("Day") }}
</span> </button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<span id="accounting-period-chooser-custom-tab" class="nav-link {% if report.period.is_type_arbitrary %} active {% endif %} accounting-clickable" aria-current="{% if report.period.is_type_arbitrary %} page {% else %} false {% endif %}" data-tab-id="custom"> <button id="accounting-period-chooser-custom-tab" class="nav-link {% if report.period.is_type_arbitrary %} active {% endif %} accounting-clickable" type="button" tabindex="{% if report.period.is_type_arbitrary %}0{% else %}-1{% endif %}" role="tab" aria-controls="accounting-period-chooser-custom-panel" aria-selected="{% if report.period.is_type_arbitrary %}true{% else %}false{% endif %}">
{{ A_("Custom") }} {{ A_("Custom") }}
</span> </button>
</li> </li>
</ul> </ul>
{# The month periods #} {# The month periods #}
<div id="accounting-period-chooser-month-page" {% if report.period.is_type_month %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-month-tab"> <div id="accounting-period-chooser-month-panel" {% if not report.period.is_type_month %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-month-tab">
<div> <div>
<a class="btn {% if report.period.is_this_month %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.this_month_url }}"> <a class="btn {% if report.period.is_this_month %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.this_month_url }}">
{{ A_("This Month") }} {{ A_("This Month") }}
@@ -72,7 +72,7 @@ First written: 2023/3/4
</div> </div>
{# The year periods #} {# The year periods #}
<div id="accounting-period-chooser-year-page" {% if report.period.is_a_year %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-year-tab"> <div id="accounting-period-chooser-year-panel" {% if not report.period.is_a_year %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-year-tab">
<a class="btn {% if report.period.is_this_year %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.this_year_url }}"> <a class="btn {% if report.period.is_this_year %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.this_year_url }}">
{{ A_("This Year") }} {{ A_("This Year") }}
</a> </a>
@@ -93,7 +93,7 @@ First written: 2023/3/4
</div> </div>
{# The day periods #} {# The day periods #}
<div id="accounting-period-chooser-day-page" {% if report.period.is_a_day %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-day-tab"> <div id="accounting-period-chooser-day-panel" {% if not report.period.is_a_day %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-day-tab">
<div> <div>
<a class="btn {% if report.period.is_today %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.today_url }}"> <a class="btn {% if report.period.is_today %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.today_url }}">
{{ A_("Today") }} {{ A_("Today") }}
@@ -109,14 +109,14 @@ First written: 2023/3/4
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-period-chooser-day-date" class="form-control" type="date" value="{{ report.period.start|accounting_default }}" min="{{ report.period_chooser.data_start }}" required="required"> <input id="accounting-period-chooser-day-date" class="form-control" type="date" value="{{ report.period.start|accounting_default }}" min="{{ report.period_chooser.data_start }}" required="required">
<label for="accounting-period-chooser-day-date" class="form-label">{{ A_("Date") }}</label> <label for="accounting-period-chooser-day-date" class="form-label">{{ A_("Date") }}</label>
<div id="accounting-period-chooser-day-date-error" class="invalid-feedback"></div> <div id="accounting-period-chooser-day-date-error" class="invalid-feedback" role="alert"></div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{# The custom periods #} {# The custom periods #}
<div id="accounting-period-chooser-custom-page" {% if report.period.is_type_arbitrary %} aria-current="page" {% else %} class="d-none" aria-current="false" {% endif %} aria-labelledby="accounting-period-chooser-custom-tab"> <div id="accounting-period-chooser-custom-panel" {% if not report.period.is_type_arbitrary %} class="d-none" {% endif %} role="tabpanel" aria-labelledby="accounting-period-chooser-custom-tab">
<div> <div>
<a class="btn {% if report.period.is_all %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.all_url }}"> <a class="btn {% if report.period.is_all %} btn-primary {% else %} btn-outline-primary {% endif %}" role="button" href="{{ report.period_chooser.all_url }}">
{{ A_("All") }} {{ A_("All") }}
@@ -127,13 +127,13 @@ First written: 2023/3/4
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-period-chooser-custom-start" class="form-control" type="date" value="{{ report.period.start|accounting_default }}" min="{{ report.period_chooser.data_start }}" max="{{ report.period.end }}" required="required"> <input id="accounting-period-chooser-custom-start" class="form-control" type="date" value="{{ report.period.start|accounting_default }}" min="{{ report.period_chooser.data_start }}" max="{{ report.period.end }}" required="required">
<label for="accounting-period-chooser-custom-start" class="form-label">{{ A_("From") }}</label> <label for="accounting-period-chooser-custom-start" class="form-label">{{ A_("From") }}</label>
<div id="accounting-period-chooser-custom-start-error" class="invalid-feedback"></div> <div id="accounting-period-chooser-custom-start-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div class="form-floating mb-3"> <div class="form-floating mb-3">
<input id="accounting-period-chooser-custom-end" class="form-control" type="date" value="{{ report.period.end|accounting_default }}" min="{{ report.period.start }}" required="required"> <input id="accounting-period-chooser-custom-end" class="form-control" type="date" value="{{ report.period.end|accounting_default }}" min="{{ report.period.start }}" required="required">
<label for="accounting-period-chooser-custom-end" class="form-label">{{ A_("To") }}</label> <label for="accounting-period-chooser-custom-end" class="form-label">{{ A_("To") }}</label>
<div id="accounting-period-chooser-custom-end-error" class="invalid-feedback"></div> <div id="accounting-period-chooser-custom-end-error" class="invalid-feedback" role="alert"></div>
</div> </div>
<div> <div>
@@ -22,7 +22,7 @@ First written: 2023/3/8
{% if accounting_can_edit() %} {% if accounting_can_edit() %}
<div class="btn-group d-none d-md-flex" role="group"> <div class="btn-group d-none d-md-flex" role="group">
<button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"> <button type="button" class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-plus"></i> <i class="fa-solid fa-plus" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("New") }}</span> <span class="d-none d-md-inline">{{ A_("New") }}</span>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@@ -46,7 +46,7 @@ First written: 2023/3/8
{% endif %} {% endif %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button id="accounting-choose-report" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button id="accounting-choose-report" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-book"></i> <i class="fa-solid fa-book" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Report") }}</span> <span class="d-none d-md-inline">{{ A_("Report") }}</span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="accounting-choose-report"> <ul class="dropdown-menu" aria-labelledby="accounting-choose-report">
@@ -59,17 +59,17 @@ First written: 2023/3/8
</li> </li>
{% endfor %} {% endfor %}
<li> <li>
<span class="dropdown-item {% if report.report_chooser.is_search %} active {% endif %} accounting-clickable" data-bs-toggle="modal" data-bs-target="#accounting-search-modal"> <button class="dropdown-item {% if report.report_chooser.is_search %} active {% endif %}" type="button" data-bs-toggle="modal" data-bs-target="#accounting-search-modal">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
{{ A_("Search") }} {{ A_("Search") }}
</span> </button>
</li> </li>
</ul> </ul>
</div> </div>
{% if use_currency_chooser %} {% if use_currency_chooser %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button id="accounting-choose-currency" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button id="accounting-choose-currency" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-money-bill-wave"></i> <i class="fa-solid fa-money-bill-wave" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Currency") }}</span> <span class="d-none d-md-inline">{{ A_("Currency") }}</span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="accounting-choose-currency"> <ul class="dropdown-menu" aria-labelledby="accounting-choose-currency">
@@ -86,7 +86,7 @@ First written: 2023/3/8
{% if use_account_chooser %} {% if use_account_chooser %}
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button id="accounting-choose-account" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button id="accounting-choose-account" class="btn btn-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-clipboard"></i> <i class="fa-solid fa-clipboard" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Account") }}</span> <span class="d-none d-md-inline">{{ A_("Account") }}</span>
</button> </button>
<ul class="dropdown-menu accounting-toolbar-accounts" aria-labelledby="accounting-choose-account"> <ul class="dropdown-menu accounting-toolbar-accounts" aria-labelledby="accounting-choose-account">
@@ -102,18 +102,18 @@ First written: 2023/3/8
{% endif %} {% endif %}
{% if use_period_chooser %} {% if use_period_chooser %}
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-period-chooser-modal"> <button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-period-chooser-modal">
<i class="fa-solid fa-calendar-day"></i> <i class="fa-solid fa-calendar-day" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Period") }}</span> <span class="d-none d-md-inline">{{ A_("Period") }}</span>
</button> </button>
{% endif %} {% endif %}
{% if report.has_data %} {% if report.has_data %}
<a class="btn btn-primary" role="button" href="{{ report.csv_uri }}"> <a class="btn btn-primary" role="button" href="{{ report.csv_uri }}">
<i class="fa-solid fa-download"></i> <i class="fa-solid fa-download" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Download") }}</span> <span class="d-none d-md-inline">{{ A_("Download") }}</span>
</a> </a>
{% else %} {% else %}
<button class="btn btn-secondary" type="button" disabled="disabled"> <button class="btn btn-secondary" type="button" disabled="disabled">
<i class="fa-solid fa-download"></i> <i class="fa-solid fa-download" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Download") }}</span> <span class="d-none d-md-inline">{{ A_("Download") }}</span>
</button> </button>
{% endif %} {% endif %}
@@ -122,7 +122,7 @@ First written: 2023/3/8
<input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required"> <input id="accounting-toolbar-search" class="form-control form-control-sm" type="search" name="q" value="{{ request.args.q }}" placeholder=" " required="required">
<label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text"> <label id="accounting-toolbar-search-label" for="accounting-toolbar-search" class="input-group-text">
<button type="submit"> <button type="submit">
<i class="fa-solid fa-magnifying-glass"></i> <i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<span class="d-none d-md-inline">{{ A_("Search") }}</span> <span class="d-none d-md-inline">{{ A_("Search") }}</span>
</button> </button>
</label> </label>
@@ -23,6 +23,7 @@ First written: 2023/3/5
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %} {% endblock %}
@@ -49,27 +50,27 @@ First written: 2023/3/5
{% include "accounting/include/pagination.html" %} {% include "accounting/include/pagination.html" %}
{% endwith %} {% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-income-expenses-table"> <div class="d-none d-md-block accounting-report-table accounting-income-expenses-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Date") }}</div> <div role="columnheader">{{ A_("Date") }}</div>
<div>{{ A_("Account") }}</div> <div role="columnheader">{{ A_("Account") }}</div>
<div>{{ A_("Description") }}</div> <div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Income") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Income") }}</div>
<div class="accounting-amount">{{ A_("Expense") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Expense") }}</div>
<div class="accounting-amount">{{ A_("Balance") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Balance") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% if report.brought_forward %} {% if report.brought_forward %}
{% with line_item = report.brought_forward %} {% with line_item = report.brought_forward %}
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
{% include "accounting/report/include/income-expenses-row-desktop.html" %} {% include "accounting/report/include/income-expenses-row-desktop.html" %}
</div> </div>
{% endwith %} {% endwith %}
{% endif %} {% endif %}
{% for line_item in report.line_items %} {% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}"> <a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}" role="row">
{% include "accounting/report/include/income-expenses-row-desktop.html" %} {% include "accounting/report/include/income-expenses-row-desktop.html" %}
</a> </a>
{% endfor %} {% endfor %}
@@ -77,11 +78,11 @@ First written: 2023/3/5
{% if report.total %} {% if report.total %}
{% with line_item = report.total %} {% with line_item = report.total %}
<div class="accounting-report-table-footer"> <div class="accounting-report-table-footer">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount">{{ line_item.income|accounting_format_amount }}</div> <div class="accounting-amount" role="cell">{{ line_item.income|accounting_format_amount }}</div>
<div class="accounting-amount">{{ line_item.expense|accounting_format_amount }}</div> <div class="accounting-amount" role="cell">{{ line_item.expense|accounting_format_amount }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div> <div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
</div> </div>
</div> </div>
{% endwith %} {% endwith %}
@@ -23,6 +23,7 @@ First written: 2023/3/7
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %} {% endblock %}
@@ -55,44 +56,44 @@ First written: 2023/3/7
</h2> </h2>
</div> </div>
<div class="accounting-report-table accounting-income-statement-table"> <div class="accounting-report-table accounting-income-statement-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div class="accounting-amount">{{ A_("Amount") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Amount") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for section in report.sections %} {% for section in report.sections %}
<div class="accounting-report-table-row accounting-income-statement-section"> <div class="accounting-report-table-row accounting-income-statement-section" role="row">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ section.title.code }}</span> <span class="d-none d-md-inline">{{ section.title.code }}</span>
{{ section.title.title }} {{ section.title.title }}
</div> </div>
</div> </div>
{% for subsection in section.subsections %} {% for subsection in section.subsections %}
<div class="accounting-report-table-row accounting-income-statement-subsection"> <div class="accounting-report-table-row accounting-income-statement-subsection" role="row">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ subsection.title.code }}</span> <span class="d-none d-md-inline">{{ subsection.title.code }}</span>
{{ subsection.title.title }} {{ subsection.title.title }}
</div> </div>
</div> </div>
{% for account in subsection.accounts %} {% for account in subsection.accounts %}
<a class="accounting-report-table-row accounting-income-statement-account" href="{{ account.url }}"> <a class="accounting-report-table-row accounting-income-statement-account" role="row" href="{{ account.url }}">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ account.account.code }}</span> <span class="d-none d-md-inline">{{ account.account.code }}</span>
{{ account.account.title }} {{ account.account.title }}
</div> </div>
<div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}">{{ account.amount|accounting_report_format_amount }}</div> <div class="accounting-amount {% if account.amount < 0 %} text-danger {% endif %}" role="cell">{{ account.amount|accounting_report_format_amount }}</div>
</a> </a>
{% endfor %} {% endfor %}
<div class="accounting-report-table-row accounting-income-statement-subtotal"> <div class="accounting-report-table-row accounting-income-statement-subtotal" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount {% if subsection.total < 0 %} text-danger {% endif %}">{{ subsection.total|accounting_report_format_amount }}</div> <div class="accounting-amount {% if subsection.total < 0 %} text-danger {% endif %}" role="cell">{{ subsection.total|accounting_report_format_amount }}</div>
</div> </div>
{% endfor %} {% endfor %}
<div class="accounting-report-table-row accounting-income-statement-total"> <div class="accounting-report-table-row accounting-income-statement-total" role="row">
<div>{{ section.accumulated.title }}</div> <div role="cell">{{ section.accumulated.title }}</div>
<div class="accounting-amount {% if section.accumulated.amount < 0 %} text-danger {% endif %}">{{ section.accumulated.amount|accounting_report_format_amount }}</div> <div class="accounting-amount {% if section.accumulated.amount < 0 %} text-danger {% endif %}" role="cell">{{ section.accumulated.amount|accounting_report_format_amount }}</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -23,6 +23,7 @@ First written: 2023/3/4
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %} {% endblock %}
@@ -47,29 +48,29 @@ First written: 2023/3/4
{% include "accounting/include/pagination.html" %} {% include "accounting/include/pagination.html" %}
{% endwith %} {% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-journal-table"> <div class="d-none d-md-block accounting-report-table accounting-journal-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Date") }}</div> <div role="columnheader">{{ A_("Date") }}</div>
<div>{{ A_("Currency") }}</div> <div role="columnheader">{{ A_("Currency") }}</div>
<div>{{ A_("Account") }}</div> <div role="columnheader">{{ A_("Account") }}</div>
<div>{{ A_("Description") }}</div> <div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for line_item in report.line_items %} {% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}"> <a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div> <div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>{{ line_item.currency.name }}</div> <div role="cell">{{ line_item.currency.name }}</div>
<div> <div role="cell">
<span class="d-none d-md-inline">{{ line_item.account.code }}</span> <span class="d-none d-md-inline">{{ line_item.account.code }}</span>
{{ line_item.account.title }} {{ line_item.account.title }}
</div> </div>
<div>{{ line_item.description|accounting_default }}</div> <div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@@ -23,6 +23,7 @@ First written: 2023/3/5
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %} {% endblock %}
@@ -49,28 +50,28 @@ First written: 2023/3/5
{% include "accounting/include/pagination.html" %} {% include "accounting/include/pagination.html" %}
{% endwith %} {% endwith %}
<div class="d-none d-md-block accounting-report-table {% if report.account.is_real %} accounting-ledger-real-table {% else %} accounting-ledger-nominal-table {% endif %}"> <div class="d-none d-md-block accounting-report-table {% if report.account.is_real %} accounting-ledger-real-table {% else %} accounting-ledger-nominal-table {% endif %}" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row">
<div>{{ A_("Date") }}</div> <div role="columnheader">{{ A_("Date") }}</div>
<div>{{ A_("Description") }}</div> <div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
{% if report.account.is_real %} {% if report.account.is_real %}
<div class="accounting-amount">{{ A_("Balance") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Balance") }}</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% if report.brought_forward %} {% if report.brought_forward %}
{% with line_item = report.brought_forward %} {% with line_item = report.brought_forward %}
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
{% include "accounting/report/include/ledger-row-desktop.html" %} {% include "accounting/report/include/ledger-row-desktop.html" %}
</div> </div>
{% endwith %} {% endwith %}
{% endif %} {% endif %}
{% for line_item in report.line_items %} {% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}"> <a class="accounting-report-table-row" href="{{ line_item.url|accounting_append_next }}" role="row">
{% include "accounting/report/include/ledger-row-desktop.html" %} {% include "accounting/report/include/ledger-row-desktop.html" %}
</a> </a>
{% endfor %} {% endfor %}
@@ -78,12 +79,12 @@ First written: 2023/3/5
{% if report.total %} {% if report.total %}
{% with line_item = report.total %} {% with line_item = report.total %}
<div class="accounting-report-table-footer"> <div class="accounting-report-table-footer">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
{% if report.account.is_real %} {% if report.account.is_real %}
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div> <div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@@ -44,29 +44,29 @@ First written: 2023/3/8
{% include "accounting/include/pagination.html" %} {% include "accounting/include/pagination.html" %}
{% endwith %} {% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-journal-table"> <div class="d-none d-md-block accounting-report-table accounting-journal-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Date") }}</div> <div role="columnheader">{{ A_("Date") }}</div>
<div>{{ A_("Currency") }}</div> <div role="columnheader">{{ A_("Currency") }}</div>
<div>{{ A_("Account") }}</div> <div role="columnheader">{{ A_("Account") }}</div>
<div>{{ A_("Description") }}</div> <div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for line_item in report.line_items %} {% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}"> <a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div> <div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>{{ line_item.currency.name }}</div> <div role="cell">{{ line_item.currency.name }}</div>
<div> <div role="cell">
<span class="d-none d-md-inline">{{ line_item.account.code }}</span> <span class="d-none d-md-inline">{{ line_item.account.code }}</span>
{{ line_item.account.title }} {{ line_item.account.title }}
</div> </div>
<div>{{ line_item.description|accounting_default }}</div> <div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@@ -23,6 +23,7 @@ First written: 2023/3/5
{% block accounting_scripts %} {% block accounting_scripts %}
<script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/material-fab-speed-dial.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/base-tablist.js") }}"></script>
<script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script> <script src="{{ url_for("accounting.static", filename="js/period-chooser.js") }}"></script>
{% endblock %} {% endblock %}
@@ -55,31 +56,31 @@ First written: 2023/3/5
</h2> </h2>
</div> </div>
<div class="accounting-report-table accounting-trial-balance-table"> <div class="accounting-report-table accounting-trial-balance-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Account") }}</div> <div role="columnheader">{{ A_("Account") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for account in report.accounts %} {% for account in report.accounts %}
<a class="accounting-report-table-row" href="{{ account.url }}"> <a class="accounting-report-table-row" href="{{ account.url }}" role="row">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ account.account.code }}</span> <span class="d-none d-md-inline">{{ account.account.code }}</span>
{{ account.account.title }} {{ account.account.title }}
</div> </div>
<div class="accounting-amount">{{ account.debit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ account.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ account.credit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ account.credit|accounting_format_amount|accounting_default }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
<div class="accounting-report-table-footer"> <div class="accounting-report-table-footer">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Total") }}</div> <div role="cell">{{ A_("Total") }}</div>
<div class="accounting-amount">{{ report.total.debit|accounting_format_amount }}</div> <div class="accounting-amount" role="cell">{{ report.total.debit|accounting_format_amount }}</div>
<div class="accounting-amount">{{ report.total.credit|accounting_format_amount }}</div> <div class="accounting-amount" role="cell">{{ report.total.credit|accounting_format_amount }}</div>
</div> </div>
</div> </div>
</div> </div>
@@ -52,20 +52,20 @@ First written: 2023/4/8
</h2> </h2>
</div> </div>
<div class="accounting-report-table accounting-unapplied-account-table"> <div class="accounting-report-table accounting-unapplied-account-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div class="accounting-amount">{{ A_("Count") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Count") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for account in report.accounts %} {% for account in report.accounts %}
<a class="accounting-report-table-row" href="{{ url_for("accounting-report.unapplied", currency=report.currency, account=account, period=report.period) }}"> <a class="accounting-report-table-row" href="{{ url_for("accounting-report.unapplied", currency=report.currency, account=account, period=report.period) }}" role="row">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ account.code }}</span> <span class="d-none d-md-inline">{{ account.code }}</span>
{{ account.title }} {{ account.title }}
</div> </div>
<div class="accounting-amount">{{ account.count }}</div> <div class="accounting-amount" role="cell">{{ account.count }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@@ -45,22 +45,22 @@ First written: 2023/4/7
{% include "accounting/include/pagination.html" %} {% include "accounting/include/pagination.html" %}
{% endwith %} {% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-unapplied-table"> <div class="d-none d-md-block accounting-report-table accounting-unapplied-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Date") }}</div> <div role="columnheader">{{ A_("Date") }}</div>
<div>{{ A_("Description") }}</div> <div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Amount") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Amount") }}</div>
<div class="accounting-amount">{{ A_("Net Balance") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Net Balance") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for line_item in report.line_items %} {% for line_item in report.line_items %}
<a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}"> <a class="accounting-report-table-row" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div> <div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div>{{ line_item.description|accounting_default }}</div> <div role="cell">{{ line_item.description|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.amount|accounting_format_amount }}</div> <div class="accounting-amount" role="cell">{{ line_item.amount|accounting_format_amount }}</div>
<div class="accounting-amount">{{ line_item.net_balance|accounting_format_amount }}</div> <div class="accounting-amount" role="cell">{{ line_item.net_balance|accounting_format_amount }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@@ -52,20 +52,20 @@ First written: 2023/4/17
</h2> </h2>
</div> </div>
<div class="accounting-report-table accounting-unapplied-account-table"> <div class="accounting-report-table accounting-unapplied-account-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div class="accounting-amount">{{ A_("Count") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Count") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for account in report.accounts %} {% for account in report.accounts %}
<a class="accounting-report-table-row" href="{{ url_for("accounting-report.unmatched", currency=report.currency, account=account, period=report.period) }}"> <a class="accounting-report-table-row" href="{{ url_for("accounting-report.unmatched", currency=report.currency, account=account, period=report.period) }}" role="row">
<div> <div role="cell">
<span class="d-none d-md-inline">{{ account.code }}</span> <span class="d-none d-md-inline">{{ account.code }}</span>
{{ account.title }} {{ account.title }}
</div> </div>
<div class="accounting-amount">{{ account.count }}</div> <div class="accounting-amount" role="cell">{{ account.count }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
@@ -42,7 +42,7 @@ First written: 2023/4/17
{% if report.matched_pairs %} {% if report.matched_pairs %}
<button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-match-modal"> <button class="btn btn-primary" type="button" data-bs-toggle="modal" data-bs-target="#accounting-match-modal">
<i class="fa-solid fa-link"></i> <i class="fa-solid fa-link" aria-hidden="true"></i>
{{ A_("Match") }} {{ A_("Match") }}
</button> </button>
@@ -86,29 +86,29 @@ First written: 2023/4/17
{% include "accounting/include/pagination.html" %} {% include "accounting/include/pagination.html" %}
{% endwith %} {% endwith %}
<div class="d-none d-md-block accounting-report-table accounting-unmatched-table"> <div class="d-none d-md-block accounting-report-table accounting-unmatched-table" role="table">
<div class="accounting-report-table-header"> <div class="accounting-report-table-header">
<div class="accounting-report-table-row"> <div class="accounting-report-table-row" role="row">
<div>{{ A_("Date") }}</div> <div role="columnheader">{{ A_("Date") }}</div>
<div>{{ A_("Description") }}</div> <div role="columnheader">{{ A_("Description") }}</div>
<div class="accounting-amount">{{ A_("Debit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Debit") }}</div>
<div class="accounting-amount">{{ A_("Credit") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Credit") }}</div>
<div class="accounting-amount">{{ A_("Balance") }}</div> <div class="accounting-amount" role="columnheader">{{ A_("Balance") }}</div>
</div> </div>
</div> </div>
<div class="accounting-report-table-body"> <div class="accounting-report-table-body">
{% for line_item in report.line_items %} {% for line_item in report.line_items %}
<a class="accounting-report-table-row {% if not line_item.match %} accounting-report-table-row-danger {% endif %}" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}"> <a class="accounting-report-table-row {% if not line_item.match %} accounting-report-table-row-danger {% endif %}" href="{{ url_for("accounting.journal-entry.detail", journal_entry=line_item.journal_entry)|accounting_append_next }}" role="row">
<div>{{ line_item.journal_entry.date|accounting_format_date }}</div> <div role="cell">{{ line_item.journal_entry.date|accounting_format_date }}</div>
<div> <div role="cell">
{{ line_item.description|accounting_default }} {{ line_item.description|accounting_default }}
{% if line_item.match %} {% if line_item.match %}
<div class="small">{{ A_("Can match %(item)s", item=line_item.match) }}</div> <div class="small">{{ A_("Can match %(item)s", item=line_item.match) }}</div>
{% endif %} {% endif %}
</div> </div>
<div class="accounting-amount">{{ line_item.debit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.debit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount">{{ line_item.credit|accounting_format_amount|accounting_default }}</div> <div class="accounting-amount" role="cell">{{ line_item.credit|accounting_format_amount|accounting_default }}</div>
<div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}">{{ line_item.balance|accounting_report_format_amount }}</div> <div class="accounting-amount {% if line_item.balance < 0 %} text-danger {% endif %}" role="cell">{{ line_item.balance|accounting_report_format_amount }}</div>
</a> </a>
{% endfor %} {% endfor %}
</div> </div>