3cb980b629
Security — enforce write authorization server-side (was UI/RPC-only): - it_service_requests RLS: block cross-office read/edit + self-approve (QA-015) - pass_slips RLS: owner can complete but not self-approve (QA-046) - swap_requests RLS: scope select/update to participants + admin (QA-047) - storage: tighten it_service_attachments + task_attachments write/delete (QA-027) - admin_user_management edge function: allow programmers to manage users (QA-016) Fixes: - workforce generator "uncovered shifts" false alarms (QA-043/044) - network-map VLAN + New-location dialog validation, disabled-until-valid (QA-048) - de-flake time-of-day-dependent dashboard metrics test (QA-045) Toolchain: - upgrade to Flutter 3.47.5 / Dart 3.13.4; font_awesome_flutter 11.0.0, flutter_quill 11.6.0, pdfrx 2.6.5; clear resulting deprecations (QA-002) analyze clean; 139 tests pass; web build succeeds. Report + evidence in docs/qa/. Note: also carries the in-progress Brick model cleanup already present in the working tree. QA-001 (AI keys public in the build) is deferred by owner decision. Co-Authored-By: claude-flow <ruv@ruv.net>
100 lines
2.9 KiB
Dart
100 lines
2.9 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:timezone/data/latest.dart' as tz;
|
||
import 'package:timezone/timezone.dart' as tz;
|
||
|
||
class AppTime {
|
||
static bool _initialized = false;
|
||
|
||
static void initialize({String location = 'Asia/Manila'}) {
|
||
if (_initialized) return;
|
||
tz.initializeTimeZones();
|
||
tz.setLocalLocation(tz.getLocation(location));
|
||
_initialized = true;
|
||
}
|
||
|
||
static DateTime now() {
|
||
return tz.TZDateTime.now(tz.local);
|
||
}
|
||
|
||
static DateTime nowUtc() {
|
||
return now().toUtc();
|
||
}
|
||
|
||
static DateTime toAppTime(DateTime value) {
|
||
final utc = value.isUtc ? value : value.toUtc();
|
||
return tz.TZDateTime.from(utc, tz.local);
|
||
}
|
||
|
||
static DateTime parse(String value) {
|
||
return toAppTime(DateTime.parse(value));
|
||
}
|
||
|
||
/// "Just now", "5m ago", "3h ago", "2d ago", then a short date after a week.
|
||
static String relative(DateTime value) {
|
||
final diff = now().difference(value);
|
||
if (diff.inMinutes < 1) return 'Just now';
|
||
if (diff.inMinutes < 60) return '${diff.inMinutes}m ago';
|
||
if (diff.inHours < 24) return '${diff.inHours}h ago';
|
||
if (diff.inDays < 7) return '${diff.inDays}d ago';
|
||
return formatDate(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.
|
||
/// Creates a [DateTime] in the app timezone (Asia/Manila) from date/time components.
|
||
///
|
||
/// Use this instead of [DateTime] constructor when building a Manila-aware
|
||
/// timestamp from separate year/month/day/hour/minute values so that
|
||
/// `.toUtc()` correctly accounts for the +08:00 offset.
|
||
static DateTime fromComponents({
|
||
required int year,
|
||
required int month,
|
||
required int day,
|
||
int hour = 0,
|
||
int minute = 0,
|
||
}) {
|
||
return tz.TZDateTime(tz.local, year, month, day, hour, minute);
|
||
}
|
||
|
||
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';
|
||
}
|
||
}
|