diff --git a/src/ExpensesCalculator.UI/public/i18n/en.json b/src/ExpensesCalculator.UI/public/i18n/en.json index f6edd69..2c420ab 100644 --- a/src/ExpensesCalculator.UI/public/i18n/en.json +++ b/src/ExpensesCalculator.UI/public/i18n/en.json @@ -1,4 +1,8 @@ { + "GENERAL": { + "CURRENCY_SYMBOL": "$", + "CURRENCY_CODE": "USD" + }, "NAV": { "LOGIN": "Login", "REGISTER": "Register", @@ -13,7 +17,7 @@ "GITHUB": "Github" }, "HOME": { - "COPYRIGHT": "© 2024-2026 ExpensesCalculator" + "COPYRIGHT": "© 2024-2026 ExpensesTracker" }, "LOGIN": { "TITLE": "Log in", @@ -154,9 +158,9 @@ "PAYER_LABEL": "Payer", "SELECT_PAYER": "Select Payer", "SUM_LABEL": "Total Sum", - "ADD_BUTTON": "Add Check", - "EDIT_BUTTON": "Edit Check", - "DELETE_BUTTON": "Delete Check" + "ADD_BUTTON": "Add", + "EDIT_BUTTON": "Edit", + "DELETE_BUTTON": "Delete" }, "TOAST": { "SUCCESS": "Success!", @@ -189,6 +193,7 @@ "PERSON": "person", "PEOPLE": "people", "NO_TAGS": "No tags", + "VIEW_IN_EXPENSES": "View in Expenses", "SHOWING": "Showing items from", "TO": "to", "OF": "of", @@ -203,7 +208,8 @@ "TOTAL_SUM": "Total Sum", "USER_COUNT": "User Count", "RATING": "Rating", - "TAGS": "Tags" + "TAGS": "Tags", + "APPLY": "Apply" }, "SORT": { "CAPTION": "Sort", @@ -254,8 +260,8 @@ }, "VALIDATION": { "NAME_REQUIRED": "Item name is required.", - "PRICE_INVALID": "Price > 0", - "AMOUNT_INVALID": "Amount > 0", + "PRICE_INVALID": "Price > 0 & <= 10000", + "AMOUNT_INVALID": "Amount > 0 & <= 1000", "RATING_REQUIRED": "Rating is required.", "USERS_REQUIRED": "At least one user is required." }, @@ -287,8 +293,15 @@ "EXPENSES_TABLE_CONTENT": "This table shows the expenses. Click on any column header to sort your expenses by that field. The table below displays all expense rows, which you can double-click to view details.", "ACTIONS_MENU_TITLE": "Actions Menu", "ACTIONS_MENU_CONTENT": "Click the three dots button to access actions for each expense. You can open details, calculate expenses, edit, share with other users, or delete the expense entry.", + "SORT_CONTROLS_TITLE": "Sort Controls", + "SORT_CONTROLS_CONTENT": "Use these buttons to sort your expenses. Click any button to sort by that field (Date, Location, Participants, or Total Sum). Click again to toggle between ascending and descending order.", + "EXPENSES_ACCORDION_TITLE": "Expenses List", + "EXPENSES_ACCORDION_CONTENT": "This accordion shows all your expenses. Each row displays the date and location. Click the arrow to expand and view participants, total sum, and action buttons to manage the expense.", "PAGINATION_TITLE": "Pagination Controls", - "PAGINATION_CONTENT": "Navigate through your expenses with these controls. Change the number of items displayed per page using the dropdown on the right." + "PAGINATION_CONTENT": "Navigate through your expenses with these controls. Change the number of items displayed per page using the dropdown on the right.", + "PREV_BTN": "Previous", + "NEXT_BTN": "Next", + "END_BTN": "End tour" }, "TOUR_DETAILS": { "BACK_BTN_TITLE": "Back to List", @@ -300,7 +313,15 @@ "ADD_ITEM_TITLE": "Add Item", "ADD_ITEM_CONTENT": "Click this button to add items to the check. Specify the item name, price, quantity, and select which participants consumed it. The check total will update automatically.", "CALCULATOR_TITLE": "Calculate Expenses", - "CALCULATOR_CONTENT": "Once you've added all checks and items, click this button to view the calculations showing how expenses are split among participants." + "CALCULATOR_CONTENT": "Once you've added all checks and items, click this button to view the calculations showing how expenses are split among participants.", + "FILTER_SORT_CONTROLS_TITLE": "Filter & Sort", + "FILTER_SORT_CONTROLS_CONTENT": "Use these controls to filter and sort your checks. The filter button lets you search by location, payer, or sum, while the sort dropdown helps organize checks by different criteria.", + "CHECKS_ACCORDION_TITLE": "Checks List", + "CHECKS_ACCORDION_CONTENT": "This shows all your checks in an accordion format optimized for mobile. Each check displays the location and total sum. Tap any check to expand it and view its items.", + "ACCORDION_CHECK_ITEM_TITLE": "Check Details", + "ACCORDION_CHECK_ITEM_CONTENT": "Tap the check header to expand or collapse it. When expanded, you can view the payer information, edit or delete the check, and see all items belonging to this check.", + "ACTIONS_MENU_TITLE": "Actions Menu", + "ACTIONS_MENU_CONTENT": "Click the three-dot menu to access additional actions for this expense entry, including edit, share, and delete options." }, "TOUR_CALCULATIONS": { "BACK_BTN_TITLE": "Back to Details", @@ -317,6 +338,8 @@ "SEARCH_FILTER_CONTENT": "Search your items by different criteria. Use the dropdown to switch between filtering by name, description, price, amount, total sum, user count, or rating. Type in the search box to find specific items.", "SORT_BAR_TITLE": "Sort Options", "SORT_BAR_CONTENT": "Sort your items by different attributes. Click the dropdown to choose a column (name, price, amount, total price, user count, or rating) and toggle between ascending and descending order.", + "FILTER_SORT_CONTROLS_TITLE": "Filter & Sort", + "FILTER_SORT_CONTROLS_CONTENT": "Use the Filter button to search items, apply tag filters, and toggle showing only your items. Use the Sort dropdown to sort by name, price, amount, total price, user count, or rating.", "ADD_ITEM_TITLE": "Add New Item", "ADD_ITEM_CONTENT": "Click this button to add a new item to your recommendations. You can add items with name, description, price, amount, rating, and tags. These are your personal items that you can only access from this recommendations view.", "ONLY_MY_ITEMS_TITLE": "Filter Your Items", diff --git a/src/ExpensesCalculator.UI/public/i18n/ua.json b/src/ExpensesCalculator.UI/public/i18n/ua.json index 9fd4254..e22b403 100644 --- a/src/ExpensesCalculator.UI/public/i18n/ua.json +++ b/src/ExpensesCalculator.UI/public/i18n/ua.json @@ -1,4 +1,8 @@ { + "GENERAL": { + "CURRENCY_SYMBOL": "₴", + "CURRENCY_CODE": "UAH" + }, "NAV": { "LOGIN": "Увійти", "REGISTER": "Зареєструватися", @@ -13,7 +17,7 @@ "GITHUB": "Github" }, "HOME": { - "COPYRIGHT": "© 2024-2026 ExpensesCalculator" + "COPYRIGHT": "© 2024-2026 ExpensesTracker" }, "LOGIN": { "TITLE": "Увійти", @@ -188,6 +192,7 @@ "PERSON": "користувач", "PEOPLE": "користувачів", "NO_TAGS": "Без тегів", + "VIEW_IN_EXPENSES": "Переглянути у витратах", "SHOWING": "Показано товари з", "TO": "до", "OF": "з", @@ -203,7 +208,8 @@ "TOTAL_SUM": "Загальна сума", "USER_COUNT": "Користувачі", "RATING": "Рейтинг", - "TAGS": "Теги" + "TAGS": "Теги", + "APPLY": "Застосувати" }, "SORT": { "CAPTION": "Сортування", @@ -254,8 +260,8 @@ }, "VALIDATION": { "NAME_REQUIRED": "Назва товару обов'язкова.", - "PRICE_INVALID": "Ціна > 0", - "AMOUNT_INVALID": "Кількість > 0", + "PRICE_INVALID": "Ціна > 0 & <= 10000", + "AMOUNT_INVALID": "Кількість > 0 & <= 1000", "RATING_REQUIRED": "Рейтинг обов'язковий.", "USERS_REQUIRED": "Потрібен хоча б один користувач." }, @@ -287,8 +293,15 @@ "EXPENSES_TABLE_CONTENT": "Ця таблиця показує витрати. Натисніть на будь-який заголовок стовпця, щоб відсортувати витрати за цим полем. Таблиця нижче відображає всі рядки витрат, які можна двічі клацнути для перегляду деталей.", "ACTIONS_MENU_TITLE": "Меню дій", "ACTIONS_MENU_CONTENT": "Натисніть кнопку з трьома крапками, щоб отримати доступ до дій для кожної витрати. Ви можете відкрити деталі, розрахувати витрати, редагувати, поділитися з іншими користувачами або видалити запис витрат.", + "SORT_CONTROLS_TITLE": "Елементи керування сортуванням", + "SORT_CONTROLS_CONTENT": "Використовуйте ці кнопки для сортування ваших витрат. Натисніть будь-яку кнопку, щоб відсортувати за цим полем (Дата, Місце, Учасники або Загальна сума). Натисніть ще раз, щоб перемикатися між зростанням та спаданням.", + "EXPENSES_ACCORDION_TITLE": "Список витрат", + "EXPENSES_ACCORDION_CONTENT": "Цей розкривний список показує всі ваші витрати. Кожен рядок відображає дату та місце. Натисніть стрілку, щоб розгорнути та переглянути учасників, загальну суму та кнопки дій для керування витратами.", "PAGINATION_TITLE": "Сторінки", - "PAGINATION_CONTENT": "Перемикайтеся між витратами за допомогою цих елементів керування. Змініть кількість елементів на сторінці, використовуючи випадаюче меню справа." + "PAGINATION_CONTENT": "Перемикайтеся між витратами за допомогою цих елементів керування. Змініть кількість елементів на сторінці, використовуючи випадаюче меню справа.", + "PREV_BTN": "Попередній", + "NEXT_BTN": "Далі", + "END_BTN": "Завершити" }, "TOUR_DETAILS": { "BACK_BTN_TITLE": "Назад до списку", @@ -300,7 +313,15 @@ "ADD_ITEM_TITLE": "Додати товар", "ADD_ITEM_CONTENT": "Натисніть цю кнопку, щоб додати товари до чека. Вкажіть назву товару, ціну, кількість та оберіть, які учасники його споживали. Загальна сума чека оновиться автоматично.", "CALCULATOR_TITLE": "Розрахунки", - "CALCULATOR_CONTENT": "Після того, як ви додали всі чеки та товари, натисніть цю кнопку, щоб переглянути розрахунки, які показують, як витрати розподіляються між учасниками." + "CALCULATOR_CONTENT": "Після того, як ви додали всі чеки та товари, натисніть цю кнопку, щоб переглянути розрахунки, які показують, як витрати розподіляються між учасниками.", + "FILTER_SORT_CONTROLS_TITLE": "Фільтр і Сортування", + "FILTER_SORT_CONTROLS_CONTENT": "Використовуйте ці елементи керування для фільтрації та сортування ваших чеків. Кнопка фільтра дозволяє шукати за місцем, платником або сумою, а випадаюче меню сортування допомагає організувати чеки за різними критеріями.", + "CHECKS_ACCORDION_TITLE": "Список Чеків", + "CHECKS_ACCORDION_CONTENT": "Це показує всі ваші чеки в форматі акордеона, оптимізованому для мобільних пристроїв. Кожен чек відображає місце та загальну суму. Торкніться будь-якого чека, щоб розгорнути його та переглянути його елементи.", + "ACCORDION_CHECK_ITEM_TITLE": "Деталі Чека", + "ACCORDION_CHECK_ITEM_CONTENT": "Торкніться заголовка чека, щоб розгорнути або згорнути його. Коли розгорнуто, ви можете переглянути інформацію про платника, редагувати або видалити чек, а також переглянути всі елементи, що належать до цього чека.", + "ACTIONS_MENU_TITLE": "Меню Дій", + "ACTIONS_MENU_CONTENT": "Натисніть меню з трьома крапками, щоб отримати доступ до додаткових дій для цього запису витрат, включаючи редагування, спільний доступ та видалення." }, "TOUR_CALCULATIONS": { "BACK_BTN_TITLE": "Назад до деталей", @@ -317,6 +338,8 @@ "SEARCH_FILTER_CONTENT": "Шукайте свої товари за різними критеріями. Використовуйте випадаюче меню, щоб перемикатися між фільтрацією за назвою, описом, ціною, кількістю, загальною сумою, кількістю користувачів або рейтингом. Введіть текст у поле пошуку, щоб знайти конкретні товари.", "SORT_BAR_TITLE": "Сортування", "SORT_BAR_CONTENT": "Сортуйте свої товари за різними атрибутами. Натисніть на випадаюче меню, щоб вибрати колонку (назва, ціна, кількість, загальна ціна, кількість користувачів або рейтинг) та перемикайтеся між зростанням та спаданням.", + "FILTER_SORT_CONTROLS_TITLE": "Фільтр та сортування", + "FILTER_SORT_CONTROLS_CONTENT": "Використовуйте кнопку Фільтр для пошуку товарів, застосування фільтрів за тегами та перемикання показу лише ваших товарів. Використовуйте випадаюче меню Сортування для сортування за назвою, ціною, кількістю, загальною ціною, кількістю користувачів або рейтингом.", "ADD_ITEM_TITLE": "Додати новий товар", "ADD_ITEM_CONTENT": "Натисніть цю кнопку, щоб додати новий товар до ваших рекомендацій. Ви можете додавати товари з назвою, описом, ціною, кількістю, рейтингом та тегами. Це ваші персональні товари, до яких ви можете отримати доступ тільки з цього перегляду рекомендацій.", "ONLY_MY_ITEMS_TITLE": "Тільки ваші товари", diff --git a/src/ExpensesCalculator.UI/src/app/app.component.html b/src/ExpensesCalculator.UI/src/app/app.component.html index f2548d7..43ea2cd 100644 --- a/src/ExpensesCalculator.UI/src/app/app.component.html +++ b/src/ExpensesCalculator.UI/src/app/app.component.html @@ -5,7 +5,7 @@
-
- \ No newline at end of file + + \ No newline at end of file diff --git a/src/ExpensesCalculator.UI/src/app/app.component.ts b/src/ExpensesCalculator.UI/src/app/app.component.ts index 07a0e70..ca916e3 100644 --- a/src/ExpensesCalculator.UI/src/app/app.component.ts +++ b/src/ExpensesCalculator.UI/src/app/app.component.ts @@ -3,6 +3,7 @@ import { isPlatformBrowser } from '@angular/common'; import { VerticalNavbarComponent } from "./shared/vertical-navbar/vertical-navbar.component"; import { HorizontalNavbarComponent } from './shared/horizontal-navbar/horizontal-navbar.component'; import { ToastComponent } from './shared/toast/toast.component'; +import { ModalWindowComponent } from './shared/modal-window/modal-window.component'; import { RouterOutlet } from "@angular/router"; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; @@ -11,7 +12,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core'; standalone: true, templateUrl: './app.component.html', styleUrl: './app.component.css', - imports: [VerticalNavbarComponent, HorizontalNavbarComponent, ToastComponent, RouterOutlet, TranslatePipe] + imports: [VerticalNavbarComponent, HorizontalNavbarComponent, ToastComponent, ModalWindowComponent, RouterOutlet, TranslatePipe] }) export class AppComponent { title = 'Expenses Calculator'; diff --git a/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.css b/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.css index 758af88..162c928 100644 --- a/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.css +++ b/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.css @@ -138,3 +138,127 @@ td { background-color: #1f429d !important; color: white; } + +/* Accordion styles for small screens */ +.accordion-button { + background-color: transparent !important; + box-shadow: none !important; + border-radius: 0 !important; +} + +.accordion-button:not(.collapsed) { + background-color: transparent !important; + color: white !important; +} + +.accordion-button::after { + filter: invert(1); +} + +.accordion-button:focus { + box-shadow: none !important; + border-color: transparent !important; +} + +.accordion-item { + border-bottom: 1px solid rgba(13, 110, 253, 0.3) !important; +} + +.accordion-item:last-child { + border-bottom: none !important; +} + +.accordion-body { + border-top: 1px solid rgba(13, 110, 253, 0.2); +} + +/* Payer dropdown styling */ +.form-group .dropdown .btn.form-control { + font-size: 0.95rem; + padding: 0.375rem 0.75rem; + background-color: white; + border-color: #ced4da; + color: #212529; + text-align: left; +} + +.form-group .dropdown .btn.form-control:hover, +.form-group .dropdown .btn.form-control:focus, +.form-group .dropdown .btn.form-control:active { + background-color: white; + border-color: #ced4da; + color: #212529; + box-shadow: none; +} + +.form-group .dropdown .btn.form-control.is-invalid { + border-color: #dc3545; + background-color: white; +} + +.form-group .dropdown .btn.form-control.is-valid { + border-color: #198754; + background-color: white; +} + +.form-group .dropdown-menu { + background-color: white; + border-color: #ced4da; + max-height: 250px; + overflow-y: auto; +} + +.form-group .dropdown-item { + color: #212529; + font-size: 0.95rem; + padding: 0.5rem 0.75rem; +} + +.form-group .dropdown-item:hover, +.form-group .dropdown-item:focus { + background-color: #e9ecef; + color: #212529; +} + +/* Component-specific responsive styles */ +@media (max-width: 576px) { + .pagination-info { + font-size: 0.8rem !important; + } + + /* Reduce payer dropdown size on small screens */ + .form-group .dropdown .btn.form-control { + font-size: 0.85rem !important; + padding: 0.25rem 0.5rem !important; + } + + .form-group .dropdown-item { + font-size: 0.85rem !important; + padding: 0.375rem 0.5rem !important; + } + + /* Filter and Sort group - side by side buttons */ + .filter-sort-group { + gap: 0.5rem !important; + } + + .filter-sort-group .filter-btn { + width: 50% !important; + font-size: 0.85rem !important; + padding: 0.2rem 0.5rem !important; + line-height: 1.2 !important; + } + + .filter-sort-group app-sort-bar { + width: 50% !important; + flex: 0 0 50%; + } + + .filter-sort-group app-sort-bar .sort-dropdown { + width: 100%; + } + + .filter-sort-group app-sort-bar button { + width: 100% !important; + } +} diff --git a/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.html b/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.html index 6f98de5..5d9cb72 100644 --- a/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.html +++ b/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.html @@ -2,7 +2,7 @@
-

+

{{ 'CHECKS.TITLE' | translate }}

@@ -19,42 +19,108 @@
{{ 'CHECKS.NO_DATA_HINT' | translate }}
- -
-
-
-
- - {{ 'ITEMS.DATA' | translate }} - - -
-
- - {{ 'ITEMS.FILTER.CAPTION' | translate }} - - - -
+ +
+
+
+
+ {{ 'ITEMS.FILTER.CAPTION' | translate }} {{ 'CHECKS.TITLE' | translate }} +
+ + {{ dayExpensesDate | date:'EEE, MMM dd, yyyy':'':currentLocale | titlecase }} + + + {{ dayExpensesLocation }} + +
+ +
+
+
+ + {{ 'ITEMS.FILTER.CAPTION' | translate }} + + + +
+ +
+
+ +
+ + {{ 'ITEMS.DATA' | translate }} + + +
+ + +
+ + {{ 'ITEMS.FILTER.CAPTION' | translate }} + + + +
+
+
+ + +
+ +
+ + +
+ + {{ 'ITEMS.FILTER.CAPTION' | translate }} & {{ 'ITEMS.SORT.CAPTION' | translate }} + +
+ + + +
+
+

{{ 'CHECKS.NO_SEARCH_RESULTS' | translate }}

- -
+ +
@@ -101,10 +167,10 @@

{{ 'CHECKS.NO_SEARCH_RESULTS' | translate }}

- +
{{ check.location || '-' }} {{ check.payer || '-' }}{{ check.totalSum | currency:'UAH':'₴' }}{{ check.totalSum | currency:('GENERAL.CURRENCY_CODE' | translate):('GENERAL.CURRENCY_SYMBOL' | translate) }}
-
+ +
+ + {{ 'ITEMS.DATA' | translate }} + +
+ + +
+
+

+ +

+
+
+
+ +
+ + {{ 'CHECKS.PAYER' | translate }}: + + + {{ check.payer || '-' }} + +
+ + +
+ + +
+ + +
+ + +
+
+
+
+
+
+

@@ -150,106 +298,4 @@

{{ 'CHECKS.NO_SEARCH_RESULTS' | translate }}


-
- - - - - - \ No newline at end of file +
\ No newline at end of file diff --git a/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.ts b/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.ts index 1824f4d..dd84bc9 100644 --- a/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.ts +++ b/src/ExpensesCalculator.UI/src/app/checks/check-list/check-list.component.ts @@ -1,29 +1,31 @@ -import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, ChangeDetectorRef, inject } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges, ChangeDetectorRef } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { TranslatePipe, TranslateService } from '@ngx-translate/core'; -import { ModalWindowComponent } from '../../shared/modal-window/modal-window.component'; -import { ValidationErrors, parseValidationErrors } from '../../shared/models/validation-errors.model'; -import { ChecksService, Check, DeleteCheckResponse } from '../../services/checks.service'; +import { ChecksService, Check } from '../../services/checks.service'; import { ItemListComponent } from '../../items/item-list/item-list.component'; import { ItemsService, Item } from '../../services/items.service'; -import { DayExpensesTotalSumUpdateService } from '../../services/day-expenses-total-sum-update.service'; -import { ToastService } from '../../services/toast.service'; -import { FormValidationService } from '../../services/form-validation.service'; +import { ModalService } from '../../services/modal.service'; import { FilterBarComponent, FilterOption } from '../../shared/filter-bar/filter-bar.component'; +import { SortBarComponent, SortOption } from '../../shared/sort-bar/sort-bar.component'; import { TourAnchorNgBootstrapDirective } from 'ngx-ui-tour-ng-bootstrap'; +import { CheckAddFormComponent } from '../../modals/check-form/check-add-form.component'; +import { CheckEditFormComponent } from '../../modals/check-form/check-edit-form.component'; +import { CheckDeleteFormComponent } from '../../modals/check-form/check-delete-form.component'; declare var bootstrap: any; @Component({ selector: 'app-check-list', standalone: true, - imports: [CommonModule, FormsModule, ModalWindowComponent, TranslatePipe, ItemListComponent, FilterBarComponent, TourAnchorNgBootstrapDirective], + imports: [CommonModule, TranslatePipe, ItemListComponent, FilterBarComponent, SortBarComponent, TourAnchorNgBootstrapDirective], templateUrl: './check-list.component.html', styleUrl: './check-list.component.css' }) export class CheckListComponent implements OnInit, OnChanges { @Input() dayExpensesId!: string; + @Input() dayExpensesLocation?: string; + @Input() dayExpensesDate?: Date; + @Input() currentLocale: string = 'en'; @Input() participants: string[] = []; @Input() checks?: Check[]; // Optional: if provided, use these instead of loading @Input() scrollToCheckId?: string; @@ -37,21 +39,6 @@ export class CheckListComponent implements OnInit, OnChanges { checkItemsMap: Map = new Map(); // Store items per check checkLoadingMap: Map = new Map(); // Track loading state per check - // Modal properties - modalInstance: any; - currentModalContent: 'add' | 'edit' | 'delete' = 'add'; - modalTitle: string = ''; - - // Form properties - id = ''; - location = ''; - payer = ''; - currentCheckTotalSum = 0; - - // Validation properties - formErrors: ValidationErrors = {}; - formValidated = false; - // Filter and sort properties filterText = ''; filterCriteria: string = 'Location'; @@ -62,6 +49,11 @@ export class CheckListComponent implements OnInit, OnChanges { ]; sortColumn: 'location' | 'totalSum' | 'payer' = 'totalSum'; sortOrder: 'asc' | 'desc' = 'desc'; + sortOptions: SortOption[] = [ + { value: 'location', labelKey: 'CHECKS.LOCATION' }, + { value: 'payer', labelKey: 'CHECKS.PAYER' }, + { value: 'totalSum', labelKey: 'CHECKS.SUM' } + ]; // UI state properties isLoading = false; @@ -73,16 +65,12 @@ export class CheckListComponent implements OnInit, OnChanges { private shouldScrollToItem = false; highlightedItemId?: string; - // Inject services using inject() for better SSR compatibility - private dayExpensesTotalSumUpdateService = inject(DayExpensesTotalSumUpdateService); - constructor( private checksService: ChecksService, private itemsService: ItemsService, private translate: TranslateService, private cdr: ChangeDetectorRef, - private toastService: ToastService, - private formValidationService: FormValidationService + private modalService: ModalService ) {} ngOnInit(): void { @@ -172,6 +160,12 @@ export class CheckListComponent implements OnInit, OnChanges { this.applyLocalSorting(); } + onSortChange(event: { column: string; order: 'asc' | 'desc' }): void { + this.sortColumn = event.column as 'location' | 'totalSum' | 'payer'; + this.sortOrder = event.order; + this.applyLocalSorting(); + } + applyLocalFiltering(): void { const searchTerm = this.filterText.toLowerCase().trim(); @@ -254,7 +248,10 @@ export class CheckListComponent implements OnInit, OnChanges { } loadItemsForCheck(checkId: string): void { - // Set loading state + if (this.checkLoadingMap.get(checkId)) { + return; + } + this.checkLoadingMap.set(checkId, true); this.itemsService.getAllCheckItems(checkId).subscribe({ @@ -312,187 +309,63 @@ export class CheckListComponent implements OnInit, OnChanges { // Modal management methods openModal(type: 'add' | 'edit' | 'delete', id: string = ''): void { - this.currentModalContent = type; - this.modalTitle = this.translate.instant(`CHECKS.MODAL.${type.toUpperCase()}_TITLE`); - - const modalElement = document.getElementById('checksModal'); - if (!modalElement) return; - if (type === 'add') { - this.clearFormData(); - } else if (id) { - // Load check data from local array (no server call needed) - const check = this.checksList.find(c => c.id === id); - if (check) { - this.id = check.id; - this.location = check.location; - this.payer = check.payer; - this.currentCheckTotalSum = check.totalSum; - } + this.modalService.open( + CheckAddFormComponent, + this.translate.instant('CHECKS.MODAL.ADD_TITLE'), + { + participants: this.participants, + dayExpensesId: this.dayExpensesId, + onSuccess: () => this.refreshChecks() + }, + 'md' + ); + return; } - if (!this.modalInstance) { - this.modalInstance = new bootstrap.Modal(modalElement, { - backdrop: 'static', - keyboard: false - }); + if (type === 'edit') { + const check = this.checksList.find(c => c.id === id); + if (!check) return; + + this.modalService.open( + CheckEditFormComponent, + this.translate.instant('CHECKS.MODAL.EDIT_TITLE'), + { + participants: this.participants, + checkId: check.id, + location: check.location, + payer: check.payer, + onSuccess: () => this.refreshChecks() + }, + 'md' + ); + return; } - this.formErrors = {}; - this.formValidated = false; - - this.modalInstance.show(); - } - - hideModal(): void { - if (this.modalInstance) { - this.modalInstance.hide(); - this.formErrors = {}; - this.formValidated = false; - this.clearFormData(); + if (type === 'delete') { + const check = this.checksList.find(c => c.id === id); + if (!check) return; + + this.modalService.open( + CheckDeleteFormComponent, + this.translate.instant('CHECKS.MODAL.DELETE_TITLE'), + { + checkId: check.id, + dayExpensesId: this.dayExpensesId, + location: check.location, + payer: check.payer, + totalSum: check.totalSum, + onSuccess: () => this.refreshChecks() + }, + 'md' + ); + return; } } - clearFormData(): void { - this.id = ''; - this.location = ''; - this.payer = ''; - this.currentCheckTotalSum = 0; - } - - // CRUD operations - validateCheckForm(): boolean { - this.formErrors = {}; - this.formValidated = true; - - this.formErrors = this.formValidationService.validateCheckForm(this.location, this.payer); - - return !this.formValidationService.hasErrors(this.formErrors); - } - - createCheck(): void { - if (!this.validateCheckForm()) return; - this.formValidated = true; - - this.checksService.createCheck(this.location, this.payer, this.dayExpensesId).subscribe({ - next: (createdCheck) => { - // Add the new check to the list - this.checksList.push(createdCheck); - this.applyLocalFiltering(); - - // Emit event to parent to re-initialize tour with updated step count - this.checksLoaded.emit(); - - this.hideModal(); - this.toastService.success( - this.translate.instant('CHECKS.TOAST.SUCCESS'), - this.translate.instant('CHECKS.TOAST.CREATE_SUCCESS') - ); - }, - error: error => { - this.formErrors = parseValidationErrors(error); - this.formValidated = true; - if (Object.keys(this.formErrors).length === 0 || this.formErrors['general']) { - const errorMessage = this.formErrors['general'] || error?.error?.message || error?.message || this.translate.instant('CHECKS.TOAST.CREATE_ERROR'); - this.toastService.error( - this.translate.instant('CHECKS.TOAST.ERROR'), - this.translateBackendError(errorMessage) - ); - } - } - }); - } - - editCheck(): void { - if (!this.validateCheckForm()) return; - this.formValidated = true; - - this.checksService.editCheck(this.id, this.location, this.payer).subscribe({ - next: (updatedCheck) => { - // Update the check in the local list - const index = this.checksList.findIndex(c => c.id === this.id); - if (index !== -1) { - this.checksList[index] = updatedCheck; - this.applyLocalFiltering(); - } - - this.hideModal(); - this.toastService.success( - this.translate.instant('CHECKS.TOAST.SUCCESS'), - this.translate.instant('CHECKS.TOAST.EDIT_SUCCESS') - ); - }, - error: error => { - this.formErrors = parseValidationErrors(error); - this.formValidated = true; - if (Object.keys(this.formErrors).length === 0 || this.formErrors['general']) { - const errorMessage = this.formErrors['general'] || error?.error?.message || error?.message || this.translate.instant('CHECKS.TOAST.EDIT_ERROR'); - this.toastService.error( - this.translate.instant('CHECKS.TOAST.ERROR'), - this.translateBackendError(errorMessage) - ); - } - } - }); - } - - deleteCheck(): void { - this.checksService.deleteCheck(this.id).subscribe({ - next: (response: DeleteCheckResponse) => { - // Remove the check from the local list - const index = this.checksList.findIndex(c => c.id === this.id); - if (index !== -1) { - this.checksList.splice(index, 1); - // Also remove cached items for this check - this.checkItemsMap.delete(this.id); - this.checkLoadingMap.delete(this.id); - this.applyLocalFiltering(); - - // Emit day expenses total sum from backend response - this.dayExpensesTotalSumUpdateService.emitDayExpensesTotalSumUpdate(this.dayExpensesId, response.dayExpensesTotalSum); - - // Emit event to parent to re-initialize tour with updated step count - this.checksLoaded.emit(); - } - - this.hideModal(); - this.toastService.success( - this.translate.instant('CHECKS.TOAST.SUCCESS'), - this.translate.instant('CHECKS.TOAST.DELETE_SUCCESS') - ); - }, - error: error => { - console.log(error); - const errorMessage = error?.error?.message || error?.message || this.translate.instant('CHECKS.TOAST.DELETE_ERROR'); - this.toastService.error( - this.translate.instant('CHECKS.TOAST.ERROR'), - this.translateBackendError(errorMessage) - ); - } - }); - } - - // Helper methods - translateBackendError(errorMessage: string): string { - if (!errorMessage) return ''; - - const errorMap: Record = { - 'Invalid data': 'CHECKS.BACKEND_ERRORS.INVALID_DATA', - 'Unauthorized': 'CHECKS.BACKEND_ERRORS.UNAUTHORIZED' - }; - - const translationKey = errorMap[errorMessage]; - if (translationKey) { - return this.translate.instant(translationKey); - } - - for (const [key, value] of Object.entries(errorMap)) { - if (errorMessage.toLowerCase().includes(key.toLowerCase())) { - return this.translate.instant(value); - } - } - - return errorMessage; + refreshChecks(): void { + this.loadChecks(); + this.checksLoaded.emit(); } getIconOrderClass(column: string): string { diff --git a/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.css b/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.css index c1a6fe4..9c3f585 100644 --- a/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.css +++ b/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.css @@ -35,3 +35,45 @@ background-color: #1f429d !important; color: white; } + +/* Component-specific responsive styles */ +@media (max-width: 576px) { + /* Back button sizing */ + #backButton { + padding: 0.25rem 0.5rem !important; + } + + #backButton i { + transform: scaleX(1.2) !important; + } + + /* Reduce title size */ + h4 { + font-size: 0.9rem !important; + } + + /* Reduce no data text size for small screens */ + .no-data-section h2 { + font-size: 1.25rem !important; + } + + .no-data-section h5 { + font-size: 0.85rem !important; + } + + /* Reduce tab button text size */ + .tab-btn { + font-size: 0.75rem !important; + padding: 0.5rem 0.75rem !important; + } + + /* Reduce content text sizes */ + .tab-content { + font-size: 0.85rem !important; + } + + /* Reduce icon sizes */ + .bi { + font-size: 0.9rem !important; + } +} diff --git a/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.html b/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.html index 94a1be7..4cc8214 100644 --- a/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.html +++ b/src/ExpensesCalculator.UI/src/app/dayExpenses/day-expenses-calculations/day-expenses-calculations.component.html @@ -1,14 +1,14 @@ -
+
-

- {{ 'CALCULATIONS.TITLE' | translate }} + {{ 'CALCULATIONS.TITLE' | translate }}

@@ -21,7 +21,7 @@

+ class="no-data-section text-white text-center pt-4 pb-5">

{{ 'CALCULATIONS.NO_DATA' | translate }}

{{ 'CALCULATIONS.NO_DATA_HINT' | translate }}
@@ -58,18 +58,18 @@

{{ 'CALCULATIONS.NO_DATA_HINT' | translate }}
- + [attr.data-bs-title]="getCheckTooltip(checkCalc.check.totalSum, checkCalc.check.payer)"> + {{ checkCalc.check.location }}
-
    +
    • {{ itemCalc.item.name }}: - {{ itemCalc.item.price * itemCalc.item.amount | currency:'UAH':'₴' }} + {{ itemCalc.item.price * itemCalc.item.amount | currency:('GENERAL.CURRENCY_CODE' | translate):('GENERAL.CURRENCY_SYMBOL' | translate) }} / {{ 'CALCULATIONS.NO_DATA_HINT' | translate }} [attr.data-bs-title]="getItemUsersTooltip(itemCalc.item.users)"> {{ itemCalc.item.users.length }} - = - {{ itemCalc.pricePerUser | currency:'UAH':'₴' }} + = + {{ itemCalc.pricePerUser | currency:('GENERAL.CURRENCY_CODE' | translate):('GENERAL.CURRENCY_SYMBOL' | translate) }}
    @@ -86,7 +86,7 @@
    {{ 'CALCULATIONS.NO_DATA_HINT' | translate }}
    {{ 'CALCULATIONS.SUM_PER_PARTICIPANT' | translate }}: - {{ checkCalc.sumPerParticipant | currency:'UAH':'₴' }} + {{ checkCalc.sumPerParticipant | currency:('GENERAL.CURRENCY_CODE' | translate):('GENERAL.CURRENCY_SYMBOL' | translate) }}

    @@ -97,7 +97,7 @@
    {{ 'CALCULATIONS.NO_DATA_HINT' | translate }}
    {{ 'CALCULATIONS.TOTAL_EXPENSES' | translate }}: - {{ getTotalForUser(participant) | currency:'UAH':'₴' }} + {{ getTotalForUser(participant) | currency:('GENERAL.CURRENCY_CODE' | translate):('GENERAL.CURRENCY_SYMBOL' | translate) }}
    @@ -112,10 +112,10 @@
    {{ 'CALCULATIONS.NO_DATA_HINT' | translate }}
    {{ 'CALCULATIONS.TRANSACTION_LIST' | translate }}:
-
    +
    • {{ transaction.subjects.sender }} - ({{ transaction.transferAmount | currency:'UAH':'₴' + ({{ transaction.transferAmount | currency:('GENERAL.CURRENCY_CODE' | translate):('GENERAL.CURRENCY_SYMBOL' | translate) }}) {{ transaction.subjects.recipient }} @@ -140,11 +140,11 @@
      {{ 'CALCULATIONS.NO_DATA_HINT' | translate }}
-