feat(reports): add reports page and export notification downloads
This commit is contained in:
@@ -106,3 +106,67 @@ export const buildNotificationStreamUrl = (token: string) => {
|
||||
const cleanBaseUrl = API_BASE_URL.replace(/\/+$/, "")
|
||||
return `${cleanBaseUrl}/api/notifications/stream/?token=${encodeURIComponent(token)}`
|
||||
}
|
||||
|
||||
const REPORT_EXPORT_DOWNLOAD_PATTERN = /\/api\/reports\/exports\/[^/]+\/download\/?$/
|
||||
|
||||
const toApiEndpoint = (actionUrl: string) => {
|
||||
const cleanBaseUrl = API_BASE_URL.replace(/\/+$/, "")
|
||||
if (actionUrl.startsWith("http://") || actionUrl.startsWith("https://")) {
|
||||
if (!actionUrl.startsWith(cleanBaseUrl)) {
|
||||
return null
|
||||
}
|
||||
return actionUrl.slice(cleanBaseUrl.length) || "/"
|
||||
}
|
||||
return actionUrl.startsWith("/") ? actionUrl : `/${actionUrl}`
|
||||
}
|
||||
|
||||
const getFilenameFromDisposition = (contentDisposition: string | null) => {
|
||||
if (!contentDisposition) return null
|
||||
|
||||
const utfMatch = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i)
|
||||
if (utfMatch?.[1]) {
|
||||
return decodeURIComponent(utfMatch[1])
|
||||
}
|
||||
|
||||
const plainMatch = contentDisposition.match(/filename="?([^"]+)"?/i)
|
||||
return plainMatch?.[1] || null
|
||||
}
|
||||
|
||||
export const isReportExportDownloadUrl = (actionUrl?: string | null) => {
|
||||
if (!actionUrl) return false
|
||||
const endpoint = toApiEndpoint(actionUrl)
|
||||
return !!endpoint && REPORT_EXPORT_DOWNLOAD_PATTERN.test(endpoint)
|
||||
}
|
||||
|
||||
export const downloadNotificationFile = async (
|
||||
actionUrl: string,
|
||||
fallbackFilename?: string | null,
|
||||
) => {
|
||||
const endpoint = toApiEndpoint(actionUrl)
|
||||
if (!endpoint) {
|
||||
throw new Error("Unsupported download url")
|
||||
}
|
||||
|
||||
const response = await authFetch(endpoint, {
|
||||
method: "GET",
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to download file")
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const objectUrl = window.URL.createObjectURL(blob)
|
||||
const filename =
|
||||
getFilenameFromDisposition(response.headers.get("content-disposition")) ||
|
||||
fallbackFilename ||
|
||||
"download"
|
||||
|
||||
const anchor = document.createElement("a")
|
||||
anchor.href = objectUrl
|
||||
anchor.download = filename
|
||||
document.body.appendChild(anchor)
|
||||
anchor.click()
|
||||
anchor.remove()
|
||||
window.URL.revokeObjectURL(objectUrl)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user