initial commit
This commit is contained in:
@@ -52,16 +52,32 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
DateTime.sunday: 0,
|
DateTime.sunday: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
late final ScrollController _hCtrl;
|
// Scroll-Controller
|
||||||
late final ScrollController _vCtrl;
|
late final ScrollController _hHeaderCtrl; // Header (nur mitgeführt)
|
||||||
|
late final ScrollController _hBodyCtrl; // Body (führend)
|
||||||
|
late final ScrollController _vCtrl; // Body vertikal
|
||||||
|
bool _syncingH = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
_monthStart = DateTime(now.year, now.month, 1);
|
_monthStart = DateTime(now.year, now.month, 1);
|
||||||
_hCtrl = ScrollController();
|
|
||||||
|
_hHeaderCtrl = ScrollController();
|
||||||
|
_hBodyCtrl = ScrollController();
|
||||||
_vCtrl = ScrollController();
|
_vCtrl = ScrollController();
|
||||||
|
|
||||||
|
// Body scrollt -> Header folgt
|
||||||
|
_hBodyCtrl.addListener(() {
|
||||||
|
if (_syncingH) return;
|
||||||
|
_syncingH = true;
|
||||||
|
if (_hHeaderCtrl.hasClients) {
|
||||||
|
_hHeaderCtrl.jumpTo(_hBodyCtrl.offset);
|
||||||
|
}
|
||||||
|
_syncingH = false;
|
||||||
|
});
|
||||||
|
|
||||||
_client = http.Client();
|
_client = http.Client();
|
||||||
_bookingApi = BookingApi(client: _client);
|
_bookingApi = BookingApi(client: _client);
|
||||||
_dailyApi = DailyWorkingApi(client: _client);
|
_dailyApi = DailyWorkingApi(client: _client);
|
||||||
@@ -76,7 +92,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
for (final c in _controllers.values) {
|
for (final c in _controllers.values) {
|
||||||
c.dispose();
|
c.dispose();
|
||||||
}
|
}
|
||||||
_hCtrl.dispose();
|
_hHeaderCtrl.dispose();
|
||||||
|
_hBodyCtrl.dispose();
|
||||||
_vCtrl.dispose();
|
_vCtrl.dispose();
|
||||||
_client.close();
|
_client.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -235,42 +252,34 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// FIXIERTE KOPFZEILE
|
// FIXIERTE KOPFZEILE (mitgeführt, nicht interaktiv)
|
||||||
Scrollbar(
|
SingleChildScrollView(
|
||||||
controller: _hCtrl,
|
controller: _hHeaderCtrl,
|
||||||
notificationPredicate: (n) => n.metrics.axis == Axis.horizontal,
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
thumbVisibility: true,
|
scrollDirection: Axis.horizontal,
|
||||||
child: SingleChildScrollView(
|
child: ConstrainedBox(
|
||||||
controller: _hCtrl,
|
constraints: BoxConstraints(minWidth: _tableMinWidth),
|
||||||
scrollDirection: Axis.horizontal,
|
child: DataTableTheme(
|
||||||
child: ConstrainedBox(
|
data: DataTableThemeData(
|
||||||
constraints: BoxConstraints(minWidth: _tableMinWidth),
|
headingRowHeight: 30,
|
||||||
child: DataTableTheme(
|
columnSpacing: 10,
|
||||||
data: DataTableThemeData(
|
headingTextStyle: const TextStyle(fontWeight: FontWeight.w700).copyWith(
|
||||||
headingRowHeight: 30,
|
fontSize: _fontSize,
|
||||||
columnSpacing: 10,
|
|
||||||
headingTextStyle: const TextStyle(fontWeight: FontWeight.w700).copyWith(
|
|
||||||
fontSize: _fontSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DataTable(
|
|
||||||
showCheckboxColumn: false,
|
|
||||||
columns: headerColumns,
|
|
||||||
rows: const <DataRow>[],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
child: const _HeaderOnlyDataTable(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// BODY
|
// BODY (horiz. & vert. Scroll; horizontale Scrollbar hier)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
controller: _hCtrl,
|
controller: _hBodyCtrl,
|
||||||
notificationPredicate: (n) => n.metrics.axis == Axis.horizontal,
|
notificationPredicate: (n) => n.metrics.axis == Axis.horizontal,
|
||||||
thumbVisibility: true,
|
thumbVisibility: true,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: _hCtrl,
|
controller: _hBodyCtrl,
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
@@ -283,7 +292,7 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
controller: _vCtrl,
|
controller: _vCtrl,
|
||||||
child: DataTableTheme(
|
child: DataTableTheme(
|
||||||
data: const DataTableThemeData(
|
data: const DataTableThemeData(
|
||||||
headingRowHeight: 0,
|
headingRowHeight: 0, // Header im Body ausblenden
|
||||||
dataRowMinHeight: 30,
|
dataRowMinHeight: 30,
|
||||||
dataRowMaxHeight: 34,
|
dataRowMaxHeight: 34,
|
||||||
columnSpacing: 10,
|
columnSpacing: 10,
|
||||||
@@ -498,6 +507,15 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
return cells;
|
return cells;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// kleines Mono-Text-Widget für Zahlen
|
||||||
|
Widget _monoSmall(String s) => Text(
|
||||||
|
s,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: _fontSize,
|
||||||
|
fontFeatures: [FontFeature.tabularFigures()],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Widget _codeDropdown(int dayIndex, WorkDay day) {
|
Widget _codeDropdown(int dayIndex, WorkDay day) {
|
||||||
final value = day.code;
|
final value = day.code;
|
||||||
final values = <String?>[null, ...kAbsenceCodes];
|
final values = <String?>[null, ...kAbsenceCodes];
|
||||||
@@ -672,14 +690,6 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _monoSmall(String s) => Text(
|
|
||||||
s,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: _fontSize,
|
|
||||||
fontFeatures: [FontFeature.tabularFigures()],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
TextAlign _toTextAlign(Alignment a) {
|
TextAlign _toTextAlign(Alignment a) {
|
||||||
if (a == Alignment.centerRight) return TextAlign.right;
|
if (a == Alignment.centerRight) return TextAlign.right;
|
||||||
if (a == Alignment.centerLeft) return TextAlign.left;
|
if (a == Alignment.centerLeft) return TextAlign.left;
|
||||||
@@ -687,6 +697,23 @@ Widget _monoSmall(String s) => Text(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Minimaler Header-Table, damit die Kopfzeile fixiert bleiben kann.
|
||||||
|
/// Nutzt die gleichen Spalten wie der Body, aber ohne Rows.
|
||||||
|
class _HeaderOnlyDataTable extends StatelessWidget {
|
||||||
|
const _HeaderOnlyDataTable();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final state = context.findAncestorStateOfType<_MonthlyViewState>()!;
|
||||||
|
final cols = state._buildColumns();
|
||||||
|
return DataTable(
|
||||||
|
showCheckboxColumn: false,
|
||||||
|
columns: cols,
|
||||||
|
rows: const <DataRow>[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _MonthHeader extends StatelessWidget {
|
class _MonthHeader extends StatelessWidget {
|
||||||
final DateTime month;
|
final DateTime month;
|
||||||
final VoidCallback onPrev;
|
final VoidCallback onPrev;
|
||||||
|
|||||||
Reference in New Issue
Block a user