Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI for sending announcements to all identities #955

Merged
merged 79 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
3f7539b
feat: add ui from previous branch
tnotheis Nov 29, 2024
f4a3806
Merge branch 'main' into ui-for-sending-announcements
tnotheis Dec 18, 2024
b7125c1
Merge branch 'main' into ui-for-sending-announcements
tnotheis Jan 17, 2025
5d8c56f
chore: add new readme
nicole-eb Jan 21, 2025
6875e37
chore: enable button
nicole-eb Jan 21, 2025
8594581
Merge remote-tracking branch 'origin/main' into ui-for-sending-announ…
Siolto Jan 21, 2025
0f28100
fix: add missing id field to AnnouncementOverview model
Siolto Jan 22, 2025
ccd76b2
feat: add recipients field to CreateAnnouncement model
Siolto Jan 29, 2025
5580173
Merge remote-tracking branch 'origin/main' into ui-for-sending-announ…
Siolto Feb 4, 2025
9271871
feat: add route for fetching a single announcement
tnotheis Feb 4, 2025
7fd251d
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 4, 2025
2adf4d8
fix: define split query behavior
tnotheis Feb 4, 2025
a08472f
feat: add support for optional recipients and update announcement fet…
Siolto Feb 4, 2025
0b5fcb1
fix: set LastLoginAt to null if LastLoginAt is null
tnotheis Feb 4, 2025
a1bcb67
fix: use correct types
tnotheis Feb 4, 2025
b389b3f
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 5, 2025
a20862b
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 6, 2025
d4cf0f4
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 7, 2025
35d6c3a
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 7, 2025
c6bb3f8
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 8, 2025
d5cd389
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 8, 2025
d427911
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 9, 2025
ad2a1f8
feat: add announcement details page and update routing
Siolto Feb 4, 2025
7ee9af2
feat: enhance announcement creation dialog with language selection an…
Siolto Feb 4, 2025
887d2e9
feat: enhance announcement details page with improved layout and data…
Siolto Feb 10, 2025
fed7331
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 11, 2025
dae78ad
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 11, 2025
80296f7
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 11, 2025
06f667d
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 12, 2025
6f64088
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 12, 2025
02acafb
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 13, 2025
90ebc76
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 13, 2025
d1fa07d
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 14, 2025
d6d25e1
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 14, 2025
14c341b
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 15, 2025
eaab4fb
feat: replace LanguageMultiSelect with LanguagePicker and update anno…
Siolto Feb 24, 2025
f013d51
feat: add create announcement dialog with multilingual support
Siolto Feb 24, 2025
8debebc
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 24, 2025
2a49af7
refactor: simplify announcement dialog layout by replacing ListView w…
Siolto Feb 24, 2025
2be19cc
refactor: streamline validation logic in create announcement dialog
Siolto Feb 24, 2025
9e1daf3
feat: enhance language selection in announcement dialog and replace o…
Siolto Feb 24, 2025
b1034c5
refactor: replace CopyableEntityDetails with EntityDetails and enhanc…
Siolto Feb 24, 2025
3f9034a
refactor: update column labels in announcement details table for clarity
Siolto Feb 24, 2025
a479cd7
refactor: remove old create announcement dialog implementation
Siolto Feb 24, 2025
6c84d45
Merge remote-tracking branch 'origin/main' into ui-for-sending-announ…
Siolto Feb 24, 2025
d1d5688
chore: log received text
tnotheis Feb 24, 2025
9f7de26
refactor: enhance language sorting and improve announcement creation …
Siolto Feb 24, 2025
eb8bc43
chore: delete README_DEV.md
tnotheis Feb 24, 2025
e334a71
refactor: localize announcement details and improve error messaging
Siolto Feb 24, 2025
f3cacd8
chore: add more logging
tnotheis Feb 24, 2025
4a105b3
refactor: add language controller to announcement dialog and update l…
Siolto Feb 24, 2025
3b9b8e3
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 24, 2025
8a452d2
chore: add more debug output
tnotheis Feb 24, 2025
6c14539
refactor: improve language handling in announcement dialog and picker
Siolto Feb 24, 2025
bc365fa
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 24, 2025
f264ef0
fix: use hardcoded language list and remove debug logs
tnotheis Feb 24, 2025
9fceb0b
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 24, 2025
3e0fb71
fix: use hardcoded languages in other places
tnotheis Feb 24, 2025
d816647
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 24, 2025
b63b24d
feat: enhance LanguagePicker with dynamic width and improved error ha…
Siolto Feb 24, 2025
f4f3de2
fix: throw on upload in case of unknown exception
tnotheis Feb 24, 2025
151b15e
refactor: remove unused language controller from AnnouncementTextForm…
Siolto Feb 24, 2025
d952626
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 24, 2025
b0c2a53
refactor: clean up CreateAnnouncementDialog by removing commented cod…
Siolto Feb 24, 2025
3ac09f7
feat: improve LanguagePicker layout with dynamic menu height and enha…
Siolto Feb 24, 2025
5ad3a96
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 25, 2025
92003c8
feat: enhance LanguagePicker with TextEditingController and improve l…
Siolto Feb 25, 2025
4808fd4
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 25, 2025
916197f
Merge branch 'main' into ui-for-sending-announcements
mergify[bot] Feb 25, 2025
bc58d8b
feat: update LanguagePicker to use callback for language selection an…
Siolto Feb 25, 2025
4e659d9
Merge remote-tracking branch 'origin/ui-for-sending-announcements' in…
Siolto Feb 25, 2025
d4e92da
feat: refactor announcement title retrieval and improve error handlin…
Siolto Feb 25, 2025
a0c564f
feat: replace SeverityType with AnnouncementSeverityType and update r…
Siolto Feb 25, 2025
6002f66
feat: update announcement details dialog to use GoRouter for navigation
Siolto Feb 25, 2025
bcb7f3f
feat: update language picker to use AnnouncementLanguages for languag…
Siolto Feb 25, 2025
2578ed6
feat: refactor announcement text controllers to improve code readabil…
Siolto Feb 25, 2025
f31e085
refactor: announcement and severity types
Siolto Feb 25, 2025
b5dd06f
refactor: use the AnnouncementSeverity type in the creat_announcement…
Siolto Feb 25, 2025
e2d3a8f
fix: update severity to use its name in announcement creation payload
Siolto Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using Backbone.BuildingBlocks.API.Mvc;
using Backbone.BuildingBlocks.API.Mvc.ControllerAttributes;
using Backbone.Modules.Announcements.Application.Announcements.Commands.CreateAnnouncement;
using Backbone.Modules.Announcements.Application.Announcements.DTOs;
using Backbone.Modules.Announcements.Application.Announcements.Queries.GetAllAnnouncements;
using Backbone.Modules.Announcements.Application.Announcements.Queries.GetAnnouncementById;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
Expand All @@ -11,8 +14,11 @@ namespace Backbone.AdminApi.Controllers;
[Authorize("ApiKey")]
public class AnnouncementsController : ApiControllerBase
{
public AnnouncementsController(IMediator mediator) : base(mediator)
private readonly ILogger<AnnouncementsController> _logger;

public AnnouncementsController(IMediator mediator, ILogger<AnnouncementsController> logger) : base(mediator)
{
_logger = logger;
}

[HttpPost]
Expand All @@ -22,7 +28,17 @@ public async Task<IActionResult> CreateAnnouncement([FromBody] CreateAnnouncemen
return Created(response);
}

[HttpGet("{id}")]
[ProducesResponseType(typeof(AnnouncementDTO), StatusCodes.Status200OK)]
[ProducesError(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetAnnouncement(string id, CancellationToken cancellationToken)
{
var response = await _mediator.Send(new GetAnnouncementByIdQuery(id), cancellationToken);
return Ok(response);
}

[HttpGet]
[ProducesResponseType(typeof(GetAllAnnouncementsResponse), StatusCodes.Status200OK)]
public async Task<IActionResult> ListAnnouncements(CancellationToken cancellationToken)
{
var response = await _mediator.Send(new GetAllAnnouncementsQuery(), cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import 'package:admin_api_sdk/admin_api_sdk.dart';
import 'package:admin_api_types/admin_api_types.dart';
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:intl/intl.dart';

import '/core/core.dart';

class AnnouncementDetails extends StatefulWidget {
final String announcementId;

const AnnouncementDetails({
required this.announcementId,
super.key,
});

@override
State<AnnouncementDetails> createState() => _AnnouncementDetailsState();
}

class _AnnouncementDetailsState extends State<AnnouncementDetails> {
Announcement? _announcmentDetails;

@override
void initState() {
super.initState();

_loadAnnouncement();
}

@override
void dispose() {
super.dispose();
}

@override
Widget build(BuildContext context) {
if (_announcmentDetails == null) return const Center(child: CircularProgressIndicator());

return Scrollbar(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (kIsDesktop)
Row(
children: [
const BackButton(),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadAnnouncement,
tooltip: context.l10n.reload,
),
],
),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(context.l10n.announcementsDetails, style: Theme.of(context).textTheme.headlineLarge),
const SizedBox(height: 32),
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
runSpacing: 8,
children: [
EntityDetails(title: context.l10n.announcementsOverview_severity, value: _announcmentDetails!.severity),
EntityDetails(
title: context.l10n.createdAt,
value: DateFormat.yMd(Localizations.localeOf(context).languageCode).format(_announcmentDetails!.createdAt),
),
if (_announcmentDetails!.expiresAt != null)
EntityDetails(
title: context.l10n.expiresAt,
value: DateFormat.yMd(Localizations.localeOf(context).languageCode).format(_announcmentDetails!.expiresAt!),
),
],
),
],
),
),
),
_AnnouncementsTextTable(announcementTexts: _announcmentDetails!.texts),
],
),
),
);
}

