feat(pagination): 공통 컨트롤 도입과 사용자 관리 가이드 추가

- 테이블 푸터에서 SuperportPaginationControls를 사용하도록 각 관리 페이지 페이지네이션 로직을 정리

- SuperportPaginationControls 위젯을 추가하고 SuperportTable 푸터를 개선해 페이지 사이즈 선택과 이동 버튼을 분리

- 사용자 등록·계정 관리 요구사항을 문서화한 doc/user_setting.md를 작성하고 AGENTS.md 코멘트 규칙을 업데이트

- flutter analyze를 수행해 빌드 경고가 없음을 확인
This commit is contained in:
JiWoong Sul
2025-10-24 16:24:02 +09:00
parent 9d6cbb1ab2
commit 9beb161527
17 changed files with 432 additions and 502 deletions

View File

@@ -13,6 +13,7 @@ import '../../../../widgets/components/feedback.dart';
import '../../../../widgets/components/filter_bar.dart';
import '../../../../widgets/components/superport_dialog.dart';
import '../../../../widgets/components/superport_table.dart';
import '../../../../widgets/components/superport_pagination_controls.dart';
import '../../../../widgets/components/feature_disabled_placeholder.dart';
import '../../domain/entities/approval.dart';
import '../../domain/entities/approval_template.dart';
@@ -212,9 +213,6 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final isLoadingActions = _controller.isLoadingActions;
final isPerformingAction = _controller.isPerformingAction;
final processingStepId = _controller.processingStepId;
@@ -349,44 +347,11 @@ class _ApprovalEnabledPageState extends State<_ApprovalEnabledPage> {
'페이지 $currentPage / $totalPages',
style: theme.textTheme.small,
),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoadingList || currentPage <= 1
? null
: () => _controller.fetch(page: 1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoadingList || currentPage <= 1
? null
: () => _controller.fetch(page: currentPage - 1),
child: const Text('이전'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoadingList || !hasNext
? null
: () => _controller.fetch(page: currentPage + 1),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoadingList ||
currentPage >= totalPages
? null
: () => _controller.fetch(page: totalPages),
child: const Text('마지막'),
),
],
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoadingList,
onPageSelected: (page) => _controller.fetch(page: page),
),
],
),

View File

