Monthy calculation
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import '../models/work_day.dart';
|
import '../models/work_day.dart';
|
||||||
|
|||||||
+244
-74
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async'; // << neu: für Timer (Debounce)
|
import 'dart:async';
|
||||||
|
import 'dart:convert'; // <-- für jsonEncode (Debug)
|
||||||
import 'dart:ui' show FontFeature;
|
import 'dart:ui' show FontFeature;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@@ -27,6 +28,9 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
static const double _wNumber = 76;
|
static const double _wNumber = 76;
|
||||||
static const double _fontSize = 12;
|
static const double _fontSize = 12;
|
||||||
|
|
||||||
|
// API Base (nur für Debug-Ausgabe)
|
||||||
|
static const String _apiUrl = 'https://api.windesign.at/workinghours.php';
|
||||||
|
|
||||||
// Codes, die Zeiten leeren/sperren
|
// Codes, die Zeiten leeren/sperren
|
||||||
static const Set<String> _lockCodes = {'G', 'K', 'U', 'SU'};
|
static const Set<String> _lockCodes = {'G', 'K', 'U', 'SU'};
|
||||||
|
|
||||||
@@ -34,7 +38,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
final Set<String> _invalidCells = <String>{};
|
final Set<String> _invalidCells = <String>{};
|
||||||
final Map<String, FocusNode> _focusNodes = {};
|
final Map<String, FocusNode> _focusNodes = {};
|
||||||
final Map<String, TextEditingController> _controllers = {};
|
final Map<String, TextEditingController> _controllers = {};
|
||||||
FocusNode _nodeFor(String key) => _focusNodes.putIfAbsent(key, () => FocusNode());
|
FocusNode _nodeFor(String key) =>
|
||||||
|
_focusNodes.putIfAbsent(key, () => FocusNode());
|
||||||
TextEditingController _controllerFor(String key, String initial) =>
|
TextEditingController _controllerFor(String key, String initial) =>
|
||||||
_controllers.putIfAbsent(key, () => TextEditingController(text: initial));
|
_controllers.putIfAbsent(key, () => TextEditingController(text: initial));
|
||||||
|
|
||||||
@@ -68,11 +73,12 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
late final ScrollController _vCtrl;
|
late final ScrollController _vCtrl;
|
||||||
bool _syncingH = false;
|
bool _syncingH = false;
|
||||||
|
|
||||||
// --- NEU: Debounce + Save-Status pro Zeile ---
|
// Save-Status pro Zeile
|
||||||
final Map<int, Timer> _saveTimers = {}; // rowIndex -> Timer
|
final Map<int, Timer> _saveTimers = {}; // rowIndex -> Timer
|
||||||
final Set<int> _savingRows = {}; // Zeilen, die gerade speichern
|
final Set<int> _savingRows = {}; // Zeilen, die gerade speichern
|
||||||
final Set<int> _justSavedRows = {}; // Zeilen, die eben gespeichert haben (Häkchen kurz anzeigen)
|
final Set<int> _justSavedRows =
|
||||||
final Map<int, String> _rowSaveError = {}; // Zeilenfehler
|
{}; // Zeilen, die eben gespeichert haben (Häkchen kurz anzeigen)
|
||||||
|
final Map<int, String> _rowSaveError = {}; // Zeilenfehler
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -118,6 +124,9 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadMonth(DateTime m) async {
|
Future<void> _loadMonth(DateTime m) async {
|
||||||
|
// DEBUG
|
||||||
|
print('[monthly] LOAD month=${m.year}-${m.month.toString().padLeft(2, '0')}');
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_loading = true;
|
_loading = true;
|
||||||
_error = null;
|
_error = null;
|
||||||
@@ -125,14 +134,14 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
final results = await Future.wait([
|
final results = await Future.wait([
|
||||||
_bookingApi.getBookingList(m), // List<WorkDay>
|
_bookingApi.getBookingList(m), // List<WorkDay>
|
||||||
_dailyApi.getDailyMinutes(), // Map<int,int>
|
_dailyApi.getDailyMinutes(), // Map<int,int>
|
||||||
_bookingApi.getMonthStart(m), // MonthStart
|
_bookingApi.getMonthStart(m), // MonthStart
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final apiDays = results[0] as List<WorkDay>;
|
final apiDays = results[0] as List<WorkDay>;
|
||||||
final plan = results[1] as Map<int, int>;
|
final plan = results[1] as Map<int, int>;
|
||||||
final mStart = results[2] as MonthStart;
|
final mStart = results[2] as MonthStart;
|
||||||
|
|
||||||
final holidayMap = buildHolidayMapAT(m.year);
|
final holidayMap = buildHolidayMapAT(m.year);
|
||||||
|
|
||||||
@@ -140,10 +149,12 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
final withTargets = filled.map((d) {
|
final withTargets = filled.map((d) {
|
||||||
final isHoliday = holidayMap.containsKey(ymd(d.date));
|
final isHoliday = holidayMap.containsKey(ymd(d.date));
|
||||||
// Feiertage haben immer Soll 0
|
// Feiertage haben immer Soll 0
|
||||||
final baseTarget = isHoliday ? 0 : (plan[d.date.weekday] ?? d.targetMinutes);
|
final baseTarget =
|
||||||
|
isHoliday ? 0 : (plan[d.date.weekday] ?? d.targetMinutes);
|
||||||
// U/SU/K ebenfalls Soll 0
|
// U/SU/K ebenfalls Soll 0
|
||||||
final code = d.code;
|
final code = d.code;
|
||||||
final target = (code == 'U' || code == 'SU' || code == 'K') ? 0 : baseTarget;
|
final target =
|
||||||
|
(code == 'U' || code == 'SU' || code == 'K') ? 0 : baseTarget;
|
||||||
return WorkDay(
|
return WorkDay(
|
||||||
date: d.date,
|
date: d.date,
|
||||||
intervals: d.intervals,
|
intervals: d.intervals,
|
||||||
@@ -158,7 +169,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
_days = withTargets;
|
_days = withTargets;
|
||||||
_dailyPlan = plan;
|
_dailyPlan = plan;
|
||||||
_monthStartInfo = mStart;
|
_monthStartInfo = mStart;
|
||||||
_carryBaseMinutes = mStart.carryBaseMinutes; // starthours + overtime + correction
|
_carryBaseMinutes =
|
||||||
|
mStart.carryBaseMinutes; // starthours + overtime + correction
|
||||||
_loading = false;
|
_loading = false;
|
||||||
|
|
||||||
// Status-Maps leeren (neuer Monat)
|
// Status-Maps leeren (neuer Monat)
|
||||||
@@ -167,8 +179,16 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
_rowSaveError.clear();
|
_rowSaveError.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// DEBUG: geladene Monats-Startdaten
|
||||||
|
print('[monthly] monthStart loaded for ${ymd(_monthStart)} '
|
||||||
|
'carryBase=${_carryBaseMinutes} '
|
||||||
|
'startvacation=${_monthStartInfo?.startVacationUnits ?? 0} '
|
||||||
|
'overtime=${_monthStartInfo?.overtimeMinutes ?? 0} '
|
||||||
|
'correction=${_monthStartInfo?.correctionMinutes ?? 0}');
|
||||||
|
|
||||||
_syncControllersWithDays();
|
_syncControllersWithDays();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print('[monthly] LOAD ERROR: $e');
|
||||||
setState(() {
|
setState(() {
|
||||||
_error = e.toString();
|
_error = e.toString();
|
||||||
_loading = false;
|
_loading = false;
|
||||||
@@ -193,7 +213,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Color? _rowColorFor(WorkDay d, {required Color? holidayBg, required Color? weekendBg}) {
|
Color? _rowColorFor(WorkDay d,
|
||||||
|
{required Color? holidayBg, required Color? weekendBg}) {
|
||||||
switch (d.code) {
|
switch (d.code) {
|
||||||
case 'G':
|
case 'G':
|
||||||
return const Color(0xFFBFBFFF); // Gleitzeit
|
return const Color(0xFFBFBFFF); // Gleitzeit
|
||||||
@@ -202,12 +223,13 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
case 'SU':
|
case 'SU':
|
||||||
return const Color(0xFF7F7FFF); // Sonderurlaub
|
return const Color(0xFF7F7FFF); // Sonderurlaub
|
||||||
case 'K':
|
case 'K':
|
||||||
return Colors.yellow; // Krankenstand
|
return Colors.yellow; // Krankenstand
|
||||||
case 'T':
|
case 'T':
|
||||||
return Colors.red; // Training
|
return Colors.red; // Training
|
||||||
}
|
}
|
||||||
final isHoliday = _holidays.containsKey(ymd(d.date));
|
final isHoliday = _holidays.containsKey(ymd(d.date));
|
||||||
final isWeekend = d.date.weekday == DateTime.saturday || d.date.weekday == DateTime.sunday;
|
final isWeekend = d.date.weekday == DateTime.saturday ||
|
||||||
|
d.date.weekday == DateTime.sunday;
|
||||||
if (isHoliday) return holidayBg;
|
if (isHoliday) return holidayBg;
|
||||||
if (isWeekend) return weekendBg;
|
if (isWeekend) return weekendBg;
|
||||||
return null;
|
return null;
|
||||||
@@ -215,7 +237,6 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final headerColumns = _buildColumns();
|
|
||||||
final bodyColumns = _buildColumns();
|
final bodyColumns = _buildColumns();
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
@@ -223,7 +244,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
final weekendBg = Colors.grey.withOpacity(0.30);
|
final weekendBg = Colors.grey.withOpacity(0.30);
|
||||||
|
|
||||||
// Live-„Effective“-Tage (inkl. Eingabetexte + Sperrlogik)
|
// Live-„Effective“-Tage (inkl. Eingabetexte + Sperrlogik)
|
||||||
final effectiveDays = List<WorkDay>.generate(_days.length, (i) => _effectiveDay(i, _days[i]));
|
final effectiveDays =
|
||||||
|
List<WorkDay>.generate(_days.length, (i) => _effectiveDay(i, _days[i]));
|
||||||
|
|
||||||
// Tagesdifferenzen & kumuliert (Start mit Monatssaldo aus API)
|
// Tagesdifferenzen & kumuliert (Start mit Monatssaldo aus API)
|
||||||
final diffs = <int>[];
|
final diffs = <int>[];
|
||||||
@@ -257,7 +279,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
final correctionMin = _monthStartInfo?.correctionMinutes ?? 0;
|
final correctionMin = _monthStartInfo?.correctionMinutes ?? 0;
|
||||||
final nextCarryMin = cumulative.isNotEmpty ? cumulative.last : _carryBaseMinutes;
|
final nextCarryMin =
|
||||||
|
cumulative.isNotEmpty ? cumulative.last : _carryBaseMinutes;
|
||||||
|
|
||||||
final rows = List<DataRow>.generate(_days.length, (i) {
|
final rows = List<DataRow>.generate(_days.length, (i) {
|
||||||
final day = effectiveDays[i];
|
final day = effectiveDays[i];
|
||||||
@@ -274,8 +297,10 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
_MonthHeader(
|
_MonthHeader(
|
||||||
month: _monthStart,
|
month: _monthStart,
|
||||||
loading: _loading,
|
loading: _loading,
|
||||||
onPrev: () => _loadMonth(DateTime(_monthStart.year, _monthStart.month - 1, 1)),
|
onPrev: () =>
|
||||||
onNext: () => _loadMonth(DateTime(_monthStart.year, _monthStart.month + 1, 1)),
|
_loadMonth(DateTime(_monthStart.year, _monthStart.month - 1, 1)),
|
||||||
|
onNext: () =>
|
||||||
|
_loadMonth(DateTime(_monthStart.year, _monthStart.month + 1, 1)),
|
||||||
onPickMonth: () async {
|
onPickMonth: () async {
|
||||||
final picked = await showDatePicker(
|
final picked = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -300,8 +325,10 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text('Fehler beim Laden:', style: TextStyle(fontSize: _fontSize)),
|
Text('Fehler beim Laden:',
|
||||||
SelectableText(_error ?? '', style: const TextStyle(fontSize: _fontSize)),
|
style: TextStyle(fontSize: _fontSize)),
|
||||||
|
SelectableText(_error ?? '',
|
||||||
|
style: const TextStyle(fontSize: _fontSize)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
@@ -324,7 +351,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
data: DataTableThemeData(
|
data: DataTableThemeData(
|
||||||
headingRowHeight: 30,
|
headingRowHeight: 30,
|
||||||
columnSpacing: 10,
|
columnSpacing: 10,
|
||||||
headingTextStyle: const TextStyle(fontWeight: FontWeight.w700, fontSize: _fontSize),
|
headingTextStyle: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700, fontSize: _fontSize),
|
||||||
),
|
),
|
||||||
child: const _HeaderOnlyDataTable(),
|
child: const _HeaderOnlyDataTable(),
|
||||||
),
|
),
|
||||||
@@ -397,7 +425,9 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<DataColumn> _buildColumns() {
|
List<DataColumn> _buildColumns() {
|
||||||
DataColumn c(String label, {Alignment align = Alignment.center, double? width}) => DataColumn(
|
DataColumn c(String label,
|
||||||
|
{Alignment align = Alignment.center, double? width}) =>
|
||||||
|
DataColumn(
|
||||||
label: SizedBox(
|
label: SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Align(
|
child: Align(
|
||||||
@@ -433,14 +463,16 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DataCell> _buildEditableCells(int dayIndex, WorkDay day, int runningDiff) {
|
List<DataCell> _buildEditableCells(
|
||||||
final leftLabel = rightDayLabel(day.date); // "Mo 01.09."
|
int dayIndex, WorkDay day, int runningDiff) {
|
||||||
final rightLabel = leftDayLabel(day.date); // "01.09. Mo"
|
final leftLabel = rightDayLabel(day.date); // "Mo 01.09."
|
||||||
|
final rightLabel = leftDayLabel(day.date); // "01.09. Mo"
|
||||||
final hName = _holidays[ymd(day.date)] ?? '';
|
final hName = _holidays[ymd(day.date)] ?? '';
|
||||||
|
|
||||||
final bool lockTimes = day.code != null && _lockCodes.contains(day.code);
|
final bool lockTimes = day.code != null && _lockCodes.contains(day.code);
|
||||||
final bool isHoliday = _holidays.containsKey(ymd(day.date));
|
final bool isHoliday = _holidays.containsKey(ymd(day.date));
|
||||||
final bool isWeekend = day.date.weekday == DateTime.saturday || day.date.weekday == DateTime.sunday;
|
final bool isWeekend = day.date.weekday == DateTime.saturday ||
|
||||||
|
day.date.weekday == DateTime.sunday;
|
||||||
final bool codeDisabled = isHoliday || isWeekend;
|
final bool codeDisabled = isHoliday || isWeekend;
|
||||||
|
|
||||||
String slotText(int slot, bool isStart) {
|
String slotText(int slot, bool isStart) {
|
||||||
@@ -480,7 +512,7 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
_rowStatusBadge(dayIndex), // << neu: Spinner/Häkchen/Fault
|
_rowStatusBadge(dayIndex), // Spinner/Häkchen/Fehler
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -603,7 +635,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
)));
|
)));
|
||||||
cells.add(DataCell(SizedBox(
|
cells.add(DataCell(SizedBox(
|
||||||
width: _wNumber,
|
width: _wNumber,
|
||||||
child: Align(alignment: Alignment.centerRight, child: _monoSmall(diffSum)),
|
child:
|
||||||
|
Align(alignment: Alignment.centerRight, child: _monoSmall(diffSum)),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
// 17: Datum (linksbündig)
|
// 17: Datum (linksbündig)
|
||||||
@@ -615,7 +648,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
),
|
),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
assert(cells.length == 18, 'Row has ${cells.length} cells but expected 18.');
|
assert(
|
||||||
|
cells.length == 18, 'Row has ${cells.length} cells but expected 18.');
|
||||||
return cells;
|
return cells;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +662,7 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- NEU: kleiner Status-Badge pro Zeile ---
|
// kleiner Status-Badge pro Zeile
|
||||||
Widget _rowStatusBadge(int row) {
|
Widget _rowStatusBadge(int row) {
|
||||||
if (_savingRows.contains(row)) {
|
if (_savingRows.contains(row)) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
@@ -638,7 +672,8 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (_rowSaveError.containsKey(row)) {
|
if (_rowSaveError.containsKey(row)) {
|
||||||
return Icon(Icons.error_outline, size: 14, color: Theme.of(context).colorScheme.error);
|
return Icon(Icons.error_outline,
|
||||||
|
size: 14, color: Theme.of(context).colorScheme.error);
|
||||||
}
|
}
|
||||||
if (_justSavedRows.contains(row)) {
|
if (_justSavedRows.contains(row)) {
|
||||||
return const Icon(Icons.check_circle, size: 14, color: Colors.green);
|
return const Icon(Icons.check_circle, size: 14, color: Colors.green);
|
||||||
@@ -657,20 +692,30 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
final label = v == null ? '—' : codeLabel(v); // langer Name im Dropdown
|
final label = v == null ? '—' : codeLabel(v); // langer Name im Dropdown
|
||||||
return DropdownMenuItem<String?>(
|
return DropdownMenuItem<String?>(
|
||||||
value: v,
|
value: v,
|
||||||
child: Text(label, textAlign: TextAlign.center, style: const TextStyle(fontSize: _fontSize)),
|
child: Text(label,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: _fontSize)),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
selectedItemBuilder: (context) {
|
selectedItemBuilder: (context) {
|
||||||
return values.map((v) {
|
return values.map((v) {
|
||||||
final shortText = v ?? '—'; // kurzer Name in der Tabelle
|
final shortText = v ?? '—'; // kurzer Name in der Tabelle
|
||||||
return Center(child: Text(shortText, style: const TextStyle(fontSize: _fontSize)));
|
return Center(
|
||||||
|
child:
|
||||||
|
Text(shortText, style: const TextStyle(fontSize: _fontSize)));
|
||||||
}).toList();
|
}).toList();
|
||||||
},
|
},
|
||||||
onChanged: disabled
|
onChanged: disabled
|
||||||
? null
|
? null
|
||||||
: (newCode) {
|
: (newCode) {
|
||||||
final d = _days[dayIndex];
|
final d = _days[dayIndex];
|
||||||
final bool willLock = newCode != null && _lockCodes.contains(newCode);
|
final bool willLock =
|
||||||
|
newCode != null && _lockCodes.contains(newCode);
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
print('[monthly] CODE change day=${ymd(d.date)} newCode=${newCode ?? '-'} '
|
||||||
|
'(lockTimes=$willLock)');
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
final newDays = List<WorkDay>.from(_days);
|
final newDays = List<WorkDay>.from(_days);
|
||||||
newDays[dayIndex] = WorkDay(
|
newDays[dayIndex] = WorkDay(
|
||||||
@@ -693,7 +738,7 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_scheduleSave(dayIndex); // << neu: debounce statt sofort speichern
|
_scheduleSave(dayIndex);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -715,6 +760,11 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
|
|
||||||
if (d.code != null && _lockCodes.contains(d.code)) return;
|
if (d.code != null && _lockCodes.contains(d.code)) return;
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
print('[monthly] TIME commit day=${ymd(d.date)} '
|
||||||
|
'slot=${slot + 1} ${isStart ? 'start' : 'end'}="$text" '
|
||||||
|
'parsed=${t != null ? fmtTimeOfDay(t) : '-'}');
|
||||||
|
|
||||||
final starts = List<TimeOfDay?>.filled(5, null);
|
final starts = List<TimeOfDay?>.filled(5, null);
|
||||||
final ends = List<TimeOfDay?>.filled(5, null);
|
final ends = List<TimeOfDay?>.filled(5, null);
|
||||||
|
|
||||||
@@ -747,21 +797,20 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
_days = newDays;
|
_days = newDays;
|
||||||
});
|
});
|
||||||
|
|
||||||
_scheduleSave(dayIndex); // << neu: debounce
|
_scheduleSave(dayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NEU: Debounce + Save-Status-Handling ---
|
// Debounce für Tages-Save
|
||||||
void _scheduleSave(int dayIndex) {
|
void _scheduleSave(int dayIndex) {
|
||||||
// alten Timer stoppen
|
|
||||||
_saveTimers[dayIndex]?.cancel();
|
_saveTimers[dayIndex]?.cancel();
|
||||||
// neuen Timer setzen
|
|
||||||
_saveTimers[dayIndex] = Timer(const Duration(milliseconds: 500), () async {
|
_saveTimers[dayIndex] = Timer(const Duration(milliseconds: 500), () async {
|
||||||
|
// DEBUG
|
||||||
|
print('[monthly] SAVE (debounced) row=$dayIndex date=${ymd(_days[dayIndex].date)}');
|
||||||
await _saveDay(dayIndex);
|
await _saveDay(dayIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveDay(int dayIndex) async {
|
Future<void> _saveDay(int dayIndex) async {
|
||||||
// evtl. laufenden Debounce-Timer löschen (wir speichern jetzt)
|
|
||||||
_saveTimers[dayIndex]?.cancel();
|
_saveTimers[dayIndex]?.cancel();
|
||||||
_saveTimers.remove(dayIndex);
|
_saveTimers.remove(dayIndex);
|
||||||
|
|
||||||
@@ -773,7 +822,19 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final effective = _effectiveDay(dayIndex, _days[dayIndex]);
|
final effective = _effectiveDay(dayIndex, _days[dayIndex]);
|
||||||
await _bookingApi.saveDay(effective);
|
|
||||||
|
// --- DEBUG: booking/saveDay Aufruf + Payload spiegeln ---
|
||||||
|
final payload = _debugDayPayload(effective);
|
||||||
|
print('[api] POST $_apiUrl');
|
||||||
|
print('[api] body=${jsonEncode(payload)}');
|
||||||
|
|
||||||
|
await _bookingApi.saveDay(effective); // tatsächlicher API-Call
|
||||||
|
|
||||||
|
// DEBUG
|
||||||
|
print('[api] booking/saveDay OK date=${ymd(effective.date)}');
|
||||||
|
|
||||||
|
// Folgemonat mitrechnen/speichern
|
||||||
|
await _saveMonthStartNow();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -789,12 +850,12 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
print('[api] booking/saveDay ERROR: $e');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_savingRows.remove(dayIndex);
|
_savingRows.remove(dayIndex);
|
||||||
_rowSaveError[dayIndex] = e.toString();
|
_rowSaveError[dayIndex] = e.toString();
|
||||||
});
|
});
|
||||||
// optional non-intrusive Snack
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text('Speichern fehlgeschlagen: $e'),
|
content: Text('Speichern fehlgeschlagen: $e'),
|
||||||
@@ -805,6 +866,59 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _saveMonthStartNow() async {
|
||||||
|
// 1) Folgemonat bestimmen
|
||||||
|
final nextMonth = DateTime(_monthStart.year, _monthStart.month + 1, 1);
|
||||||
|
|
||||||
|
// 2) Effektive Tage (inkl. Eingabefelder berücksichtigen)
|
||||||
|
final effectiveDays =
|
||||||
|
List<WorkDay>.generate(_days.length, (i) => _effectiveDay(i, _days[i]));
|
||||||
|
|
||||||
|
// 3) Saldo des Monats berechnen (Start + Summe(IST - SOLL))
|
||||||
|
int saldoMin = _carryBaseMinutes;
|
||||||
|
for (final d in effectiveDays) {
|
||||||
|
final worked = _workedFor(d);
|
||||||
|
final target = d.targetMinutes;
|
||||||
|
saldoMin += worked - target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Urlaubs-Übertrag berechnen
|
||||||
|
final startVacation = _monthStartInfo?.startVacationUnits ?? 0;
|
||||||
|
final usedVacation = _days.where((d) => d.code == 'U').length;
|
||||||
|
final carryVacation = startVacation - usedVacation;
|
||||||
|
|
||||||
|
// 5) Overtime/Correction aus Startdaten übernehmen
|
||||||
|
final overtime = _monthStartInfo?.overtimeMinutes ?? 0;
|
||||||
|
final correction = _monthStartInfo?.correctionMinutes ?? 0;
|
||||||
|
|
||||||
|
// --- DEBUG: monthlybooking/saveStart Aufruf + Payload spiegeln ---
|
||||||
|
final payload = {
|
||||||
|
'module': 'monthlybooking',
|
||||||
|
'function': 'saveStart',
|
||||||
|
'date':
|
||||||
|
'${nextMonth.year.toString().padLeft(4, '0')}-${nextMonth.month.toString().padLeft(2, '0')}-01',
|
||||||
|
'starthours': saldoMin,
|
||||||
|
'startvacation': carryVacation,
|
||||||
|
'overtime': overtime,
|
||||||
|
'correction': correction,
|
||||||
|
};
|
||||||
|
print('[api] POST $_apiUrl');
|
||||||
|
print('[api] body=${jsonEncode(payload)}');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _bookingApi.saveMonthStart(
|
||||||
|
nextMonth,
|
||||||
|
starthours: saldoMin,
|
||||||
|
startvacation: carryVacation,
|
||||||
|
overtime: overtime,
|
||||||
|
correction: correction,
|
||||||
|
);
|
||||||
|
print('[api] monthlybooking/saveStart OK for ${ymd(nextMonth)}');
|
||||||
|
} catch (e) {
|
||||||
|
print('[api] monthlybooking/saveStart ERROR: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _syncControllersWithDays() {
|
void _syncControllersWithDays() {
|
||||||
for (int i = 0; i < _days.length; i++) {
|
for (int i = 0; i < _days.length; i++) {
|
||||||
final lock = _days[i].code != null && _lockCodes.contains(_days[i].code);
|
final lock = _days[i].code != null && _lockCodes.contains(_days[i].code);
|
||||||
@@ -823,8 +937,12 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
: '';
|
: '';
|
||||||
final cs = _controllers[sKey];
|
final cs = _controllers[sKey];
|
||||||
final ce = _controllers[eKey];
|
final ce = _controllers[eKey];
|
||||||
if (cs != null && cs.text != sText && !(_focusNodes[sKey]?.hasFocus ?? false)) cs.text = sText;
|
if (cs != null &&
|
||||||
if (ce != null && ce.text != eText && !(_focusNodes[eKey]?.hasFocus ?? false)) ce.text = eText;
|
cs.text != sText &&
|
||||||
|
!(_focusNodes[sKey]?.hasFocus ?? false)) cs.text = sText;
|
||||||
|
if (ce != null &&
|
||||||
|
ce.text != eText &&
|
||||||
|
!(_focusNodes[eKey]?.hasFocus ?? false)) ce.text = eText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -866,8 +984,12 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
final sText = _controllers[sKey]?.text;
|
final sText = _controllers[sKey]?.text;
|
||||||
final eText = _controllers[eKey]?.text;
|
final eText = _controllers[eKey]?.text;
|
||||||
|
|
||||||
final s = (sText != null && sText.isNotEmpty) ? parseTextHHMM(sText) : baseSlot(base, slot, true);
|
final s = (sText != null && sText.isNotEmpty)
|
||||||
final e = (eText != null && eText.isNotEmpty) ? parseTextHHMM(eText) : baseSlot(base, slot, false);
|
? parseTextHHMM(sText)
|
||||||
|
: baseSlot(base, slot, true);
|
||||||
|
final e = (eText != null && eText.isNotEmpty)
|
||||||
|
? parseTextHHMM(eText)
|
||||||
|
: baseSlot(base, slot, false);
|
||||||
|
|
||||||
if (s != null && e != null) intervals.add(WorkInterval(s, e));
|
if (s != null && e != null) intervals.add(WorkInterval(s, e));
|
||||||
}
|
}
|
||||||
@@ -929,6 +1051,40 @@ class _MonthlyViewState extends State<MonthlyView> {
|
|||||||
if (a == Alignment.centerLeft) return TextAlign.left;
|
if (a == Alignment.centerLeft) return TextAlign.left;
|
||||||
return TextAlign.center;
|
return TextAlign.center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------ DEBUG-Helper ------------------------
|
||||||
|
|
||||||
|
Map<String, dynamic> _debugDayPayload(WorkDay day) {
|
||||||
|
// Intervalle als 5 Slots wie im echten POST
|
||||||
|
final starts = List<String?>.filled(5, null);
|
||||||
|
final ends = List<String?>.filled(5, null);
|
||||||
|
for (int i = 0; i < day.intervals.length && i < 5; i++) {
|
||||||
|
starts[i] = fmtTimeOfDay(day.intervals[i].start);
|
||||||
|
ends[i] = fmtTimeOfDay(day.intervals[i].end);
|
||||||
|
}
|
||||||
|
final lock = _lockCodes.contains(day.code);
|
||||||
|
|
||||||
|
final y = day.date.year.toString().padLeft(4, '0');
|
||||||
|
final m = day.date.month.toString().padLeft(2, '0');
|
||||||
|
final d = day.date.day.toString().padLeft(2, '0');
|
||||||
|
|
||||||
|
return {
|
||||||
|
'module': 'booking',
|
||||||
|
'function': 'saveDay',
|
||||||
|
'date': '$y-$m-$d',
|
||||||
|
'code': day.code, // null → wird als NULL gespeichert
|
||||||
|
'come1': lock ? null : starts[0],
|
||||||
|
'leave1': lock ? null : ends[0],
|
||||||
|
'come2': lock ? null : starts[1],
|
||||||
|
'leave2': lock ? null : ends[1],
|
||||||
|
'come3': lock ? null : starts[2],
|
||||||
|
'leave3': lock ? null : ends[2],
|
||||||
|
'come4': lock ? null : starts[3],
|
||||||
|
'leave4': lock ? null : ends[3],
|
||||||
|
'come5': lock ? null : starts[4],
|
||||||
|
'leave5': lock ? null : ends[4],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kopfzeile als separate DataTable (fixiert)
|
/// Kopfzeile als separate DataTable (fixiert)
|
||||||
@@ -969,16 +1125,26 @@ class _MonthHeader extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||||
child: Row(children: [
|
child: Row(children: [
|
||||||
IconButton(onPressed: loading ? null : onPrev, icon: const Icon(Icons.chevron_left)),
|
IconButton(
|
||||||
|
onPressed: loading ? null : onPrev,
|
||||||
|
icon: const Icon(Icons.chevron_left)),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
onPressed: loading ? null : onPickMonth,
|
onPressed: loading ? null : onPickMonth,
|
||||||
icon: const Icon(Icons.calendar_month),
|
icon: const Icon(Icons.calendar_month),
|
||||||
label: Text(title, style: Theme.of(context).textTheme.titleLarge?.copyWith(fontSize: 16)),
|
label: Text(title,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleLarge
|
||||||
|
?.copyWith(fontSize: 16)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(onPressed: loading ? null : onReload, icon: const Icon(Icons.refresh)),
|
IconButton(
|
||||||
IconButton(onPressed: loading ? null : onNext, icon: const Icon(Icons.chevron_right)),
|
onPressed: loading ? null : onReload,
|
||||||
|
icon: const Icon(Icons.refresh)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: loading ? null : onNext,
|
||||||
|
icon: const Icon(Icons.chevron_right)),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -991,14 +1157,14 @@ class _MonthlySummaryFooter extends StatelessWidget {
|
|||||||
|
|
||||||
// Linke Spalte (Strings bereits formatiert)
|
// Linke Spalte (Strings bereits formatiert)
|
||||||
final String uebertragStartText; // Startsaldo (Minuten → HH:MM)
|
final String uebertragStartText; // Startsaldo (Minuten → HH:MM)
|
||||||
final String sollText; // Summe Soll (HH:MM)
|
final String sollText; // Summe Soll (HH:MM)
|
||||||
final String istText; // Summe Ist (HH:MM)
|
final String istText; // Summe Ist (HH:MM)
|
||||||
final String correctionText; // Correction (HH:MM, ±)
|
final String correctionText; // Correction (HH:MM, ±)
|
||||||
final String saldoText; // IST - SOLL (±HH:MM)
|
final String saldoText; // IST - SOLL (±HH:MM)
|
||||||
final String paidOvertimeText; // folgt später (—)
|
final String paidOvertimeText; // folgt später (—)
|
||||||
final String uebertragNextText; // letzter „Differenz gesamt“ (±HH:MM)
|
final String uebertragNextText; // letzter „Differenz gesamt“ (±HH:MM)
|
||||||
final String restUrlaubText; // startvacation (Zahl)
|
final String restUrlaubText; // startvacation (Zahl)
|
||||||
final String urlaubUebertragText;// startvacation - used
|
final String urlaubUebertragText; // startvacation - used
|
||||||
|
|
||||||
// Rechte Spalte (Counts)
|
// Rechte Spalte (Counts)
|
||||||
final int countGleitzeit;
|
final int countGleitzeit;
|
||||||
@@ -1036,16 +1202,17 @@ class _MonthlySummaryFooter extends StatelessWidget {
|
|||||||
// Monatslabels: Vormonat für Überträge/Resturlaub, aktueller Monat für Soll/Ist
|
// Monatslabels: Vormonat für Überträge/Resturlaub, aktueller Monat für Soll/Ist
|
||||||
final prev = DateTime(month.year, month.month - 1, 1);
|
final prev = DateTime(month.year, month.month - 1, 1);
|
||||||
final prevLabel = monthTitle(prev);
|
final prevLabel = monthTitle(prev);
|
||||||
final curLabel = monthLabel;
|
final curLabel = monthLabel;
|
||||||
|
|
||||||
// Layout-Konstanten
|
// Layout-Konstanten
|
||||||
const double leftValueWidth = 120; // Werte-Breite links (rechtsbündig)
|
const double leftValueWidth = 120; // Werte-Breite links (rechtsbündig)
|
||||||
const double rightValueWidth = 80; // Werte-Breite rechts (rechtsbündig)
|
const double rightValueWidth = 80; // Werte-Breite rechts (rechtsbündig)
|
||||||
const double colGap = 24; // Abstand zwischen Spalten
|
const double colGap = 24; // Abstand zwischen Spalten
|
||||||
const double rowGap = 2; // Zeilenabstand
|
const double rowGap = 2; // Zeilenabstand
|
||||||
const double footerMaxWidth = 720; // maximale Footer-Breite
|
const double footerMaxWidth = 720; // maximale Footer-Breite
|
||||||
|
|
||||||
final labelStyle = TextStyle(fontSize: fontSize, color: Theme.of(context).colorScheme.onSurface);
|
final labelStyle = TextStyle(
|
||||||
|
fontSize: fontSize, color: Theme.of(context).colorScheme.onSurface);
|
||||||
final valueStyle = TextStyle(
|
final valueStyle = TextStyle(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontFeatures: const [FontFeature.tabularFigures()],
|
fontFeatures: const [FontFeature.tabularFigures()],
|
||||||
@@ -1097,7 +1264,8 @@ class _MonthlySummaryFooter extends StatelessWidget {
|
|||||||
width: leftValueWidth,
|
width: leftValueWidth,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Text(value, style: valueStyle, textAlign: TextAlign.right),
|
child:
|
||||||
|
Text(value, style: valueStyle, textAlign: TextAlign.right),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -1116,7 +1284,8 @@ class _MonthlySummaryFooter extends StatelessWidget {
|
|||||||
width: rightValueWidth,
|
width: rightValueWidth,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Text(value, style: valueStyle, textAlign: TextAlign.right),
|
child:
|
||||||
|
Text(value, style: valueStyle, textAlign: TextAlign.right),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
@@ -1158,7 +1327,8 @@ class _MonthlySummaryFooter extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: rightItems.map((e) => rightRow(e.$1, e.$2)).toList(),
|
children:
|
||||||
|
rightItems.map((e) => rightRow(e.$1, e.$2)).toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import file_picker
|
||||||
|
import path_provider_foundation
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
+158
-1
@@ -1,6 +1,14 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -41,6 +49,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.18.0"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4+2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -49,6 +65,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.8"
|
||||||
|
dbus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dbus
|
||||||
|
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.11"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -57,6 +81,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.3.3"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -70,11 +110,24 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.0"
|
version: "5.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: "1c2b787f99bdca1f3718543f81d38aa1b124817dfeb9fb196201bea85b6134bf"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.26"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -147,6 +200,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
open_filex:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: open_filex
|
||||||
|
sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.7.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -155,6 +216,78 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.15"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -240,6 +373,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.1"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.10.1"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.5.4 <4.0.0"
|
dart: ">=3.5.4 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.24.0"
|
||||||
|
|||||||
+4
-1
@@ -35,7 +35,10 @@ dependencies:
|
|||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
http: ^1.5.0
|
http: any
|
||||||
|
path_provider: any
|
||||||
|
open_filex: any
|
||||||
|
file_picker: any
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user