Yimaru-Admin/src/api/notifications.api.ts
Yared Yemane 1014f4a72f feat(admin): notification details, question type library, and schema UX
Wire GET /notifications/:id for topbar and notifications page detail views, harden notification WebSocket lifecycle, paginate question type and app version lists from API, and expand dynamic question type schema labels and slot editing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 05:44:09 -07:00

126 lines
4.0 KiB
TypeScript

import http from "./http"
import type {
GetNotificationsResponse,
Notification,
UnreadCountResponse,
} from "../types/notification.types"
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value)
}
function unwrapEnvelopeData(body: unknown): unknown {
if (!isRecord(body)) return body
if ("data" in body || "Data" in body) {
return body.data ?? body.Data
}
return body
}
function normalizePayload(raw: unknown): Notification["payload"] {
if (!isRecord(raw)) {
return { tags: null }
}
const tags = Array.isArray(raw.tags)
? raw.tags.filter((tag): tag is string => typeof tag === "string" && tag.length > 0)
: null
return {
headline: raw.headline != null ? String(raw.headline) : undefined,
title: raw.title != null ? String(raw.title) : undefined,
message: raw.message != null ? String(raw.message) : undefined,
body: raw.body != null ? String(raw.body) : undefined,
tags,
}
}
export function normalizeNotification(raw: unknown): Notification | null {
if (!isRecord(raw)) return null
const id = String(raw.id ?? "")
if (!id) return null
return {
id,
recipient_id: Number(raw.recipient_id ?? 0),
receiver_type: raw.receiver_type != null ? String(raw.receiver_type) : undefined,
type: String(raw.type ?? ""),
level: String(raw.level ?? ""),
error_severity: String(raw.error_severity ?? ""),
reciever: String(raw.reciever ?? ""),
is_read: Boolean(raw.is_read),
delivery_status: String(raw.delivery_status ?? ""),
delivery_channel: String(raw.delivery_channel ?? ""),
payload: normalizePayload(raw.payload),
timestamp: String(raw.timestamp ?? ""),
expires: String(raw.expires ?? ""),
image: String(raw.image ?? ""),
}
}
function parseNotificationsListData(body: unknown, limit: number, offset: number): GetNotificationsResponse {
const inner = unwrapEnvelopeData(body)
if (!isRecord(inner)) {
return { notifications: [], total_count: 0, limit, offset }
}
const rows = Array.isArray(inner.notifications) ? inner.notifications : []
const notifications = rows
.map(normalizeNotification)
.filter((n): n is Notification => n !== null)
return {
notifications,
total_count: Number(inner.total_count ?? notifications.length),
limit: Number(inner.limit ?? limit),
offset: Number(inner.offset ?? offset),
}
}
function parseUnreadCount(body: unknown): UnreadCountResponse {
const inner = unwrapEnvelopeData(body)
if (!isRecord(inner)) return { unread: 0 }
return { unread: Number(inner.unread ?? 0) }
}
export const getNotifications = (limit = 10, offset = 0) =>
http.get<unknown>("/notifications", { params: { limit, offset } }).then((res) => ({
...res,
data: parseNotificationsListData(res.data, limit, offset),
}))
export const getNotificationById = (id: string) =>
http.get<unknown>(`/notifications/${id}`).then((res) => ({
...res,
data: normalizeNotification(unwrapEnvelopeData(res.data)),
}))
export const getUnreadCount = () =>
http.get<unknown>("/notifications/unread").then((res) => ({
...res,
data: parseUnreadCount(res.data),
}))
export const markAsRead = (id: string) =>
http.patch(`/notifications/${id}/read`)
export const markAsUnread = (id: string) =>
http.patch(`/notifications/${id}/unread`)
export const markAllRead = () =>
http.post("/notifications/mark-all-read")
export const markAllUnread = () =>
http.post("/notifications/mark-all-unread")
export const sendBulkSms = (data: { message: string; user_ids: number[]; scheduled_at?: string }) =>
http.post("/notifications/bulk-sms", data)
export const sendBulkEmail = (formData: FormData) =>
http.post("/notifications/bulk-email", formData, {
headers: { "Content-Type": "multipart/form-data" },
})
export const sendBulkPush = (formData: FormData) =>
http.post("/notifications/bulk-push", formData, {
headers: { "Content-Type": "multipart/form-data" },
})