|
|
@@ -34,12 +34,26 @@ class ImportableItem {
|
|
|
bool selected = false;
|
|
|
|
|
|
ImportableItem({
|
|
|
- required this.aeNo, required this.aeDd, required this.reason,
|
|
|
- required this.headAmtnYj, required this.itm, required this.sqMan, required this.sqName,
|
|
|
- required this.typeNo, required this.typeName, required this.amtnYj,
|
|
|
- required this.accNo, required this.accName, required this.dep,
|
|
|
- required this.depName, required this.objNo, required this.objName,
|
|
|
- required this.startDd, required this.endDd, required this.priority, required this.rem,
|
|
|
+ required this.aeNo,
|
|
|
+ required this.aeDd,
|
|
|
+ required this.reason,
|
|
|
+ required this.headAmtnYj,
|
|
|
+ required this.itm,
|
|
|
+ required this.sqMan,
|
|
|
+ required this.sqName,
|
|
|
+ required this.typeNo,
|
|
|
+ required this.typeName,
|
|
|
+ required this.amtnYj,
|
|
|
+ required this.accNo,
|
|
|
+ required this.accName,
|
|
|
+ required this.dep,
|
|
|
+ required this.depName,
|
|
|
+ required this.objNo,
|
|
|
+ required this.objName,
|
|
|
+ required this.startDd,
|
|
|
+ required this.endDd,
|
|
|
+ required this.priority,
|
|
|
+ required this.rem,
|
|
|
});
|
|
|
|
|
|
factory ImportableItem.fromJson(Map<String, dynamic> json) => ImportableItem(
|
|
|
@@ -80,7 +94,8 @@ class ExpenseApplyImportPage extends ConsumerStatefulWidget {
|
|
|
const ExpenseApplyImportPage({super.key});
|
|
|
|
|
|
@override
|
|
|
- ConsumerState<ExpenseApplyImportPage> createState() => _ExpenseApplyImportPageState();
|
|
|
+ ConsumerState<ExpenseApplyImportPage> createState() =>
|
|
|
+ _ExpenseApplyImportPageState();
|
|
|
}
|
|
|
|
|
|
class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
@@ -102,8 +117,10 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
final now = DateTime.now();
|
|
|
- _startDateCtrl.text = '${now.year}-${now.month.toString().padLeft(2, '0')}-01';
|
|
|
- _endDateCtrl.text = '${now.year}-${now.month.toString().padLeft(2, '0')}-${DateTime(now.year, now.month + 1, 0).day.toString().padLeft(2, '0')}';
|
|
|
+ _startDateCtrl.text =
|
|
|
+ '${now.year}-${now.month.toString().padLeft(2, '0')}-01';
|
|
|
+ _endDateCtrl.text =
|
|
|
+ '${now.year}-${now.month.toString().padLeft(2, '0')}-${DateTime(now.year, now.month + 1, 0).day.toString().padLeft(2, '0')}';
|
|
|
_scrollCtrl = ScrollController()..addListener(_onScroll);
|
|
|
_refreshCtrl = EasyRefreshController();
|
|
|
WidgetsBinding.instance.addObserver(this);
|
|
|
@@ -113,7 +130,9 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
@override
|
|
|
void dispose() {
|
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
|
- _aeNoCtrl.dispose(); _startDateCtrl.dispose(); _endDateCtrl.dispose();
|
|
|
+ _aeNoCtrl.dispose();
|
|
|
+ _startDateCtrl.dispose();
|
|
|
+ _endDateCtrl.dispose();
|
|
|
_scrollCtrl.dispose();
|
|
|
_refreshCtrl.dispose();
|
|
|
super.dispose();
|
|
|
@@ -125,15 +144,24 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
FocusScope.of(context).unfocus();
|
|
|
_aeNoCtrl.clear();
|
|
|
final now = DateTime.now();
|
|
|
- _startDateCtrl.text = '${now.year}-${now.month.toString().padLeft(2, '0')}-01';
|
|
|
- _endDateCtrl.text = '${now.year}-${now.month.toString().padLeft(2, '0')}-${DateTime(now.year, now.month + 1, 0).day.toString().padLeft(2, '0')}';
|
|
|
- _items = []; _page = 1; _hasMore = true; _initialLoaded = false; _generation++;
|
|
|
+ _startDateCtrl.text =
|
|
|
+ '${now.year}-${now.month.toString().padLeft(2, '0')}-01';
|
|
|
+ _endDateCtrl.text =
|
|
|
+ '${now.year}-${now.month.toString().padLeft(2, '0')}-${DateTime(now.year, now.month + 1, 0).day.toString().padLeft(2, '0')}';
|
|
|
+ _items = [];
|
|
|
+ _page = 1;
|
|
|
+ _hasMore = true;
|
|
|
+ _initialLoaded = false;
|
|
|
+ _generation++;
|
|
|
_load();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void _onScroll() {
|
|
|
- if (!_loading && _hasMore && _scrollCtrl.position.pixels >= _scrollCtrl.position.maxScrollExtent - 200) {
|
|
|
+ if (!_loading &&
|
|
|
+ _hasMore &&
|
|
|
+ _scrollCtrl.position.pixels >=
|
|
|
+ _scrollCtrl.position.maxScrollExtent - 200) {
|
|
|
_load(append: true);
|
|
|
}
|
|
|
}
|
|
|
@@ -146,7 +174,7 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
setState(() => _loading = true);
|
|
|
try {
|
|
|
final api = ref.read(expenseApiProvider);
|
|
|
- final result = await api.getImportableExpenseApplies(
|
|
|
+ final result = await api.getImportableExpenseApplyList(
|
|
|
keyword: _aeNoCtrl.text.trim(),
|
|
|
startDate: _startDateCtrl.text,
|
|
|
endDate: _endDateCtrl.text,
|
|
|
@@ -155,11 +183,19 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
);
|
|
|
if (!mounted) return;
|
|
|
if (gen != _generation) return;
|
|
|
- final list = (result['list'] as List<dynamic>?)
|
|
|
- ?.map((e) => ImportableItem.fromJson(e as Map<String, dynamic>))
|
|
|
- .toList() ?? [];
|
|
|
+ final list =
|
|
|
+ (result['list'] as List<dynamic>?)
|
|
|
+ ?.map((e) => ImportableItem.fromJson(e as Map<String, dynamic>))
|
|
|
+ .toList() ??
|
|
|
+ [];
|
|
|
setState(() {
|
|
|
- if (append) { _items.addAll(list); _page++; } else { _items = list; _page = 2; }
|
|
|
+ if (append) {
|
|
|
+ _items.addAll(list);
|
|
|
+ _page++;
|
|
|
+ } else {
|
|
|
+ _items = list;
|
|
|
+ _page = 2;
|
|
|
+ }
|
|
|
_loading = false;
|
|
|
_initialLoaded = true;
|
|
|
_hasMore = list.length >= 20;
|
|
|
@@ -171,8 +207,13 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
|
|
|
void _search() {
|
|
|
FocusScope.of(context).unfocus();
|
|
|
- if (_startDateCtrl.text.isNotEmpty && _endDateCtrl.text.isNotEmpty && _startDateCtrl.text.compareTo(_endDateCtrl.text) > 0) {
|
|
|
- TDToast.showText(AppLocalizations.of(context).get('filterDateStartAfterEnd'), context: context);
|
|
|
+ if (_startDateCtrl.text.isNotEmpty &&
|
|
|
+ _endDateCtrl.text.isNotEmpty &&
|
|
|
+ _startDateCtrl.text.compareTo(_endDateCtrl.text) > 0) {
|
|
|
+ TDToast.showText(
|
|
|
+ AppLocalizations.of(context).get('filterDateStartAfterEnd'),
|
|
|
+ context: context,
|
|
|
+ );
|
|
|
return;
|
|
|
}
|
|
|
_refreshCtrl.callRefresh();
|
|
|
@@ -194,7 +235,9 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
final items = _items.where((e) => e.aeNo == aeNo).toList();
|
|
|
final allSelected = items.every((e) => e.selected);
|
|
|
final newVal = !allSelected;
|
|
|
- for (final e in items) { e.selected = newVal; }
|
|
|
+ for (final e in items) {
|
|
|
+ e.selected = newVal;
|
|
|
+ }
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -227,13 +270,19 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
context,
|
|
|
title: l10n.get('selectDate'),
|
|
|
backgroundColor: colors.bgCard,
|
|
|
- useYear: true, useMonth: true, useDay: true,
|
|
|
- useHour: false, useMinute: false, useSecond: false, useWeekDay: false,
|
|
|
+ useYear: true,
|
|
|
+ useMonth: true,
|
|
|
+ useDay: true,
|
|
|
+ useHour: false,
|
|
|
+ useMinute: false,
|
|
|
+ useSecond: false,
|
|
|
+ useWeekDay: false,
|
|
|
dateStart: const [2020, 1, 1],
|
|
|
dateEnd: [now.year + 1, 12, 31],
|
|
|
initialDate: [now.year, now.month, now.day],
|
|
|
onConfirm: (selected) {
|
|
|
- final d = '${selected['year']}-${selected['month'].toString().padLeft(2, '0')}-${selected['day'].toString().padLeft(2, '0')}';
|
|
|
+ final d =
|
|
|
+ '${selected['year']}-${selected['month'].toString().padLeft(2, '0')}-${selected['day'].toString().padLeft(2, '0')}';
|
|
|
if (_validateDateRange(ctrl, d)) {
|
|
|
ctrl.text = d;
|
|
|
setState(() {});
|
|
|
@@ -247,7 +296,10 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
final start = changed == _startDateCtrl ? newValue : _startDateCtrl.text;
|
|
|
final end = changed == _endDateCtrl ? newValue : _endDateCtrl.text;
|
|
|
if (start.isNotEmpty && end.isNotEmpty && start.compareTo(end) > 0) {
|
|
|
- TDToast.showText(AppLocalizations.of(context).get('filterDateStartAfterEnd'), context: context);
|
|
|
+ TDToast.showText(
|
|
|
+ AppLocalizations.of(context).get('filterDateStartAfterEnd'),
|
|
|
+ context: context,
|
|
|
+ );
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
@@ -265,85 +317,143 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
children: [
|
|
|
Padding(
|
|
|
padding: const EdgeInsets.fromLTRB(12, 8, 12, 0),
|
|
|
- child: Row(children: [
|
|
|
- Expanded(
|
|
|
- child: GestureDetector(
|
|
|
- onTap: () => _pickDate(_startDateCtrl),
|
|
|
- child: _dateChip(_startDateCtrl, l10n.get('filterStartDate'), tdTheme, colors),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: GestureDetector(
|
|
|
+ onTap: () => _pickDate(_startDateCtrl),
|
|
|
+ child: _dateChip(
|
|
|
+ _startDateCtrl,
|
|
|
+ l10n.get('filterStartDate'),
|
|
|
+ tdTheme,
|
|
|
+ colors,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- Text('—', style: TextStyle(fontSize: 14, color: colors.textSecondary)),
|
|
|
- const SizedBox(width: 8),
|
|
|
- Expanded(
|
|
|
- child: GestureDetector(
|
|
|
- onTap: () => _pickDate(_endDateCtrl),
|
|
|
- child: _dateChip(_endDateCtrl, l10n.get('filterEndDate'), tdTheme, colors),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Text(
|
|
|
+ '—',
|
|
|
+ style: TextStyle(fontSize: 14, color: colors.textSecondary),
|
|
|
),
|
|
|
- ),
|
|
|
- ]),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Expanded(
|
|
|
+ child: GestureDetector(
|
|
|
+ onTap: () => _pickDate(_endDateCtrl),
|
|
|
+ child: _dateChip(
|
|
|
+ _endDateCtrl,
|
|
|
+ l10n.get('filterEndDate'),
|
|
|
+ tdTheme,
|
|
|
+ colors,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
),
|
|
|
const SizedBox(height: 8),
|
|
|
Padding(
|
|
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
|
|
|
- child: Row(children: [
|
|
|
- Expanded(
|
|
|
- child: Container(
|
|
|
- height: 40,
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
|
- decoration: BoxDecoration(
|
|
|
- color: colors.bgSecondaryContainer,
|
|
|
- borderRadius: BorderRadius.circular(20),
|
|
|
- border: Border.all(color: tdTheme.componentStrokeColor),
|
|
|
- ),
|
|
|
- child: Row(children: [
|
|
|
- Expanded(
|
|
|
- child: TextField(
|
|
|
- controller: _aeNoCtrl,
|
|
|
- style: TextStyle(fontSize: 14, color: colors.textPrimary),
|
|
|
- decoration: InputDecoration(
|
|
|
- hintText: l10n.get('searchImportHint'),
|
|
|
- hintStyle: TextStyle(fontSize: 14, color: colors.textSecondary),
|
|
|
- border: InputBorder.none,
|
|
|
- isCollapsed: true,
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: Container(
|
|
|
+ height: 40,
|
|
|
+ padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: colors.bgSecondaryContainer,
|
|
|
+ borderRadius: BorderRadius.circular(20),
|
|
|
+ border: Border.all(color: tdTheme.componentStrokeColor),
|
|
|
+ ),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: TextField(
|
|
|
+ controller: _aeNoCtrl,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 14,
|
|
|
+ color: colors.textPrimary,
|
|
|
+ ),
|
|
|
+ decoration: InputDecoration(
|
|
|
+ hintText: l10n.get('searchImportHint'),
|
|
|
+ hintStyle: TextStyle(
|
|
|
+ fontSize: 14,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ border: InputBorder.none,
|
|
|
+ isCollapsed: true,
|
|
|
+ ),
|
|
|
+ onChanged: (_) => setState(() {}),
|
|
|
+ ),
|
|
|
),
|
|
|
- onChanged: (_) => setState(() {}),
|
|
|
- ),
|
|
|
+ if (_aeNoCtrl.text.isNotEmpty)
|
|
|
+ GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ _aeNoCtrl.clear();
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ child: Icon(
|
|
|
+ Icons.close,
|
|
|
+ size: 18,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ setState(() => _sortDesc = !_sortDesc);
|
|
|
+ _search();
|
|
|
+ },
|
|
|
+ child: Container(
|
|
|
+ width: 40,
|
|
|
+ height: 40,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: colors.primary,
|
|
|
+ borderRadius: BorderRadius.circular(20),
|
|
|
),
|
|
|
- if (_aeNoCtrl.text.isNotEmpty)
|
|
|
- GestureDetector(
|
|
|
- onTap: () { _aeNoCtrl.clear(); setState(() {}); },
|
|
|
- child: Icon(Icons.close, size: 18, color: colors.textSecondary),
|
|
|
+ child: Center(
|
|
|
+ child: Icon(
|
|
|
+ _sortDesc ? Icons.arrow_downward : Icons.arrow_upward,
|
|
|
+ color: Colors.white,
|
|
|
+ size: 20,
|
|
|
),
|
|
|
- ]),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- GestureDetector(
|
|
|
- onTap: () { setState(() => _sortDesc = !_sortDesc); _search(); },
|
|
|
- child: Container(
|
|
|
- width: 40, height: 40,
|
|
|
- decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(20)),
|
|
|
- child: Center(child: Icon(_sortDesc ? Icons.arrow_downward : Icons.arrow_upward, color: Colors.white, size: 20)),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ GestureDetector(
|
|
|
+ onTap: _search,
|
|
|
+ child: Container(
|
|
|
+ width: 40,
|
|
|
+ height: 40,
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: colors.primary,
|
|
|
+ borderRadius: BorderRadius.circular(20),
|
|
|
+ ),
|
|
|
+ child: const Icon(
|
|
|
+ Icons.search,
|
|
|
+ color: Colors.white,
|
|
|
+ size: 22,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
),
|
|
|
- ),
|
|
|
- const SizedBox(width: 8),
|
|
|
- GestureDetector(
|
|
|
- onTap: _search,
|
|
|
- child: Container(
|
|
|
- width: 40, height: 40,
|
|
|
- decoration: BoxDecoration(color: colors.primary, borderRadius: BorderRadius.circular(20)),
|
|
|
- child: const Icon(Icons.search, color: Colors.white, size: 22),
|
|
|
- ),
|
|
|
+ ],
|
|
|
),
|
|
|
- ]),
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _dateChip(TextEditingController ctrl, String hint, TDThemeData tdTheme, AppColorsExtension colors) {
|
|
|
+ Widget _dateChip(
|
|
|
+ TextEditingController ctrl,
|
|
|
+ String hint,
|
|
|
+ TDThemeData tdTheme,
|
|
|
+ AppColorsExtension colors,
|
|
|
+ ) {
|
|
|
final text = ctrl.text;
|
|
|
return Container(
|
|
|
height: 40,
|
|
|
@@ -353,26 +463,41 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
borderRadius: BorderRadius.circular(20),
|
|
|
border: Border.all(color: tdTheme.componentStrokeColor),
|
|
|
),
|
|
|
- child: Row(children: [
|
|
|
- Icon(Icons.calendar_today, size: 16, color: colors.textSecondary),
|
|
|
- const SizedBox(width: 6),
|
|
|
- Expanded(
|
|
|
- child: Text(
|
|
|
- text.isNotEmpty ? text : hint,
|
|
|
- maxLines: 1, overflow: TextOverflow.ellipsis,
|
|
|
- style: TextStyle(fontSize: 14, color: text.isNotEmpty ? colors.textPrimary : colors.textSecondary),
|
|
|
- ),
|
|
|
- ),
|
|
|
- if (text.isNotEmpty)
|
|
|
- GestureDetector(
|
|
|
- onTap: () { ctrl.clear(); setState(() {}); },
|
|
|
- child: Icon(Icons.close, size: 18, color: colors.textSecondary),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ Icon(Icons.calendar_today, size: 16, color: colors.textSecondary),
|
|
|
+ const SizedBox(width: 6),
|
|
|
+ Expanded(
|
|
|
+ child: Text(
|
|
|
+ text.isNotEmpty ? text : hint,
|
|
|
+ maxLines: 1,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 14,
|
|
|
+ color: text.isNotEmpty
|
|
|
+ ? colors.textPrimary
|
|
|
+ : colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
),
|
|
|
- ]),
|
|
|
+ if (text.isNotEmpty)
|
|
|
+ GestureDetector(
|
|
|
+ onTap: () {
|
|
|
+ ctrl.clear();
|
|
|
+ setState(() {});
|
|
|
+ },
|
|
|
+ child: Icon(Icons.close, size: 18, color: colors.textSecondary),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _buildListContent(AppLocalizations l10n, AppColorsExtension colors, Map<String, List<ImportableItem>> grouped) {
|
|
|
+ Widget _buildListContent(
|
|
|
+ AppLocalizations l10n,
|
|
|
+ AppColorsExtension colors,
|
|
|
+ Map<String, List<ImportableItem>> grouped,
|
|
|
+ ) {
|
|
|
if (_loading && !_initialLoaded) {
|
|
|
return EasyRefresh(
|
|
|
header: TDRefreshHeader(),
|
|
|
@@ -389,7 +514,10 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
controller: _refreshCtrl,
|
|
|
onRefresh: _refresh,
|
|
|
child: ListView(
|
|
|
- children: [const SizedBox(height: 120), EmptyState(message: l10n.get('noData'))],
|
|
|
+ children: [
|
|
|
+ const SizedBox(height: 120),
|
|
|
+ EmptyState(message: l10n.get('noData')),
|
|
|
+ ],
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
@@ -412,7 +540,12 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _buildGroupCard(String aeNo, List<ImportableItem> items, AppLocalizations l10n, AppColorsExtension colors) {
|
|
|
+ Widget _buildGroupCard(
|
|
|
+ String aeNo,
|
|
|
+ List<ImportableItem> items,
|
|
|
+ AppLocalizations l10n,
|
|
|
+ AppColorsExtension colors,
|
|
|
+ ) {
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.only(bottom: 16),
|
|
|
child: Container(
|
|
|
@@ -428,34 +561,90 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
onTap: () => _toggleGroup(aeNo),
|
|
|
child: Padding(
|
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
|
|
- child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
|
- Row(children: [
|
|
|
- _buildHeaderCheckbox(aeNo, colors),
|
|
|
- const SizedBox(width: 8),
|
|
|
- Expanded(child: Text(aeNo, style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.textPrimary))),
|
|
|
- Column(crossAxisAlignment: CrossAxisAlignment.end, children: [
|
|
|
- Text(items.first.aeDd, style: TextStyle(fontSize: 13, color: colors.textSecondary)),
|
|
|
- const SizedBox(height: 2),
|
|
|
- _priorityChip(items.first.priority, l10n, colors),
|
|
|
- ]),
|
|
|
- ]),
|
|
|
- if (items.first.reason.isNotEmpty)
|
|
|
- Padding(
|
|
|
- padding: const EdgeInsets.only(top: 4, left: 30),
|
|
|
- child: Text(items.first.reason, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 13, color: colors.textSecondary)),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Row(
|
|
|
+ children: [
|
|
|
+ _buildHeaderCheckbox(aeNo, colors),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Expanded(
|
|
|
+ child: Text(
|
|
|
+ aeNo,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 15,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: colors.textPrimary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ items.first.aeDd,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 2),
|
|
|
+ _priorityChip(items.first.priority, l10n, colors),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
),
|
|
|
- ]),
|
|
|
+ if (items.first.reason.isNotEmpty)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(top: 4, left: 30),
|
|
|
+ child: Text(
|
|
|
+ items.first.reason,
|
|
|
+ maxLines: 1,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
),
|
|
|
),
|
|
|
Divider(height: 1, color: colors.border),
|
|
|
if (items.length <= 3)
|
|
|
- ...items.asMap().entries.map((entry) => _buildDetailRow(entry.value, _items.indexOf(entry.value), l10n, colors))
|
|
|
+ ...items.asMap().entries.map(
|
|
|
+ (entry) => _buildDetailRow(
|
|
|
+ entry.value,
|
|
|
+ _items.indexOf(entry.value),
|
|
|
+ l10n,
|
|
|
+ colors,
|
|
|
+ ),
|
|
|
+ )
|
|
|
else ...[
|
|
|
- ...items.take(3).toList().asMap().entries.map((entry) => _buildDetailRow(entry.value, _items.indexOf(entry.value), l10n, colors)),
|
|
|
+ ...items
|
|
|
+ .take(3)
|
|
|
+ .toList()
|
|
|
+ .asMap()
|
|
|
+ .entries
|
|
|
+ .map(
|
|
|
+ (entry) => _buildDetailRow(
|
|
|
+ entry.value,
|
|
|
+ _items.indexOf(entry.value),
|
|
|
+ l10n,
|
|
|
+ colors,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
GestureDetector(
|
|
|
- onTap: () => setState(() => _expandedGroups[aeNo] = !(_expandedGroups[aeNo] ?? false)),
|
|
|
+ onTap: () => setState(
|
|
|
+ () =>
|
|
|
+ _expandedGroups[aeNo] = !(_expandedGroups[aeNo] ?? false),
|
|
|
+ ),
|
|
|
child: Padding(
|
|
|
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
|
|
+ padding: const EdgeInsets.symmetric(
|
|
|
+ horizontal: 16,
|
|
|
+ vertical: 4,
|
|
|
+ ),
|
|
|
child: Row(
|
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
|
children: [
|
|
|
@@ -470,14 +659,35 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
),
|
|
|
),
|
|
|
if (_expandedGroups[aeNo] == true)
|
|
|
- ...items.skip(3).toList().asMap().entries.map((entry) => _buildDetailRow(entry.value, _items.indexOf(entry.value), l10n, colors)),
|
|
|
+ ...items
|
|
|
+ .skip(3)
|
|
|
+ .toList()
|
|
|
+ .asMap()
|
|
|
+ .entries
|
|
|
+ .map(
|
|
|
+ (entry) => _buildDetailRow(
|
|
|
+ entry.value,
|
|
|
+ _items.indexOf(entry.value),
|
|
|
+ l10n,
|
|
|
+ colors,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
],
|
|
|
if (items.length > 1)
|
|
|
Padding(
|
|
|
padding: const EdgeInsets.fromLTRB(0, 4, 16, 12),
|
|
|
- child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
|
|
|
- Text('${l10n.get('total')} ${items.length} ${l10n.get('unitItem')}', style: TextStyle(fontSize: 12, color: colors.textSecondary)),
|
|
|
- ]),
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.end,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ '${l10n.get('total')} ${items.length} ${l10n.get('unitItem')}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 12,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
)
|
|
|
else
|
|
|
const SizedBox(height: 8),
|
|
|
@@ -487,36 +697,120 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _buildDetailRow(ImportableItem d, int idx, AppLocalizations l10n, AppColorsExtension colors) {
|
|
|
+ Widget _buildDetailRow(
|
|
|
+ ImportableItem d,
|
|
|
+ int idx,
|
|
|
+ AppLocalizations l10n,
|
|
|
+ AppColorsExtension colors,
|
|
|
+ ) {
|
|
|
return GestureDetector(
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
onTap: () => _toggleItem(idx),
|
|
|
child: Padding(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
|
|
- child: Row(children: [
|
|
|
- SizedBox(width: 30, child: _buildItemCheckbox(idx, colors)),
|
|
|
- const SizedBox(width: 4),
|
|
|
- Container(width: 24, alignment: Alignment.center, child: Text('#${d.itm}', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: colors.textSecondary))),
|
|
|
- const SizedBox(width: 8),
|
|
|
- Expanded(
|
|
|
- child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
|
- Text('${d.typeName.isNotEmpty ? '${d.typeNo}/${d.typeName}' : d.typeNo} ${d.accName}', style: TextStyle(fontSize: 14, color: colors.textPrimary)),
|
|
|
- if (d.rem.isNotEmpty)
|
|
|
- Padding(padding: const EdgeInsets.only(top: 2), child: Text(d.rem, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 13, color: colors.textSecondary))),
|
|
|
- const SizedBox(height: 3),
|
|
|
- if (d.sqMan.isNotEmpty)
|
|
|
- Padding(padding: const EdgeInsets.only(bottom: 2), child: Text('${l10n.get('applicant')}: ${d.sqName.isNotEmpty ? '${d.sqMan}/${d.sqName}' : d.sqMan}', style: TextStyle(fontSize: 13, color: colors.textSecondary))),
|
|
|
- Padding(padding: const EdgeInsets.only(bottom: 2), child: Text('${l10n.get('acctSubject')}: ${d.accNo}/${d.accName}', style: TextStyle(fontSize: 13, color: colors.textSecondary))),
|
|
|
- if (d.depName.isNotEmpty)
|
|
|
- Padding(padding: const EdgeInsets.only(bottom: 2), child: Text('${l10n.get('dept')}: ${d.dep}/${d.depName}', style: TextStyle(fontSize: 13, color: colors.textSecondary))),
|
|
|
- if (d.objName.isNotEmpty)
|
|
|
- Padding(padding: const EdgeInsets.only(bottom: 2), child: Text('${l10n.get('project')}: ${d.objNo}/${d.objName}', style: TextStyle(fontSize: 13, color: colors.textSecondary))),
|
|
|
- if (d.startDd.isNotEmpty || d.endDd.isNotEmpty)
|
|
|
- Text('${l10n.get('date')}: ${d.startDd} ~ ${d.endDd}', style: TextStyle(fontSize: 13, color: colors.textSecondary)),
|
|
|
- ]),
|
|
|
- ),
|
|
|
- Text('¥${d.amtnYj.toStringAsFixed(2)}', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: colors.amountPrimary)),
|
|
|
- ]),
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ SizedBox(width: 30, child: _buildItemCheckbox(idx, colors)),
|
|
|
+ const SizedBox(width: 4),
|
|
|
+ Container(
|
|
|
+ width: 24,
|
|
|
+ alignment: Alignment.center,
|
|
|
+ child: Text(
|
|
|
+ '#${d.itm}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(width: 8),
|
|
|
+ Expanded(
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ Text(
|
|
|
+ '${d.typeName.isNotEmpty ? '${d.typeNo}/${d.typeName}' : d.typeNo} ${d.accName}',
|
|
|
+ style: TextStyle(fontSize: 14, color: colors.textPrimary),
|
|
|
+ ),
|
|
|
+ if (d.rem.isNotEmpty)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(top: 2),
|
|
|
+ child: Text(
|
|
|
+ d.rem,
|
|
|
+ maxLines: 2,
|
|
|
+ overflow: TextOverflow.ellipsis,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 3),
|
|
|
+ if (d.sqMan.isNotEmpty)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(bottom: 2),
|
|
|
+ child: Text(
|
|
|
+ '${l10n.get('applicant')}: ${d.sqName.isNotEmpty ? '${d.sqMan}/${d.sqName}' : d.sqMan}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(bottom: 2),
|
|
|
+ child: Text(
|
|
|
+ '${l10n.get('acctSubject')}: ${d.accNo}/${d.accName}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (d.depName.isNotEmpty)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(bottom: 2),
|
|
|
+ child: Text(
|
|
|
+ '${l10n.get('dept')}: ${d.dep}/${d.depName}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (d.objName.isNotEmpty)
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(bottom: 2),
|
|
|
+ child: Text(
|
|
|
+ '${l10n.get('project')}: ${d.objNo}/${d.objName}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ if (d.startDd.isNotEmpty || d.endDd.isNotEmpty)
|
|
|
+ Text(
|
|
|
+ '${l10n.get('date')}: ${d.startDd} ~ ${d.endDd}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 13,
|
|
|
+ color: colors.textSecondary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ Text(
|
|
|
+ '¥${d.amtnYj.toStringAsFixed(2)}',
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 15,
|
|
|
+ fontWeight: FontWeight.w600,
|
|
|
+ color: colors.amountPrimary,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
@@ -554,9 +848,21 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Widget _priorityChip(String priority, AppLocalizations l10n, AppColorsExtension colors) {
|
|
|
- final label = priority == '3' ? l10n.get('filterCritical') : priority == '2' ? l10n.get('urgent') : l10n.get('normal');
|
|
|
- final chipColor = priority == '3' ? colors.danger : priority == '2' ? colors.warning : colors.primary;
|
|
|
+ Widget _priorityChip(
|
|
|
+ String priority,
|
|
|
+ AppLocalizations l10n,
|
|
|
+ AppColorsExtension colors,
|
|
|
+ ) {
|
|
|
+ final label = priority == '3'
|
|
|
+ ? l10n.get('filterCritical')
|
|
|
+ : priority == '2'
|
|
|
+ ? l10n.get('urgent')
|
|
|
+ : l10n.get('normal');
|
|
|
+ final chipColor = priority == '3'
|
|
|
+ ? colors.danger
|
|
|
+ : priority == '2'
|
|
|
+ ? colors.warning
|
|
|
+ : colors.primary;
|
|
|
return Container(
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
|
|
decoration: BoxDecoration(
|
|
|
@@ -564,7 +870,14 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
borderRadius: BorderRadius.circular(4),
|
|
|
border: Border.all(color: chipColor, width: 0.5),
|
|
|
),
|
|
|
- child: Text(label, style: TextStyle(fontSize: 11, fontWeight: FontWeight.w500, color: chipColor)),
|
|
|
+ child: Text(
|
|
|
+ label,
|
|
|
+ style: TextStyle(
|
|
|
+ fontSize: 11,
|
|
|
+ fontWeight: FontWeight.w500,
|
|
|
+ color: chipColor,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
);
|
|
|
}
|
|
|
|
|
|
@@ -577,32 +890,37 @@ class _ExpenseApplyImportPageState extends ConsumerState<ExpenseApplyImportPage>
|
|
|
grouped.putIfAbsent(item.aeNo, () => []).add(item);
|
|
|
}
|
|
|
|
|
|
- return Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
|
|
- Expanded(
|
|
|
- child: GestureDetector(
|
|
|
- behavior: HitTestBehavior.translucent,
|
|
|
- onTap: () => FocusScope.of(context).unfocus(),
|
|
|
- child: Column(children: [
|
|
|
- _buildSearchBar(l10n, colors),
|
|
|
- const SizedBox(height: 8),
|
|
|
- Expanded(
|
|
|
- child: _buildListContent(l10n, colors, grouped),
|
|
|
+ return Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ child: GestureDetector(
|
|
|
+ behavior: HitTestBehavior.translucent,
|
|
|
+ onTap: () => FocusScope.of(context).unfocus(),
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ _buildSearchBar(l10n, colors),
|
|
|
+ const SizedBox(height: 8),
|
|
|
+ Expanded(child: _buildListContent(l10n, colors, grouped)),
|
|
|
+ ],
|
|
|
),
|
|
|
- ]),
|
|
|
+ ),
|
|
|
),
|
|
|
- ),
|
|
|
- SafeArea(
|
|
|
- top: false, left: false, right: false,
|
|
|
- child: Padding(
|
|
|
- padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
|
|
- child: TDButton(
|
|
|
- text: l10n.get('confirmImport'),
|
|
|
- size: TDButtonSize.large,
|
|
|
- theme: TDButtonTheme.primary,
|
|
|
- onTap: _confirmImport,
|
|
|
+ SafeArea(
|
|
|
+ top: false,
|
|
|
+ left: false,
|
|
|
+ right: false,
|
|
|
+ child: Padding(
|
|
|
+ padding: const EdgeInsets.fromLTRB(16, 16, 16, 12),
|
|
|
+ child: TDButton(
|
|
|
+ text: l10n.get('confirmImport'),
|
|
|
+ size: TDButtonSize.large,
|
|
|
+ theme: TDButtonTheme.primary,
|
|
|
+ onTap: _confirmImport,
|
|
|
+ ),
|
|
|
),
|
|
|
),
|
|
|
- ),
|
|
|
- ]);
|
|
|
+ ],
|
|
|
+ );
|
|
|
}
|
|
|
}
|