Line data Source code
1 : // Copyright 2025 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/controller/iterator_controller.dart';
5 : import 'package:app_finance/_classes/controller/focus_controller.dart';
6 : import 'package:app_finance/_classes/herald/app_design.dart';
7 : import 'package:app_finance/_classes/herald/app_locale.dart';
8 : import 'package:app_finance/_classes/storage/app_data.dart';
9 : import 'package:app_finance/_classes/structure/invoice_app_data.dart';
10 : import 'package:app_finance/_configs/account_type.dart';
11 : import 'package:app_finance/_configs/screen_helper.dart';
12 : import 'package:app_finance/_configs/theme_helper.dart';
13 : import 'package:app_finance/_ext/build_context_ext.dart';
14 : import 'package:app_finance/design/form/date_range_input.dart';
15 : import 'package:app_finance/design/form/list_account_selector.dart';
16 : import 'package:app_finance/design/form/simple_input.dart';
17 : import 'package:app_finance/design/wrapper/input_wrapper.dart';
18 : import 'package:app_finance/design/wrapper/row_widget.dart';
19 : import 'package:app_finance/design/wrapper/single_scroll_wrapper.dart';
20 : import 'package:app_finance/design/wrapper/text_wrapper.dart';
21 : import 'package:app_finance/pages/invoice/invoice_page.dart';
22 : import 'package:material_ui/material_ui.dart';
23 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
24 :
25 : class InvoiceSearchPage extends StatefulWidget {
26 0 : const InvoiceSearchPage({super.key});
27 :
28 0 : @override
29 0 : InvoiceSearchPageState createState() => InvoiceSearchPageState();
30 : }
31 :
32 : class InvoiceSearchPageState extends InvoicePageState<InvoiceSearchPage> {
33 : String? account;
34 : String? type;
35 : Currency? currency;
36 : DateTime? startDate;
37 : DateTime? endDate;
38 : late TextEditingController description;
39 : late TextEditingController amountFrom;
40 : late TextEditingController amountTo;
41 : late FocusController focus;
42 :
43 : late List<ListAccountSelectorItem> accountList =
44 : state.getList(AppDataType.accounts).map((item) => ListAccountSelectorItem(item: item)).toList();
45 :
46 0 : @override
47 : void initState() {
48 0 : description = TextEditingController();
49 0 : amountFrom = TextEditingController();
50 0 : amountTo = TextEditingController();
51 0 : focus = FocusController();
52 0 : super.initState();
53 : }
54 :
55 0 : @override
56 : dispose() {
57 0 : description.dispose();
58 0 : amountFrom.dispose();
59 0 : amountTo.dispose();
60 0 : focus.dispose();
61 0 : super.dispose();
62 : }
63 :
64 0 : @override
65 0 : String getTitle() => AppLocale.labels.searchTooltip;
66 :
67 0 : @override
68 0 : String getButtonName() => AppLocale.labels.searchTooltip;
69 :
70 0 : @override
71 : Widget buildButton(BuildContext context, BoxConstraints constraints) {
72 0 : return FloatingActionButton(
73 : heroTag: 'invoice_search_page',
74 0 : onPressed: () => setState(clearState),
75 0 : tooltip: getButtonName(),
76 : child: const Icon(Icons.search),
77 : );
78 : }
79 :
80 0 : @override
81 : bool getContentFilter(InvoiceAppData o) {
82 0 : final descriptionMatch = o.title.toLowerCase().contains(description.text.toLowerCase());
83 0 : final accountMatch = account == null || o.account == account;
84 0 : final typeMatch = type == null || state.getByUuid(o.account)?.type == type;
85 0 : final currencyMatch = currency == null || o.currency == currency;
86 0 : final startDateMatch = startDate == null || o.createdAt.isAfter(startDate!);
87 0 : final endDateMatch = endDate == null || o.createdAt.isBefore(endDate!);
88 0 : final amountFromMatch = amountFrom.text.isEmpty || o.details >= double.tryParse(amountFrom.text)!;
89 0 : final amountToMatch = amountTo.text.isEmpty || o.details <= double.tryParse(amountTo.text)!;
90 :
91 : return !(descriptionMatch &&
92 : accountMatch &&
93 : typeMatch &&
94 : currencyMatch &&
95 : startDateMatch &&
96 : endDateMatch &&
97 : amountFromMatch &&
98 : amountToMatch) ||
99 0 : o.accountFrom != null;
100 : }
101 :
102 0 : @override
103 : InterfaceIterator getContentStream() =>
104 0 : state.getStream<InvoiceAppData>(AppDataType.invoice, filter: getContentFilter);
105 :
106 0 : @override
107 : Widget addHeaderWidget() {
108 0 : final width = ScreenHelper.state().width - ThemeHelper.getIndent(4);
109 0 : final indent = ThemeHelper.getIndent(2);
110 0 : return SliverToBoxAdapter(
111 0 : child: SingleScrollWrapper(
112 0 : controller: focus,
113 0 : child: Column(
114 0 : crossAxisAlignment: AppDesign.getAlignment(),
115 0 : children: [
116 0 : InputWrapper.text(
117 0 : title: AppLocale.labels.description,
118 0 : controller: description,
119 0 : tooltip: AppLocale.labels.descriptionTooltip,
120 : ),
121 0 : RowWidget(
122 : indent: indent,
123 0 : maxWidth: width + indent,
124 : chunk: const [0.5, 0.5],
125 0 : children: [
126 0 : [
127 0 : InputWrapper.text(
128 0 : title: AppLocale.labels.amountFrom,
129 0 : tooltip: AppLocale.labels.billSetTooltip,
130 0 : controller: amountFrom,
131 : inputType: const TextInputType.numberWithOptions(decimal: true),
132 0 : formatter: [
133 0 : SimpleInputFormatter.filterDouble,
134 : ],
135 : ),
136 : ],
137 0 : [
138 0 : InputWrapper.text(
139 0 : title: AppLocale.labels.amountTo,
140 0 : tooltip: AppLocale.labels.billSetTooltip,
141 0 : controller: amountTo,
142 : inputType: const TextInputType.numberWithOptions(decimal: true),
143 0 : formatter: [
144 0 : SimpleInputFormatter.filterDouble,
145 : ],
146 : ),
147 : ],
148 : ],
149 : ),
150 0 : InputWrapper.select(
151 0 : value: type,
152 0 : title: AppLocale.labels.accountType,
153 0 : tooltip: AppLocale.labels.accountTypeTooltip,
154 0 : options: AccountType.getList(),
155 0 : onChange: (value) => setState(() {
156 0 : if (value != type) clearState();
157 0 : type = value;
158 : }),
159 : ),
160 0 : InputWrapper(
161 : type: NamedInputType.accountSelector,
162 0 : value: account != null ? state.getByUuid(account!) : null,
163 0 : title: AppLocale.labels.account,
164 0 : tooltip: AppLocale.labels.titleAccountTooltip,
165 0 : state: state,
166 0 : options: accountList,
167 0 : onChange: (value) => setState(() {
168 0 : if (value?.uuid != account) clearState();
169 0 : account = value?.uuid;
170 : }),
171 : width: width,
172 : ),
173 0 : InputWrapper.currency(
174 0 : value: currency,
175 0 : title: AppLocale.labels.currency,
176 0 : tooltip: AppLocale.labels.currencyTooltip,
177 0 : onChange: (value) => setState(() {
178 0 : if (value != currency) clearState();
179 0 : currency = value;
180 : }),
181 : ),
182 0 : TextWrapper(
183 0 : AppLocale.labels.dateRange,
184 0 : style: context.textTheme.bodyLarge,
185 : ),
186 0 : DateRangeInput(
187 0 : valueFrom: startDate,
188 0 : value: endDate,
189 0 : setState: (start, end) => setState(() {
190 0 : startDate = start;
191 0 : endDate = end;
192 0 : clearState();
193 : }),
194 : ),
195 : ],
196 : ),
197 : ),
198 : );
199 : }
200 : }
|