From 2762aceb047550f98972173165f6a8e3972253c3 Mon Sep 17 00:00:00 2001 From: kovakmedya Date: Sat, 23 May 2026 17:54:47 +0300 Subject: [PATCH] feat(notifications): severity (info/warning) + cover the gaps in the flow matrix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modelled the real crown-and-bridge workflow (impression → accept → framework try-in → bisque try-in → glaze/cila → delivery, with revisions bouncing back at any try-in stage). Mapped every event in our system to the appropriate side and tagged the ones a user actually needs to act on versus normal progress traffic. DB - notifications.severity enum ('info' | 'warning', default 'info'). Existing rows default to info. Helper - createNotification gains an optional severity param; persists into the new column. Matrix changes (warnings vs infos) Lab side: - Düzeltme talebi (clinic → lab) WARNING - İş iptal edildi (counterpart action) WARNING Clinic side: - Ödeme reddedildi (lab → clinic) WARNING - Bağlantı reddedildi (counterpart → req) WARNING (newly fired — rejectConnectionAction now sends a notification, previously silent) Everything else (accept, hand-off, prova ready, prova approved, delivery, payment submitted/approved, connection request/approved): stays at info. Behaviour additions in actions - cancelJobAction now notifies the *other* party. Previously cancelled jobs vanished from one side's inbox without warning. severity=warning. - rejectConnectionAction notifies the requester (severity=warning) so they know the connection didn't come through. - requestRevisionAction tags its existing notification as warning. - rejectPaymentAction tags its existing notification as warning. UI - Notifications list row: unread warning rows get an amber dot, an amber-tinted background, and a 'Dikkat' destructive badge instead of the regular 'Yeni' secondary one. - Dashboard recent-notifications widget mirrors the same dot + bold treatment so warnings stand out at a glance. Net result: a user opening the bell sees normal traffic muted (still listed for the 'herkes her şeyden haberdar' guarantee) and the things that actually need them coloured amber and labeled Dikkat. --- src/app/(dashboard)/dashboard/page.tsx | 19 ++++++++++++---- .../components/notifications-list.tsx | 22 +++++++++++++++---- src/lib/appwrite/connection-actions.ts | 9 ++++++++ src/lib/appwrite/job-actions.ts | 11 ++++++++++ src/lib/appwrite/notification-helpers.ts | 11 +++++++++- src/lib/appwrite/payment-actions.ts | 1 + src/lib/appwrite/schema.ts | 5 +++++ 7 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index f424d6c..f5729af 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -249,23 +249,34 @@ export default async function DashboardPage() {

) : ( )} diff --git a/src/app/(dashboard)/notifications/components/notifications-list.tsx b/src/app/(dashboard)/notifications/components/notifications-list.tsx index 992fb30..505160b 100644 --- a/src/app/(dashboard)/notifications/components/notifications-list.tsx +++ b/src/app/(dashboard)/notifications/components/notifications-list.tsx @@ -66,10 +66,21 @@ function NotificationRow({ row }: { row: Notification }) { ? "/connections" : null; + const isWarning = row.severity === "warning"; return ( -
  • +
  • @@ -79,8 +90,11 @@ function NotificationRow({ row }: { row: Notification }) {

    {!row.read && ( - - Yeni + + {isWarning ? "Dikkat" : "Yeni"} )} {link && ( diff --git a/src/lib/appwrite/connection-actions.ts b/src/lib/appwrite/connection-actions.ts index b3c4c2e..b542fb7 100644 --- a/src/lib/appwrite/connection-actions.ts +++ b/src/lib/appwrite/connection-actions.ts @@ -278,6 +278,15 @@ export async function rejectConnectionAction( entityId: connectionId, changes: { status: "rejected" }, }); + // Tell the requester their request was turned down — warning, not info. + const requesterTenant = + conn.clinicTenantId === ctx.tenantId ? conn.labTenantId : conn.clinicTenantId; + void createNotification({ + tenantId: requesterTenant, + connectionId, + severity: "warning", + message: `${ctx.settings?.companyName ?? "Karşı taraf"} bağlantı talebinizi reddetti.`, + }); } catch (e) { return { ok: false, error: appwriteError(e, "Reddedilemedi.") }; } diff --git a/src/lib/appwrite/job-actions.ts b/src/lib/appwrite/job-actions.ts index 141bc45..44df4af 100644 --- a/src/lib/appwrite/job-actions.ts +++ b/src/lib/appwrite/job-actions.ts @@ -664,6 +664,7 @@ export async function requestRevisionAction( void createNotification({ tenantId: job.labTenantId, jobId, + severity: "warning", message: `Hasta ${job.patientCode} ${stepLabel} provası için düzeltme istendi: ${note.slice(0, 120)}`, }); } catch (e) { @@ -773,6 +774,16 @@ export async function cancelJobAction( entityId: jobId, changes: { status: "cancelled" }, }); + // Notify the other side — cancellation is a warning, not normal traffic. + const otherTenantId = + ctx.tenantId === job.clinicTenantId ? job.labTenantId : job.clinicTenantId; + const actor = ctx.kind === "lab" ? "Laboratuvar" : "Klinik"; + void createNotification({ + tenantId: otherTenantId, + jobId, + severity: "warning", + message: `${actor} hasta ${job.patientCode} işini iptal etti.`, + }); } catch (e) { return { ok: false, error: appwriteError(e, "İptal edilemedi.") }; } diff --git a/src/lib/appwrite/notification-helpers.ts b/src/lib/appwrite/notification-helpers.ts index 8c67e9a..7e4c8e6 100644 --- a/src/lib/appwrite/notification-helpers.ts +++ b/src/lib/appwrite/notification-helpers.ts @@ -2,7 +2,12 @@ import "server-only"; import { ID, Permission, Query, Role } from "node-appwrite"; -import { DATABASE_ID, TABLES, type Notification } from "./schema"; +import { + DATABASE_ID, + TABLES, + type Notification, + type NotificationSeverity, +} from "./schema"; import { createAdminClient } from "./server"; import { toPlain } from "./serialize"; @@ -12,6 +17,9 @@ type CreateNotificationInput = { jobId?: string; connectionId?: string; message: string; + /** Defaults to 'info'. Use 'warning' for things that need the user's + * attention (revision, cancellation, rejections). */ + severity?: NotificationSeverity; }; /** @@ -32,6 +40,7 @@ export async function createNotification(input: CreateNotificationInput): Promis connectionId: input.connectionId, message: input.message.slice(0, 500), read: false, + severity: input.severity ?? "info", }, [ Permission.read(Role.team(input.tenantId)), diff --git a/src/lib/appwrite/payment-actions.ts b/src/lib/appwrite/payment-actions.ts index 761673f..ad3c23c 100644 --- a/src/lib/appwrite/payment-actions.ts +++ b/src/lib/appwrite/payment-actions.ts @@ -269,6 +269,7 @@ export async function rejectPaymentAction( }); void createNotification({ tenantId: row.tenantId, + severity: "warning", message: `Ödeme bildiriminiz reddedildi: ${row.amount.toLocaleString("tr-TR")} ${row.currency}.`, }); } catch (e) { diff --git a/src/lib/appwrite/schema.ts b/src/lib/appwrite/schema.ts index 76902b0..053cb90 100644 --- a/src/lib/appwrite/schema.ts +++ b/src/lib/appwrite/schema.ts @@ -194,6 +194,8 @@ export interface Payment extends Row { recordedBy: string; } +export type NotificationSeverity = "info" | "warning"; + export interface Notification extends Row { tenantId: string; userId?: string; @@ -201,6 +203,9 @@ export interface Notification extends Row { connectionId?: string; message: string; read: boolean; + /** Visual + filtering hint. 'warning' for things requiring attention + * (revision request, cancellation, payment / connection rejection). */ + severity?: NotificationSeverity; } export type AuditAction = "create" | "update" | "delete";