Future<void> _loadAnnouncement() async {
final announcementDetailsResponse = await GetIt.I.get<AdminApiClient>().announcements.getAnnouncement(widget.announcementId);

if (!mounted) return;

setState(() {
_announcmentDetails = announcementDetailsResponse.data;
});
}
}

class _AnnouncementsTextTable extends StatelessWidget {
final List<AnnouncementText> announcementTexts;

const _AnnouncementsTextTable({
required this.announcementTexts,
});

@override
Widget build(BuildContext context) {
return Card(
child: SizedBox(
width: double.infinity,
height: 500,
child: DataTable2(
columns: <DataColumn>[
DataColumn2(label: Text(context.l10n.announcementsLanguage)),
DataColumn2(label: Text(context.l10n.title)),
DataColumn2(label: Text(context.l10n.body)),
],
rows: announcementTexts
.map(
(announcementText) => DataRow(
cells: [
DataCell(Text(AnnouncementLanguages.languages.firstWhere((language) => language.isoCode == announcementText.language).name)),
DataCell(Text(announcementText.title)),
DataCell(Text(announcementText.body)),
],
),
)
.toList(),
),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'package:admin_api_sdk/admin_api_sdk.dart';
import 'package:admin_api_types/admin_api_types.dart';
import 'package:data_table_2/data_table_2.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';

import '/core/core.dart';
import 'modals/create_announcement_dialog.dart';

class AnnouncementsOverview extends StatefulWidget {
const AnnouncementsOverview({super.key});

@override
State<AnnouncementsOverview> createState() => _AnnouncementsOverviewState();
}

class _AnnouncementsOverviewState extends State<AnnouncementsOverview> {
List<Announcement> _announcements = [];

@override
void initState() {
super.initState();

_reloadAnnouncements();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(context.l10n.announcementsOverview_title)),
body: Card(
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (kIsDesktop)
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () async => _reloadAnnouncements(),
tooltip: context.l10n.reload,
),
IconButton.filled(
icon: const Icon(Icons.add),
onPressed: () => showCreateAnnouncementDialog(context: context, onAnnouncementCreated: _reloadAnnouncements),
),
],
),
Expanded(
child: DataTable2(
empty: Text(context.l10n.announcementsOverview_noAnnouncementsFound),
columns: <DataColumn2>[
DataColumn2(label: Text(context.l10n.title)),
DataColumn2(label: Text(context.l10n.createdAt)),
DataColumn2(label: Text(context.l10n.expiresAt)),
DataColumn2(label: Text(context.l10n.announcementsOverview_severity)),
],
rows: _announcements
.map(
(announcement) => DataRow2(
onTap: () => context.go('/announcements/${announcement.id}'),
cells: [
DataCell(Text(_getAnnouncementTitle(announcement, 'en'))),
DataCell(
Tooltip(
message:
'${DateFormat.yMd(Localizations.localeOf(context).languageCode).format(announcement.createdAt)} ${DateFormat.Hms().format(announcement.createdAt)}',
child: Text(DateFormat.yMd(Localizations.localeOf(context).languageCode).format(announcement.createdAt)),
),
),
DataCell(
Tooltip(
message: announcement.expiresAt != null
? '${DateFormat.yMd(Localizations.localeOf(context).languageCode).format(announcement.expiresAt!)} ${DateFormat.Hms().format(announcement.expiresAt!)}'
: '',
child: Text(
announcement.expiresAt != null
? DateFormat.yMd(Localizations.localeOf(context).languageCode).format(announcement.expiresAt!)
: '',
),
),
),
DataCell(Text(announcement.severity)),
],
),
)
.toList(),
),
),
],
),
),
),
);
}

String _getAnnouncementTitle(Announcement announcement, String language) {
return announcement.texts.firstWhere((t) => t.language == language).title;
}

Future<void> _reloadAnnouncements() async {
final response = await GetIt.I.get<AdminApiClient>().announcements.getAnnouncements();
final announcements = response.data..sort((a, b) => a.createdAt.compareTo(b.createdAt));

setState(() {
_announcements = announcements;
});
}
}
Loading
Loading