diff --git a/Controllers/NotificationsController.cs b/Controllers/NotificationsController.cs new file mode 100644 index 0000000..74bd1e3 --- /dev/null +++ b/Controllers/NotificationsController.cs @@ -0,0 +1,105 @@ +using BankSampahApp.Models; +using BankSampahApp.Services; +using Microsoft.AspNetCore.Mvc; + +namespace BankSampahApp.Controllers; + +public class NotificationsController : Controller +{ + private readonly INotificationService _notificationService; + + public NotificationsController(INotificationService notificationService) + { + _notificationService = notificationService; + } + + [HttpGet] + public IActionResult Index(string? tab) + { + NotificationCategory? categoryFilter = null; + if (!string.IsNullOrWhiteSpace(tab) && !tab.Equals("Semua", StringComparison.OrdinalIgnoreCase) + && Enum.TryParse(tab, ignoreCase: true, out NotificationCategory parsedCategory)) + { + categoryFilter = parsedCategory; + } + + var viewModel = new NotificationListViewModel + { + ActiveCategory = categoryFilter + }; + + return View(viewModel); + } + + [HttpGet] + public async Task Show(int id) + { + var notification = _notificationService.GetById(id); + if (notification is null) + { + return NotFound(); + } + + if (!notification.IsRead) + { + await _notificationService.UpdateReadStateAsync(id, true); + notification.IsRead = true; + } + + return View(notification); + } + + [HttpGet] + public async Task Table(string? category) + { + NotificationCategory? categoryFilter = null; + + if (!string.IsNullOrWhiteSpace(category) && + Enum.TryParse(category, ignoreCase: true, out NotificationCategory parsedCategory)) + { + categoryFilter = parsedCategory; + } + + var notifications = await _notificationService.GetNotificationsAsync(categoryFilter); + + var rows = notifications.Select(n => new + { + id = n.Id, + title = n.Title, + category = n.Category.ToString(), + severity = n.Severity.ToString(), + isRead = n.IsRead, + summary = n.Summary, + createdAt = n.CreatedAt.ToString("O"), + detailUrl = Url.Action("Show", "Notifications", new { id = n.Id }) + }); + + return Json(new { data = rows }); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UpdateReadStatus(int id, bool isRead) + { + var success = await _notificationService.UpdateReadStateAsync(id, isRead); + if (!success) + { + return NotFound(); + } + + return Json(new { success = true }); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Delete(int id) + { + var success = await _notificationService.DeleteAsync(id); + if (!success) + { + return NotFound(); + } + + return Json(new { success = true }); + } +} diff --git a/Models/NotificationItem.cs b/Models/NotificationItem.cs new file mode 100644 index 0000000..655c30b --- /dev/null +++ b/Models/NotificationItem.cs @@ -0,0 +1,28 @@ +namespace BankSampahApp.Models; + +public enum NotificationCategory +{ + StatusAkun, + Transaksi, + Pengajuan +} + +public enum NotificationSeverity +{ + Info, + Success, + Warning, + Error +} + +public class NotificationItem +{ + public int Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Summary { get; set; } = string.Empty; + public bool IsRead { get; set; } + public NotificationCategory Category { get; set; } + public NotificationSeverity Severity { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public string Link { get; set; } = string.Empty; +} diff --git a/Models/NotificationListViewModel.cs b/Models/NotificationListViewModel.cs new file mode 100644 index 0000000..ce83b0a --- /dev/null +++ b/Models/NotificationListViewModel.cs @@ -0,0 +1,6 @@ +namespace BankSampahApp.Models; + +public class NotificationListViewModel +{ + public NotificationCategory? ActiveCategory { get; set; } +} diff --git a/Program.cs b/Program.cs index 49aa603..7bb6c07 100644 --- a/Program.cs +++ b/Program.cs @@ -31,6 +31,7 @@ builder.Services.AddControllersWithViews(options => // Register application services builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddSingleton(); // Register new optimized services builder.Services.AddSingleton(); @@ -158,4 +159,4 @@ app.Lifetime.ApplicationStopping.Register(() => app.Logger.LogInformation("Application is shutting down gracefully..."); }); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/Services/INotificationService.cs b/Services/INotificationService.cs new file mode 100644 index 0000000..9e1eca9 --- /dev/null +++ b/Services/INotificationService.cs @@ -0,0 +1,11 @@ +using BankSampahApp.Models; + +namespace BankSampahApp.Services; + +public interface INotificationService +{ + Task> GetNotificationsAsync(NotificationCategory? category = null); + NotificationItem? GetById(int id); + Task UpdateReadStateAsync(int id, bool isRead); + Task DeleteAsync(int id); +} diff --git a/Services/NotificationService.cs b/Services/NotificationService.cs new file mode 100644 index 0000000..010cf19 --- /dev/null +++ b/Services/NotificationService.cs @@ -0,0 +1,207 @@ +using BankSampahApp.Models; + +namespace BankSampahApp.Services; + +public class NotificationService : INotificationService +{ + private readonly List _notifications = + [ + new() + { + Id = 1, + Title = "User BSU - 1", + Category = NotificationCategory.StatusAkun, + Severity = NotificationSeverity.Warning, + Summary = "Status akun Anda saat ini belum disetujui oleh [Nama BSU]. Mohon menunggu proses verifikasi data oleh pihak BSU. Anda akan mendapatkan notifikasi kembali setelah data Anda diverifikasi.", + CreatedAt = DateTime.UtcNow.AddDays(-1), + IsRead = false + }, + new() + { + Id = 2, + Title = "User BSU - 2", + Category = NotificationCategory.StatusAkun, + Severity = NotificationSeverity.Success, + Summary = "Selamat! Akun Anda telah disetujui dan aktif oleh [Nama Bank Sampah Unit (BSU)]. Anda kini dapat melakukan transaksi penyetoran dan penarikan sampah melalui aplikasi Bank Sampah.", + CreatedAt = DateTime.UtcNow.AddDays(-2), + IsRead = true + }, + new() + { + Id = 3, + Title = "User BSU - 2", + Category = NotificationCategory.StatusAkun, + Severity = NotificationSeverity.Error, + Summary = "Akun Anda ditolak oleh [Nama Bank Sampah Unit (BSU)]. Alasan: i. Data yang Anda berikan tidak valid atau tidak sesuai. ii. Pelanggaran terhadap kebijakan atau aturan Bank Sampah. Silakan hubungi [Nama BSU] untuk informasi lebih lanjut atau proses pengaktifan kembali akun Anda.", + CreatedAt = DateTime.UtcNow.AddDays(-3), + IsRead = false + }, + new() + { + Id = 4, + Title = "User BSU - 2", + Category = NotificationCategory.Transaksi, + Severity = NotificationSeverity.Success, + Summary = "Terima kasih telah melakukan pengumpulan sampah di [Nama BSU]! Anda telah menyetorkan sampah seberat {berat_sampah} kg dengan total nilai sebesar Rp {jumlah_uang}. Terus berkontribusi menjaga lingkungan!", + CreatedAt = DateTime.UtcNow.AddDays(-4), + IsRead = false + }, + new() + { + Id = 5, + Title = "User BSU - 2", + Category = NotificationCategory.Transaksi, + Severity = NotificationSeverity.Success, + Summary = "Penarikan saldo Anda telah berhasil diproses di [Nama BSU]. Jumlah yang ditarik: Rp {jumlah_uang}. Sisa saldo tabungan Anda saat ini: Rp {sisa_saldo}. Terima kasih telah berpartisipasi!", + CreatedAt = DateTime.UtcNow.AddDays(-5), + IsRead = true + }, + new() + { + Id = 6, + Title = "User BSU - 2", + Category = NotificationCategory.StatusAkun, + Severity = NotificationSeverity.Success, + Summary = "Selamat! Akun Bank Sampah Unit (BSU) Anda telah disetujui dan diaktifkan oleh pihak Dinas. Anda kini dapat melakukan pengelolaan data nasabah, transaksi, serta pelaporan melalui sistem Bank Sampah.", + CreatedAt = DateTime.UtcNow.AddDays(-6), + IsRead = true + }, + new() + { + Id = 7, + Title = "User BSU - 2", + Category = NotificationCategory.StatusAkun, + Severity = NotificationSeverity.Warning, + Summary = "Status verifikasi Bank Sampah Unit (BSU) Anda saat ini belum disetujui oleh pihak Dinas. Mohon menunggu proses pemeriksaan data dan kelengkapan dokumen.", + CreatedAt = DateTime.UtcNow.AddDays(-7), + IsRead = false + }, + new() + { + Id = 8, + Title = "User BSU - 2", + Category = NotificationCategory.StatusAkun, + Severity = NotificationSeverity.Error, + Summary = "Mohon maaf, akun Bank Sampah Unit (BSU) Anda tidak dapat diaktifkan oleh pihak Dinas. Silakan lengkapi data atau hubungi pihak Dinas untuk proses klarifikasi dan pengajuan ulang.", + CreatedAt = DateTime.UtcNow.AddDays(-8), + IsRead = false + }, + new() + { + Id = 9, + Title = "Pengajuan BSU - 1", + Category = NotificationCategory.Pengajuan, + Severity = NotificationSeverity.Info, + Summary = "Pengajuan verifikasi Bank Sampah Unit (BSU) Anda telah berhasil dikirim ke Bank Sampah Induk (BSI).\nMohon menunggu proses verifikasi dan persetujuan dari pihak BSI.\nAnda akan mendapatkan notifikasi kembali setelah pengajuan diverifikasi. ♻️", + CreatedAt = DateTime.UtcNow.AddDays(-9), + IsRead = false + }, + new() + { + Id = 10, + Title = "Pengajuan BSU - 1", + Category = NotificationCategory.Pengajuan, + Severity = NotificationSeverity.Success, + Summary = "Selamat! 🎉\nPengajuan verifikasi Bank Sampah Unit (BSU) Anda telah disetujui dan diaktifkan oleh Bank Sampah Induk (BSI).\nAnda kini dapat mulai melakukan pengelolaan data transaksi.", + CreatedAt = DateTime.UtcNow.AddDays(-10), + IsRead = false + }, + new() + { + Id = 11, + Title = "Pengajuan BSU - 2", + Category = NotificationCategory.Pengajuan, + Severity = NotificationSeverity.Warning, + Summary = "Anda menerima pengajuan keanggotaan baru dari Nasabah yang ingin bergabung dengan Bank Sampah Unit (BSU) Anda.\nMohon lakukan verifikasi data dan kelengkapan identitas sebelum memberikan keputusan.\n🔹 Nama Nasabah: {nama_nasabah}\n🔹 Tanggal Pengajuan: {tanggal_pengajuan}\n🔹 Nomor HP / ID Nasabah: {id_nasabah}\nLakukan verifikasi agar nasabah dapat segera menjadi anggota aktif BSU Anda. ♻️", + CreatedAt = DateTime.UtcNow.AddDays(-11), + IsRead = true + }, + new() + { + Id = 12, + Title = "Pengajuan BSU - 2", + Category = NotificationCategory.Pengajuan, + Severity = NotificationSeverity.Info, + Summary = "Anda menerima pengajuan keanggotaan baru dari Bank Sampah Unit (BSU).\nMohon tinjau dan verifikasi data sebelum menyetujui atau menolak pengajuan.\n🔹 Nama BSU: {nama_bsu}\n🔹 Tanggal Pengajuan: {tanggal_pengajuan}\n🔹 Diajukan oleh: {nama_pengaju / penanggung_jawab}\nSegera lakukan verifikasi agar BSU dapat menjadi bagian dari jaringan Bank Sampah Induk Anda. ♻️", + CreatedAt = DateTime.UtcNow.AddDays(-12), + IsRead = false + }, + new() + { + Id = 13, + Title = "Pengajuan BSU - 3", + Category = NotificationCategory.Pengajuan, + Severity = NotificationSeverity.Warning, + Summary = "Terdapat pengajuan verifikasi baru dari Bank Sampah Unit (BSU) yang perlu ditinjau.\nSilakan lakukan pemeriksaan data dan dokumen BSU sebelum menentukan status persetujuan.\n🔹 Nama BSU: {nama_bsu}\n🔹 Tanggal Pengajuan: {tanggal_pengajuan}\n🔹 Diajukan oleh: {nama_pengaju / penanggung_jawab}\nSegera lakukan verifikasi agar BSU dapat melanjutkan proses operasionalnya. ♻️", + CreatedAt = DateTime.UtcNow.AddDays(-13), + IsRead = true + } + ]; + + public Task> GetNotificationsAsync(NotificationCategory? category = null) + { + IEnumerable query = _notifications; + + if (category.HasValue) + { + query = query.Where(n => n.Category == category.Value); + } + + return Task.FromResult>(query + .OrderByDescending(n => n.CreatedAt) + .Select(n => new NotificationItem + { + Id = n.Id, + Title = n.Title, + Summary = n.Summary, + Category = n.Category, + Severity = n.Severity, + CreatedAt = n.CreatedAt, + Link = $"/Notifications/Show/{n.Id}", + IsRead = n.IsRead + }) + .ToList()); + } + + public NotificationItem? GetById(int id) + { + var notification = _notifications.FirstOrDefault(n => n.Id == id); + return notification is null + ? null + : new NotificationItem + { + Id = notification.Id, + Title = notification.Title, + Summary = notification.Summary, + Category = notification.Category, + Severity = notification.Severity, + CreatedAt = notification.CreatedAt, + Link = $"/Notifications/Show/{notification.Id}", + IsRead = notification.IsRead + }; + } + + public Task UpdateReadStateAsync(int id, bool isRead) + { + var notification = _notifications.FirstOrDefault(n => n.Id == id); + if (notification is null) + { + return Task.FromResult(false); + } + + notification.IsRead = isRead; + return Task.FromResult(true); + } + + public Task DeleteAsync(int id) + { + var notification = _notifications.FirstOrDefault(n => n.Id == id); + if (notification is null) + { + return Task.FromResult(false); + } + + _notifications.Remove(notification); + return Task.FromResult(true); + } +} diff --git a/Views/Notifications/Index.cshtml b/Views/Notifications/Index.cshtml new file mode 100644 index 0000000..55eac05 --- /dev/null +++ b/Views/Notifications/Index.cshtml @@ -0,0 +1,321 @@ +@model NotificationListViewModel +@{ + ViewData["Title"] = "Notifikasi"; + var activeTabKey = Model.ActiveCategory?.ToString() ?? "Semua"; + var statusKey = NotificationCategory.StatusAkun.ToString(); + var transaksiKey = NotificationCategory.Transaksi.ToString(); + var pengajuanKey = NotificationCategory.Pengajuan.ToString(); + var isSemuaActive = activeTabKey.Equals("Semua", StringComparison.OrdinalIgnoreCase); + var isStatusActive = activeTabKey.Equals(statusKey, StringComparison.OrdinalIgnoreCase); + var isTransaksiActive = activeTabKey.Equals(transaksiKey, StringComparison.OrdinalIgnoreCase); + var isPengajuanActive = activeTabKey.Equals(pengajuanKey, StringComparison.OrdinalIgnoreCase); +} + + + +
+
+ + Notifikasi + +
+
+ +
+ +
+
+
+
+ Status Notifikasi + +
+
+ Status Baca + +
+
+
+
+ +
+ +
+
+ +
+
+
+ + + + + + + + + + + + +
KategoriStatus NotifikasiStatus BacaRingkasanDiterima PadaAksi
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +@section Scripts { + +} diff --git a/Views/Notifications/Show.cshtml b/Views/Notifications/Show.cshtml new file mode 100644 index 0000000..7e3f078 --- /dev/null +++ b/Views/Notifications/Show.cshtml @@ -0,0 +1,67 @@ +@model NotificationItem +@{ + ViewData["Title"] = "Detail Notifikasi"; +} + +
+
+ + Notifikasi + +
+ + +
+ +
+ +
+
+
+ @GetCategoryLabel(Model.Category) + @GetSeverityLabel(Model.Severity) + + @(Model.IsRead ? "Sudah Dibaca" : "Belum Dibaca") + + + @Model.CreatedAt.ToString("dd MMMM yyyy HH:mm") + +
+ +
+ @Model.Summary +
+
+
+ +@functions { + private static string GetCategoryLabel(NotificationCategory category) => category switch + { + NotificationCategory.StatusAkun => "Status Akun", + NotificationCategory.Transaksi => "Transaksi", + NotificationCategory.Pengajuan => "Pengajuan", + _ => category.ToString() + }; + + private static string GetSeverityClass(NotificationSeverity severity) => severity switch + { + NotificationSeverity.Success => "badge-success", + NotificationSeverity.Warning => "badge-warning", + NotificationSeverity.Error => "badge-error", + _ => "badge-neutral" + }; + + private static string GetSeverityLabel(NotificationSeverity severity) => severity switch + { + NotificationSeverity.Success => "Sukses", + NotificationSeverity.Warning => "Peringatan", + NotificationSeverity.Error => "Gagal", + NotificationSeverity.Info => "Info", + _ => severity.ToString() + }; +} diff --git a/Views/Shared/_NavbarApp.cshtml b/Views/Shared/_NavbarApp.cshtml index 2ec8561..71acbe0 100644 --- a/Views/Shared/_NavbarApp.cshtml +++ b/Views/Shared/_NavbarApp.cshtml @@ -1,4 +1,15 @@ -