@@ -10,6 +10,7 @@ import '../../../../../core/permissions/permission_resources.dart';
import '../../../../../widgets/app_layout.dart';
import '../../../../../widgets/components/filter_bar.dart';
import '../../../../../widgets/components/superport_dialog.dart';
import '../../../../../widgets/components/superport_pagination_controls.dart';
import '../../../../../widgets/components/feature_disabled_placeholder.dart';
import '../controllers/approval_step_controller.dart';
import '../../domain/entities/approval_step_input.dart';
@@ -119,9 +120,6 @@ class _ApprovalStepEnabledPageState extends State<_ApprovalStepEnabledPage> {
final totalPages = pageSize == 0
? 1
: (totalCount / pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final statusOptions = _buildStatusOptions(records);
final selectedStatus = _controller.statusId ?? -1;
final approverOptions = _buildApproverOptions(records);
@@ -411,54 +409,12 @@ class _ApprovalStepEnabledPageState extends State<_ApprovalStepEnabledPage> {
Text('$totalCount건', style: theme.textTheme.small),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading ||
isSaving ||
currentPage <= 1
? null
: () => _controller.fetch(page: 1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading ||
isSaving ||
currentPage <= 1
? null
: () => _controller.fetch(
page: currentPage - 1,
),
child: const Text('이전'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading ||
isSaving ||
!hasNext
? null
: () => _controller.fetch(
page: currentPage + 1,
),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading ||
isSaving ||
currentPage >= totalPages
? null
: () => _controller.fetch(
page: totalPages,
),
child: const Text('마지막'),
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading || isSaving,
onPageSelected: (page) =>
_controller.fetch(page: page),
),
const SizedBox(width: 12),
Text(

View File

@@ -645,10 +645,10 @@ class _InboundPageState extends State<InboundPage> {
return records;
}
List<String> _buildRecordRow(InboundRecord record) {
List<String> _buildRecordRow(InboundRecord record, int displayIndex) {
final primaryItem = record.items.isNotEmpty ? record.items.first : null;
return [
record.number.split('-').last,
displayIndex.toString(),
_dateFormatter.format(record.processedAt),
record.warehouse,
record.transactionNumber,
@@ -670,13 +670,15 @@ class _InboundPageState extends State<InboundPage> {
DeviceBreakpoint breakpoint,
) {
final visibleColumns = _visibleColumnsFor(breakpoint);
final baseOffset = _rowNumberOffset(records.length);
return ShadTable.list(
header: [
for (final index in visibleColumns)
ShadTableCell.header(child: Text(InboundTableSpec.headers[index])),
],
children: [
for (final record in records) _buildTableCells(record, visibleColumns),
for (var i = 0; i < records.length; i++)
_buildTableCells(records[i], visibleColumns, baseOffset + i + 1),
],
columnSpanExtent: (index) =>
const FixedTableSpanExtent(InboundTableSpec.columnSpanWidth),
@@ -718,8 +720,9 @@ class _InboundPageState extends State<InboundPage> {
List<ShadTableCell> _buildTableCells(
InboundRecord record,
List<int> visibleColumns,
int displayIndex,
) {
final values = _buildRecordRow(record);
final values = _buildRecordRow(record, displayIndex);
return [
for (final index in visibleColumns)
ShadTableCell(
@@ -728,6 +731,16 @@ class _InboundPageState extends State<InboundPage> {
];
}
int _rowNumberOffset(int currentCount) {
final page = _result?.page ?? _currentPage;
final pageSize = _result?.pageSize ?? _pageSize;
final safePage = page > 0 ? page : 1;
final safePageSize = pageSize > 0
? pageSize
: (currentCount > 0 ? currentCount : 1);
return (safePage - 1) * safePageSize;
}
Future<void> _showDetailDialog(InboundRecord record) async {
await showInventoryTransactionDetailDialog<void>(
context: context,

View File

@@ -574,39 +574,46 @@ class _OutboundPageState extends State<OutboundPage> {
style: theme.textTheme.muted,
),
)
: ShadTable.list(
header: OutboundTableSpec.headers
.map(
(header) =>
ShadTableCell.header(child: Text(header)),
)
.toList(),
children: [
for (final row in visibleRecords.map(
_buildRecordRow,
))
[
for (final value in row)
ShadTableCell(
child: Text(
value,
overflow: TextOverflow.ellipsis,
: () {
final baseOffset = _rowNumberOffset(
visibleRecords.length,
);
return ShadTable.list(
header: OutboundTableSpec.headers
.map(
(header) =>
ShadTableCell.header(child: Text(header)),
)
.toList(),
children: [
for (var i = 0; i < visibleRecords.length; i++)
[
for (final value in _buildRecordRow(
visibleRecords[i],
baseOffset + i + 1,
))
ShadTableCell(
child: Text(
value,
overflow: TextOverflow.ellipsis,
),
),
),
],
],
columnSpanExtent: (index) =>
const FixedTableSpanExtent(
OutboundTableSpec.columnSpanWidth,
),
rowSpanExtent: (index) => const FixedTableSpanExtent(
OutboundTableSpec.rowSpanHeight,
),
onRowTap: (rowIndex) {
final record = visibleRecords[rowIndex];
_selectRecord(record, openDetail: true);
},
),
],
],
columnSpanExtent: (index) =>
const FixedTableSpanExtent(
OutboundTableSpec.columnSpanWidth,
),
rowSpanExtent: (index) =>
const FixedTableSpanExtent(
OutboundTableSpec.rowSpanHeight,
),
onRowTap: (rowIndex) {
final record = visibleRecords[rowIndex];
_selectRecord(record, openDetail: true);
},
);
}(),
),
if (filtered.isNotEmpty) ...[
const SizedBox(height: 12),
@@ -787,10 +794,10 @@ class _OutboundPageState extends State<OutboundPage> {
}
}
List<String> _buildRecordRow(OutboundRecord record) {
List<String> _buildRecordRow(OutboundRecord record, int displayIndex) {
final primaryItem = record.items.isNotEmpty ? record.items.first : null;
return [
record.number.split('-').last,
displayIndex.toString(),
_dateFormatter.format(record.processedAt),
record.warehouse,
record.transactionNumber,
@@ -808,6 +815,16 @@ class _OutboundPageState extends State<OutboundPage> {
];
}
int _rowNumberOffset(int currentCount) {
final page = _result?.page ?? _currentPage;
final pageSize = _result?.pageSize ?? _pageSize;
final safePage = page > 0 ? page : 1;
final safePageSize = pageSize > 0
? pageSize
: (currentCount > 0 ? currentCount : 1);
return (safePage - 1) * safePageSize;
}
Future<void> _showDetailDialog(OutboundRecord record) async {
await showInventoryTransactionDetailDialog<void>(
context: context,

View File

@@ -522,36 +522,45 @@ class _RentalPageState extends State<RentalPage> {
: '대여 데이터가 없습니다.',
description: _errorMessage ?? '검색 조건을 조정해 다시 시도하세요.',
)
: ShadTable.list(
header: RentalTableSpec.headers
.map(
(header) =>
ShadTableCell.header(child: Text(header)),
)
.toList(),
children: [
for (final record in visibleRecords)
_buildRecordRow(record).map(
(value) => ShadTableCell(
child: Text(
value,
overflow: TextOverflow.ellipsis,
: () {
final baseOffset = _rowNumberOffset(
visibleRecords.length,
);
return ShadTable.list(
header: RentalTableSpec.headers
.map(
(header) =>
ShadTableCell.header(child: Text(header)),
)
.toList(),
children: [
for (var i = 0; i < visibleRecords.length; i++)
_buildRecordRow(
visibleRecords[i],
baseOffset + i + 1,
).map(
(value) => ShadTableCell(
child: Text(
value,
overflow: TextOverflow.ellipsis,
),
),
),
),
],
columnSpanExtent: (index) =>
const FixedTableSpanExtent(
RentalTableSpec.columnSpanWidth,
),
rowSpanExtent: (index) => const FixedTableSpanExtent(
RentalTableSpec.rowSpanHeight,
),
onRowTap: (rowIndex) {
final record = visibleRecords[rowIndex];
_selectRecord(record, openDetail: true);
},
),
],
columnSpanExtent: (index) =>
const FixedTableSpanExtent(
RentalTableSpec.columnSpanWidth,
),
rowSpanExtent: (index) =>
const FixedTableSpanExtent(
RentalTableSpec.rowSpanHeight,
),
onRowTap: (rowIndex) {
final record = visibleRecords[rowIndex];
_selectRecord(record, openDetail: true);
},
);
}(),
),
if (filtered.isNotEmpty) ...[
const SizedBox(height: 12),
@@ -747,9 +756,9 @@ class _RentalPageState extends State<RentalPage> {
}
}
List<String> _buildRecordRow(RentalRecord record) {
List<String> _buildRecordRow(RentalRecord record, int displayIndex) {
return [
record.number.split('-').last,
displayIndex.toString(),
_dateFormatter.format(record.processedAt),
record.warehouse,
record.rentalType,
@@ -765,6 +774,16 @@ class _RentalPageState extends State<RentalPage> {
];
}
int _rowNumberOffset(int currentCount) {
final page = _result?.page ?? _currentPage;
final pageSize = _result?.pageSize ?? _pageSize;
final safePage = page > 0 ? page : 1;
final safePageSize = pageSize > 0
? pageSize
: (currentCount > 0 ? currentCount : 1);
return (safePage - 1) * safePageSize;
}
Future<void> _showDetailDialog(RentalRecord record) async {
await showInventoryTransactionDetailDialog<void>(
context: context,

View File

@@ -7,6 +7,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import 'package:superport_v2/features/util/postal_search/presentation/models/postal_search_result.dart';
import 'package:superport_v2/features/util/postal_search/presentation/widgets/postal_search_dialog.dart';
import 'package:superport_v2/widgets/components/responsive_section.dart';
@@ -191,9 +192,6 @@ class _CustomerEnabledPageState extends State<_CustomerEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -323,34 +321,11 @@ class _CustomerEnabledPageState extends State<_CustomerEnabledPage> {
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.end,
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(1),
child: const Text('처음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(currentPage - 1),
child: const Text('이전'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _goToPage(currentPage + 1),
child: const Text('다음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _goToPage(totalPages),
child: const Text('마지막'),
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: _goToPage,
),
],
),

View File

@@ -6,6 +6,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import '../../../../../core/config/environment.dart';
import '../../../../../widgets/spec_page.dart';
@@ -131,9 +132,6 @@ class _GroupEnabledPageState extends State<_GroupEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -252,41 +250,11 @@ class _GroupEnabledPageState extends State<_GroupEnabledPage> {
'페이지 $currentPage / $totalPages',
style: theme.textTheme.small,
),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: 1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: currentPage - 1),
child: const Text('이전'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _controller.fetch(page: currentPage + 1),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _controller.fetch(page: totalPages),
child: const Text('마지막'),
),
],
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: (page) => _controller.fetch(page: page),
),
],
),
@@ -386,9 +354,9 @@ class _GroupEnabledPageState extends State<_GroupEnabledPage> {
onPressed: isSaving
? null
: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(),
dialogContext,
rootNavigator: true,
).pop(),
child: const Text('취소'),
);
},
@@ -418,10 +386,10 @@ class _GroupEnabledPageState extends State<_GroupEnabledPage> {
isActive: isActiveNotifier.value,
note: note.isEmpty ? null : note,
);
final navigator = Navigator.of(
dialogContext,
rootNavigator: true,
);
final navigator = Navigator.of(
dialogContext,
rootNavigator: true,
);
final response = isEdit
? await _controller.update(groupId!, input)
: await _controller.create(input);
@@ -577,17 +545,13 @@ class _GroupEnabledPageState extends State<_GroupEnabledPage> {
content: Text('"${group.groupName}" 그룹을 삭제하시겠습니까?'),
actions: [
TextButton(
onPressed: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(false),
onPressed: () =>
Navigator.of(dialogContext, rootNavigator: true).pop(false),
child: const Text('취소'),
),
TextButton(
onPressed: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(true),
onPressed: () =>
Navigator.of(dialogContext, rootNavigator: true).pop(true),
child: const Text('삭제'),
),
],

View File

@@ -7,6 +7,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import '../../../../../core/config/environment.dart';
import '../../../../../core/permissions/permission_manager.dart';
@@ -182,9 +183,6 @@ class _GroupPermissionEnabledPageState
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -365,41 +363,11 @@ class _GroupPermissionEnabledPageState
'페이지 $currentPage / $totalPages',
style: theme.textTheme.small,
),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: 1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: currentPage - 1),
child: const Text('이전'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _controller.fetch(page: currentPage + 1),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _controller.fetch(page: totalPages),
child: const Text('마지막'),
),
],
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: (page) => _controller.fetch(page: page),
),
],
),
@@ -500,9 +468,9 @@ class _GroupPermissionEnabledPageState
onPressed: isSaving
? null
: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(false),
dialogContext,
rootNavigator: true,
).pop(false),
child: const Text('취소'),
);
},

View File

@@ -6,6 +6,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import '../../../../../core/config/environment.dart';
import '../../../../../widgets/spec_page.dart';
@@ -152,9 +153,6 @@ class _MenuEnabledPageState extends State<_MenuEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -308,41 +306,11 @@ class _MenuEnabledPageState extends State<_MenuEnabledPage> {
'페이지 $currentPage / $totalPages',
style: theme.textTheme.small,
),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: 1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: currentPage - 1),
child: const Text('이전'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _controller.fetch(page: currentPage + 1),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _controller.fetch(page: totalPages),
child: const Text('마지막'),
),
],
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: (page) => _controller.fetch(page: page),
),
],
),
@@ -436,9 +404,9 @@ class _MenuEnabledPageState extends State<_MenuEnabledPage> {
onPressed: isSaving
? null
: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(false),
dialogContext,
rootNavigator: true,
).pop(false),
child: const Text('취소'),
);
},
@@ -730,10 +698,8 @@ class _MenuEnabledPageState extends State<_MenuEnabledPage> {
secondaryAction: Builder(
builder: (dialogContext) {
return ShadButton.ghost(
onPressed: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(false),
onPressed: () =>
Navigator.of(dialogContext, rootNavigator: true).pop(false),
child: const Text('취소'),
);
},
@@ -741,10 +707,8 @@ class _MenuEnabledPageState extends State<_MenuEnabledPage> {
primaryAction: Builder(
builder: (dialogContext) {
return ShadButton.destructive(
onPressed: () => Navigator.of(
dialogContext,
rootNavigator: true,
).pop(true),
onPressed: () =>
Navigator.of(dialogContext, rootNavigator: true).pop(true),
child: const Text('삭제'),
);
},

View File

@@ -7,6 +7,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import 'package:superport_v2/widgets/components/superport_table.dart';
import 'package:superport_v2/widgets/components/responsive_section.dart';
@@ -157,9 +158,6 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -324,34 +322,11 @@ class _ProductEnabledPageState extends State<_ProductEnabledPage> {
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.end,
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(1),
child: const Text('처음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(currentPage - 1),
child: const Text('이전'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _goToPage(currentPage + 1),
child: const Text('다음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _goToPage(totalPages),
child: const Text('마지막'),
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: _goToPage,
),
],
),

View File

@@ -6,6 +6,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import '../../../../../core/config/environment.dart';
import '../../../../../core/permissions/permission_manager.dart';
@@ -164,9 +165,6 @@ class _UserEnabledPageState extends State<_UserEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -292,41 +290,11 @@ class _UserEnabledPageState extends State<_UserEnabledPage> {
'페이지 $currentPage / $totalPages',
style: theme.textTheme.small,
),
Row(
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: 1),
child: const Text('처음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _controller.fetch(page: currentPage - 1),
child: const Text('이전'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _controller.fetch(page: currentPage + 1),
child: const Text('다음'),
),
const SizedBox(width: 8),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _controller.fetch(page: totalPages),
child: const Text('마지막'),
),
],
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: (page) => _controller.fetch(page: page),
),
],
),
@@ -475,10 +443,7 @@ class _UserEnabledPageState extends State<_UserEnabledPage> {
secondaryAction: ValueListenableBuilder<bool>(
valueListenable: saving,
builder: (context, isSaving, _) {
final navigator = Navigator.of(
context,
rootNavigator: true,
);
final navigator = Navigator.of(context, rootNavigator: true);
return ShadButton.ghost(
onPressed: isSaving ? null : () => navigator.pop(false),
child: const Text('취소'),

View File

@@ -7,6 +7,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import 'package:superport_v2/widgets/components/superport_table.dart';
import 'package:superport_v2/widgets/components/responsive_section.dart';
@@ -150,9 +151,6 @@ class _VendorEnabledPageState extends State<_VendorEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
return AppLayout(
title: '제조사(벤더) 관리',
@@ -256,34 +254,11 @@ class _VendorEnabledPageState extends State<_VendorEnabledPage> {
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.end,
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(1),
child: const Text('처음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(currentPage - 1),
child: const Text('이전'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _goToPage(currentPage + 1),
child: const Text('다음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _goToPage(totalPages),
child: const Text('마지막'),
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: _goToPage,
),
],
),

View File

@@ -7,6 +7,7 @@ import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_pagination_controls.dart';
import 'package:superport_v2/widgets/components/superport_table.dart';
import 'package:superport_v2/widgets/components/responsive_section.dart';
import 'package:superport_v2/features/util/postal_search/presentation/models/postal_search_result.dart';
@@ -159,9 +160,6 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
final totalPages = result == null || result.pageSize == 0
? 1
: (result.total / result.pageSize).ceil().clamp(1, 9999);
final hasNext = result == null
? false
: (result.page * result.pageSize) < result.total;
final showReset =
_searchController.text.isNotEmpty ||
@@ -268,34 +266,11 @@ class _WarehouseEnabledPageState extends State<_WarehouseEnabledPage> {
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.end,
children: [
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(1),
child: const Text('처음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || currentPage <= 1
? null
: () => _goToPage(currentPage - 1),
child: const Text('이전'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed: _controller.isLoading || !hasNext
? null
: () => _goToPage(currentPage + 1),
child: const Text('다음'),
),
ShadButton.outline(
size: ShadButtonSize.sm,
onPressed:
_controller.isLoading || currentPage >= totalPages
? null
: () => _goToPage(totalPages),
child: const Text('마지막'),
SuperportPaginationControls(
currentPage: currentPage,
totalPages: totalPages,
isBusy: _controller.isLoading,
onPageSelected: _goToPage,
),
],
),