Files
superport/lib/screens/common/widgets/standard_data_table.dart

315 lines
8.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
/// 표준 데이터 테이블 컬럼 정의
class DataColumn {
final String label;
final double? width;
final int? flex;
final bool isNumeric;
final TextAlign textAlign;
DataColumn({
required this.label,
this.width,
this.flex,
this.isNumeric = false,
TextAlign? textAlign,
}) : textAlign = textAlign ?? (isNumeric ? TextAlign.right : TextAlign.left);
}
/// 표준 데이터 테이블 위젯
///
/// 모든 리스트 화면에서 일관된 테이블 스타일 제공
class StandardDataTable extends StatelessWidget {
final List<DataColumn> columns;
final List<Widget> rows;
final bool showCheckbox;
final bool? isAllSelected;
final ValueChanged<bool?>? onSelectAll;
final bool enableHorizontalScroll;
final ScrollController? horizontalScrollController;
final Widget? emptyWidget;
final bool applyZebraStripes; // 짝수 행 배경색 적용 여부
const StandardDataTable({
super.key,
required this.columns,
required this.rows,
this.showCheckbox = false,
this.isAllSelected,
this.onSelectAll,
this.enableHorizontalScroll = false,
this.horizontalScrollController,
this.emptyWidget,
this.applyZebraStripes = true,
});
@override
Widget build(BuildContext context) {
if (rows.isEmpty) {
return _buildEmptyState();
}
final table = Container(
width: double.infinity,
decoration: BoxDecoration(
color: ShadcnTheme.card,
borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg),
border: Border.all(color: Colors.black),
boxShadow: ShadcnTheme.cardShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 테이블 헤더
_buildHeader(),
// 테이블 데이터 행들
...rows,
],
),
);
if (enableHorizontalScroll) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: horizontalScrollController,
child: table,
);
}
return table;
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: ShadcnTheme.spacing4,
vertical: 10,
),
decoration: BoxDecoration(
color: ShadcnTheme.muted.withValues(alpha: 0.3),
border: Border(
bottom: BorderSide(color: Colors.black),
),
),
child: Row(
children: [
// 체크박스 컬럼
if (showCheckbox)
SizedBox(
width: 40,
child: Checkbox(
value: isAllSelected,
onChanged: onSelectAll,
tristate: false,
),
),
// 데이터 컬럼들
...columns.map((column) {
Widget child = Text(
column.label,
style: ShadcnTheme.bodyMedium.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: column.textAlign,
);
if (column.width != null) {
return SizedBox(
width: column.width,
child: child,
);
} else if (column.flex != null) {
return Expanded(
flex: column.flex!,
child: child,
);
} else {
return Expanded(child: child);
}
}),
],
),
);
}
Widget _buildEmptyState() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(ShadcnTheme.spacing8),
decoration: BoxDecoration(
color: ShadcnTheme.card,
borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg),
border: Border.all(color: Colors.black),
boxShadow: ShadcnTheme.cardShadow,
),
child: emptyWidget ??
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.inbox_outlined,
size: 48,
color: ShadcnTheme.muted,
),
const SizedBox(height: ShadcnTheme.spacing4),
Text(
'데이터가 없습니다',
style: ShadcnTheme.bodyMuted,
),
],
),
),
);
}
}
/// 표준 데이터 행 위젯
class StandardDataRow extends StatelessWidget {
final int index;
final List<Widget> cells;
final bool showCheckbox;
final bool? isSelected;
final ValueChanged<bool?>? onSelect;
final bool applyZebraStripes;
final List<DataColumn> columns;
const StandardDataRow({
super.key,
required this.index,
required this.cells,
this.showCheckbox = false,
this.isSelected,
this.onSelect,
this.applyZebraStripes = true,
required this.columns,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: ShadcnTheme.spacing4,
vertical: 4,
),
decoration: BoxDecoration(
color: applyZebraStripes && index % 2 == 1
? ShadcnTheme.muted.withValues(alpha: 0.1)
: null,
border: Border(
bottom: BorderSide(color: Colors.black),
),
),
child: Row(
children: [
// 체크박스
if (showCheckbox)
SizedBox(
width: 40,
child: Checkbox(
value: isSelected,
onChanged: onSelect,
tristate: false,
),
),
// 데이터 셀들
...cells.asMap().entries.map((entry) {
final cellIndex = entry.key;
final cell = entry.value;
if (cellIndex >= columns.length) return const SizedBox.shrink();
final column = columns[cellIndex];
if (column.width != null) {
return SizedBox(
width: column.width,
child: cell,
);
} else if (column.flex != null) {
return Expanded(
flex: column.flex!,
child: cell,
);
} else {
return Expanded(child: cell);
}
}),
],
),
);
}
}
/// 표준 관리 버튼 세트
class StandardActionButtons extends StatelessWidget {
final VoidCallback? onView;
final VoidCallback? onEdit;
final VoidCallback? onDelete;
final List<Widget>? customButtons;
final double buttonSize;
const StandardActionButtons({
super.key,
this.onView,
this.onEdit,
this.onDelete,
this.customButtons,
this.buttonSize = 32,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (onView != null)
_buildIconButton(
Icons.visibility_outlined,
onView!,
'보기',
ShadcnTheme.primary,
),
if (onEdit != null)
_buildIconButton(
Icons.edit_outlined,
onEdit!,
'수정',
ShadcnTheme.primary,
),
if (onDelete != null)
_buildIconButton(
Icons.delete_outline,
onDelete!,
'삭제',
ShadcnTheme.destructive,
),
if (customButtons != null) ...customButtons!,
],
);
}
Widget _buildIconButton(
IconData icon,
VoidCallback onPressed,
String tooltip,
Color color,
) {
return IconButton(
constraints: BoxConstraints(
minWidth: buttonSize,
minHeight: buttonSize,
maxWidth: buttonSize,
maxHeight: buttonSize,
),
padding: const EdgeInsets.all(4),
icon: Icon(icon, size: 16, color: color),
onPressed: onPressed,
tooltip: tooltip,
);
}
}