UI Enhancements in IT Service Request, Announcements, Workforce and notification fixes

This commit is contained in:
2026-03-22 18:00:10 +08:00
parent 049ab2c794
commit 872c2aab87
15 changed files with 1290 additions and 183 deletions
+72 -2
View File
@@ -80,6 +80,14 @@ final announcementCommentsProvider =
return wrapper.stream.map((result) => result.data);
});
/// Active banner announcements for the current user.
/// Returns only non-template announcements whose banner is currently in its
/// active time window ([Announcement.isBannerActive]).
final activeBannerAnnouncementsProvider = Provider<List<Announcement>>((ref) {
final all = ref.watch(announcementsProvider).valueOrNull ?? [];
return all.where((a) => !a.isTemplate && a.isBannerActive).toList();
});
final announcementsControllerProvider =
Provider<AnnouncementsController>((ref) {
final client = ref.watch(supabaseClientProvider);
@@ -100,6 +108,10 @@ class AnnouncementsController {
required List<String> visibleRoles,
bool isTemplate = false,
String? templateId,
bool bannerEnabled = false,
DateTime? bannerShowAt,
DateTime? bannerHideAt,
int? pushIntervalMinutes,
}) async {
final authorId = _client.auth.currentUser?.id;
if (authorId == null) return;
@@ -111,6 +123,10 @@ class AnnouncementsController {
'visible_roles': visibleRoles,
'is_template': isTemplate,
'template_id': templateId,
'banner_enabled': bannerEnabled,
'banner_show_at': bannerShowAt?.toUtc().toIso8601String(),
'banner_hide_at': bannerHideAt?.toUtc().toIso8601String(),
'push_interval_minutes': pushIntervalMinutes,
};
final result = await _client
@@ -123,6 +139,12 @@ class AnnouncementsController {
// Don't send notifications for templates (they are drafts for reuse)
if (isTemplate) return;
// Skip the one-time creation push when a scheduled banner push is
// configured. The banner scheduler will send the first push on its own
// interval, so firing an extra push here would result in two back-to-back
// notifications for the same announcement.
if (bannerEnabled && pushIntervalMinutes != null) return;
// Query users whose role matches visible_roles, excluding the author
try {
final profiles = await _client
@@ -161,6 +183,13 @@ class AnnouncementsController {
required String body,
required List<String> visibleRoles,
bool? isTemplate,
bool? bannerEnabled,
DateTime? bannerShowAt,
DateTime? bannerHideAt,
int? pushIntervalMinutes,
bool clearBannerShowAt = false,
bool clearBannerHideAt = false,
bool clearPushInterval = false,
}) async {
final payload = <String, dynamic>{
'title': title,
@@ -168,12 +197,53 @@ class AnnouncementsController {
'visible_roles': visibleRoles,
'updated_at': AppTime.nowUtc().toIso8601String(),
};
if (isTemplate != null) {
payload['is_template'] = isTemplate;
if (isTemplate != null) payload['is_template'] = isTemplate;
if (bannerEnabled != null) payload['banner_enabled'] = bannerEnabled;
if (bannerShowAt != null) {
payload['banner_show_at'] = bannerShowAt.toUtc().toIso8601String();
} else if (clearBannerShowAt) {
payload['banner_show_at'] = null;
}
if (bannerHideAt != null) {
payload['banner_hide_at'] = bannerHideAt.toUtc().toIso8601String();
} else if (clearBannerHideAt) {
payload['banner_hide_at'] = null;
}
if (pushIntervalMinutes != null) {
payload['push_interval_minutes'] = pushIntervalMinutes;
} else if (clearPushInterval) {
payload['push_interval_minutes'] = null;
}
await _client.from('announcements').update(payload).eq('id', id);
}
/// Update only the banner settings on an existing announcement.
/// Intended for the "Manage Banner" popup available to the poster and admins.
Future<void> updateBannerSettings({
required String id,
required bool bannerEnabled,
DateTime? bannerShowAt,
DateTime? bannerHideAt,
int? pushIntervalMinutes,
}) async {
await _client.from('announcements').update({
'banner_enabled': bannerEnabled,
'banner_show_at': bannerShowAt?.toUtc().toIso8601String(),
'banner_hide_at': bannerHideAt?.toUtc().toIso8601String(),
'push_interval_minutes': pushIntervalMinutes,
'updated_at': AppTime.nowUtc().toIso8601String(),
}).eq('id', id);
}
/// Immediately stops a banner by setting [banner_hide_at] to now.
/// Usable by the poster or an admin.
Future<void> dismissBanner(String id) async {
await _client.from('announcements').update({
'banner_hide_at': AppTime.nowUtc().toIso8601String(),
'updated_at': AppTime.nowUtc().toIso8601String(),
}).eq('id', id);
}
/// Delete an announcement.
Future<void> deleteAnnouncement(String id) async {
await _client.from('announcements').delete().eq('id', id);