Common Date and Time fomatters
This commit is contained in:
parent
4811621dc5
commit
d32449d096
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:tasq/utils/app_time.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../models/notification_item.dart';
|
import '../../models/notification_item.dart';
|
||||||
|
|
@ -13,7 +14,6 @@ import '../../providers/profile_provider.dart';
|
||||||
import '../../providers/tasks_provider.dart';
|
import '../../providers/tasks_provider.dart';
|
||||||
import '../../providers/tickets_provider.dart';
|
import '../../providers/tickets_provider.dart';
|
||||||
import '../../providers/typing_provider.dart';
|
import '../../providers/typing_provider.dart';
|
||||||
import '../../utils/app_time.dart';
|
|
||||||
import '../../widgets/mono_text.dart';
|
import '../../widgets/mono_text.dart';
|
||||||
import '../../widgets/responsive_body.dart';
|
import '../../widgets/responsive_body.dart';
|
||||||
import '../../widgets/tasq_adaptive_list.dart';
|
import '../../widgets/tasq_adaptive_list.dart';
|
||||||
|
|
@ -203,7 +203,7 @@ class _TasksListScreenState extends ConsumerState<TasksListScreen> {
|
||||||
label: Text(
|
label: Text(
|
||||||
_selectedDateRange == null
|
_selectedDateRange == null
|
||||||
? 'Date range'
|
? 'Date range'
|
||||||
: _formatDateRange(_selectedDateRange!),
|
: AppTime.formatDateRange(_selectedDateRange!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_hasTaskFilters)
|
if (_hasTaskFilters)
|
||||||
|
|
@ -636,17 +636,6 @@ Map<String, int> _taskStatusCounts(List<Task> tasks) {
|
||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDateRange(DateTimeRange range) {
|
|
||||||
return '${_formatDate(range.start)} - ${_formatDate(range.end)}';
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatDate(DateTime value) {
|
|
||||||
final year = value.year.toString().padLeft(4, '0');
|
|
||||||
final month = value.month.toString().padLeft(2, '0');
|
|
||||||
final day = value.day.toString().padLeft(2, '0');
|
|
||||||
return '$year-$month-$day';
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StatusSummaryRow extends StatelessWidget {
|
class _StatusSummaryRow extends StatelessWidget {
|
||||||
const _StatusSummaryRow({required this.counts});
|
const _StatusSummaryRow({required this.counts});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:tasq/utils/app_time.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
@ -809,32 +810,6 @@ class _TicketDetailScreenState extends ConsumerState<TicketDetailScreen> {
|
||||||
return office?.name ?? ticket.officeId;
|
return office?.name ?? ticket.officeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDate(DateTime value) {
|
|
||||||
final local = value.toLocal();
|
|
||||||
final monthNames = [
|
|
||||||
'Jan',
|
|
||||||
'Feb',
|
|
||||||
'Mar',
|
|
||||||
'Apr',
|
|
||||||
'May',
|
|
||||||
'Jun',
|
|
||||||
'Jul',
|
|
||||||
'Aug',
|
|
||||||
'Sep',
|
|
||||||
'Oct',
|
|
||||||
'Nov',
|
|
||||||
'Dec',
|
|
||||||
];
|
|
||||||
final month = monthNames[local.month - 1];
|
|
||||||
final day = local.day.toString().padLeft(2, '0');
|
|
||||||
final year = local.year.toString();
|
|
||||||
final hour24 = local.hour;
|
|
||||||
final hour12 = hour24 % 12 == 0 ? 12 : hour24 % 12;
|
|
||||||
final minute = local.minute.toString().padLeft(2, '0');
|
|
||||||
final ampm = hour24 >= 12 ? 'PM' : 'AM';
|
|
||||||
return '$month $day, $year $hour12:$minute $ampm';
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showTimelineDialog(BuildContext context, Ticket ticket) async {
|
Future<void> _showTimelineDialog(BuildContext context, Ticket ticket) async {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
@ -866,7 +841,7 @@ class _TicketDetailScreenState extends ConsumerState<TicketDetailScreen> {
|
||||||
Widget _timelineRow(String label, DateTime? value) {
|
Widget _timelineRow(String label, DateTime? value) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 8),
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
child: Text('$label: ${value == null ? '—' : _formatDate(value)}'),
|
child: Text('$label: ${value == null ? '—' : AppTime.formatDate(value)}'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:tasq/utils/app_time.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../models/office.dart';
|
import '../../models/office.dart';
|
||||||
|
|
@ -10,7 +11,6 @@ import '../../providers/notifications_provider.dart';
|
||||||
import '../../providers/profile_provider.dart';
|
import '../../providers/profile_provider.dart';
|
||||||
import '../../providers/tickets_provider.dart';
|
import '../../providers/tickets_provider.dart';
|
||||||
import '../../providers/typing_provider.dart';
|
import '../../providers/typing_provider.dart';
|
||||||
import '../../utils/app_time.dart';
|
|
||||||
import '../../widgets/mono_text.dart';
|
import '../../widgets/mono_text.dart';
|
||||||
import '../../widgets/responsive_body.dart';
|
import '../../widgets/responsive_body.dart';
|
||||||
import '../../widgets/tasq_adaptive_list.dart';
|
import '../../widgets/tasq_adaptive_list.dart';
|
||||||
|
|
@ -148,7 +148,7 @@ class _TicketsListScreenState extends ConsumerState<TicketsListScreen> {
|
||||||
label: Text(
|
label: Text(
|
||||||
_selectedDateRange == null
|
_selectedDateRange == null
|
||||||
? 'Date range'
|
? 'Date range'
|
||||||
: _formatDateRange(_selectedDateRange!),
|
: AppTime.formatDateRange(_selectedDateRange!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_hasTicketFilters)
|
if (_hasTicketFilters)
|
||||||
|
|
@ -500,17 +500,6 @@ Map<String, int> _statusCounts(List<Ticket> tickets) {
|
||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDateRange(DateTimeRange range) {
|
|
||||||
return '${_formatDate(range.start)} - ${_formatDate(range.end)}';
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatDate(DateTime value) {
|
|
||||||
final year = value.year.toString().padLeft(4, '0');
|
|
||||||
final month = value.month.toString().padLeft(2, '0');
|
|
||||||
final day = value.day.toString().padLeft(2, '0');
|
|
||||||
return '$year-$month-$day';
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StatusSummaryRow extends StatelessWidget {
|
class _StatusSummaryRow extends StatelessWidget {
|
||||||
const _StatusSummaryRow({required this.counts});
|
const _StatusSummaryRow({required this.counts});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
|
|
||||||
|
|
@ -27,4 +28,47 @@ class AppTime {
|
||||||
static DateTime parse(String value) {
|
static DateTime parse(String value) {
|
||||||
return toAppTime(DateTime.parse(value));
|
return toAppTime(DateTime.parse(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts a [DateTime] into a human-readable short date string.
|
||||||
|
///
|
||||||
|
/// Example: **Jan 05, 2025**. This matches the format previously used by
|
||||||
|
/// `_formatDate` helpers across multiple screens.
|
||||||
|
static String formatDate(DateTime value) {
|
||||||
|
const months = [
|
||||||
|
'Jan',
|
||||||
|
'Feb',
|
||||||
|
'Mar',
|
||||||
|
'Apr',
|
||||||
|
'May',
|
||||||
|
'Jun',
|
||||||
|
'Jul',
|
||||||
|
'Aug',
|
||||||
|
'Sep',
|
||||||
|
'Oct',
|
||||||
|
'Nov',
|
||||||
|
'Dec',
|
||||||
|
];
|
||||||
|
final month = months[value.month - 1];
|
||||||
|
final day = value.day.toString().padLeft(2, '0');
|
||||||
|
return '$month $day, ${value.year}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Formats a [DateTimeRange] as ``start - end`` using [formatDate].
|
||||||
|
static String formatDateRange(DateTimeRange range) {
|
||||||
|
return '${formatDate(range.start)} - ${formatDate(range.end)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders a [DateTime] in 12‑hour clock notation with AM/PM suffix.
|
||||||
|
///
|
||||||
|
/// Example: **08:30 PM**. Used primarily in workforce-related screens.
|
||||||
|
static String formatTime(DateTime value) {
|
||||||
|
final rawHour = value.hour;
|
||||||
|
final hour = (rawHour % 12 == 0 ? 12 : rawHour % 12).toString().padLeft(
|
||||||
|
2,
|
||||||
|
'0',
|
||||||
|
);
|
||||||
|
final minute = value.minute.toString().padLeft(2, '0');
|
||||||
|
final suffix = rawHour >= 12 ? 'PM' : 'AM';
|
||||||
|
return '$hour:$minute $suffix';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
40
test/app_time_test.dart
Normal file
40
test/app_time_test.dart
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:tasq/utils/app_time.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
// ensure timezone is initialized (no-op if already done)
|
||||||
|
AppTime.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formatDate produces correct string', () {
|
||||||
|
final date = DateTime(2025, 1, 5);
|
||||||
|
expect(AppTime.formatDate(date), 'Jan 05, 2025');
|
||||||
|
|
||||||
|
final date2 = DateTime(2021, 12, 31);
|
||||||
|
expect(AppTime.formatDate(date2), 'Dec 31, 2021');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formatDateRange composes two dates', () {
|
||||||
|
final start = DateTime(2023, 3, 1);
|
||||||
|
final end = DateTime(2023, 3, 15);
|
||||||
|
expect(
|
||||||
|
AppTime.formatDateRange(DateTimeRange(start: start, end: end)),
|
||||||
|
'Mar 01, 2023 - Mar 15, 2023',
|
||||||
|
);
|
||||||
|
|
||||||
|
// identical start/end
|
||||||
|
expect(
|
||||||
|
AppTime.formatDateRange(DateTimeRange(start: start, end: start)),
|
||||||
|
'Mar 01, 2023 - Mar 01, 2023',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('formatTime outputs 12-hour clock with suffix', () {
|
||||||
|
expect(AppTime.formatTime(DateTime(2020, 1, 1, 0, 0)), '12:00 AM');
|
||||||
|
expect(AppTime.formatTime(DateTime(2020, 1, 1, 9, 5)), '09:05 AM');
|
||||||
|
expect(AppTime.formatTime(DateTime(2020, 1, 1, 12, 0)), '12:00 PM');
|
||||||
|
expect(AppTime.formatTime(DateTime(2020, 1, 1, 23, 59)), '11:59 PM');
|
||||||
|
});
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user