import { format } from "date-fns" import { redirect } from "next/navigation" import { toast } from "sonner" import { v4 as uuidv4 } from "uuid" import * as crypto from "crypto" // import { districtsGeoJson } from "../../prisma/data/geojson/jember/districts-geojson" import { CRegex } from "../constants/regex" // import { getNavData } from "../../prisma/data/jsons/nav" import { DateFormatOptions, DateFormatPattern, } from "../types/raws/date-format.interface" import { customAlphabet } from "nanoid" import { IUserModel } from "@/types/models" const prefixes: Record = {} interface IGenerateFilterIdOptions { length?: number separator?: string } // Used to track generated IDs const usedIdRegistry = new Set() // Add type definitions for global counters declare global { var __idCounter: number var __idCounterRegistry: Record } /** * Redirects to a specified path with an encoded message as a query parameter. * @param {('error' | 'success')} type - The type of message, either 'error' or 'success'. * @param {string} path - The path to redirect to. * @param {string} message - The message to be encoded and added as a query parameter. * @returns {never} This function doesn't return as it triggers a redirect. */ export function encodedRedirect( type: "error" | "success", path: string, message: string ) { return redirect(`${path}?${type}=${encodeURIComponent(message)}`) } /** * Formats a URL by removing any trailing slashes. * @param {string} url - The URL to format. * @returns {string} The formatted URL. */ // Helper function to ensure URLs are properly formatted export function formatUrl(url: string): string { // If URL starts with a slash, it's already absolute if (url.startsWith("/")) { return url } // Otherwise, ensure it's properly formatted relative to root // Remove any potential duplicated '/dashboard' prefixes if (url.startsWith("dashboard/")) { return "/" + url } return "/" + url } /** * Creates a FormData object from the FormData object. * @returns {FormData} The FormData object. */ export function createFormData(): FormData { const data = new FormData() Object.entries(FormData).forEach(([key, value]) => { if (value) { data.append(key, value) } }) return data } /** * Generates a unique username based on the provided email address. * * The username is created by combining the local part of the email (before the '@' symbol) * with a randomly generated alphanumeric suffix. * * @param email - The email address to generate the username from. * @returns A string representing the generated username. * * @example * ```typescript * const username = generateUsername("example@gmail.com"); * console.log(username); // Output: "example.abc123" (random suffix will vary) * ``` */ export function generateUsername(email: string): string { const [localPart] = email.split("@") const randomSuffix = Math.random().toString(36).substring(2, 8) // Generate a random alphanumeric string return `${localPart}.${randomSuffix}` } /** * Formats a date string to a human-readable format with type safety. * @param date - The date string to format. * @param options - Formatting options or a format string. * @returns The formatted date string. * @example * // Using default format * formatDate("2025-03-23") * * // Using a custom format string * formatDate("2025-03-23", "yyyy-MM-dd") * * // Using formatting options * formatDate("2025-03-23", { format: "MMMM do, yyyy", fallback: "Not available" }) */ export const formatDate = ( date: string | Date | undefined | null, options: DateFormatOptions | DateFormatPattern = { format: "PPpp" } ): string => { if (!date) { return typeof options === "string" ? "-" : options.fallback || "-" } const dateObj = date instanceof Date ? date : new Date(date) // Handle invalid dates if (isNaN(dateObj.getTime())) { return typeof options === "string" ? "-" : options.fallback || "-" } if (typeof options === "string") { return format(dateObj, options) } const { format: formatPattern = "PPpp", locale } = options return locale ? format(dateObj, formatPattern, { locale }) : format(dateObj, formatPattern) } export const copyItem = ( item: string, options?: { label?: string onSuccess?: () => void onError?: (error: unknown) => void } ) => { if (!navigator.clipboard) { const error = new Error("Clipboard not supported") toast.error("Clipboard not supported") options?.onError?.(error) return } if (!item) { const error = new Error("Nothing to copy") toast.error("Nothing to copy") options?.onError?.(error) return } navigator.clipboard .writeText(item) .then(() => { const label = options?.label || item toast.success(`${label} copied to clipboard`) options?.onSuccess?.() }) .catch((error) => { toast.error("Failed to copy to clipboard") options?.onError?.(error) }) } /** * Formats a date string to a human-readable format with type safety. * @param date - The date string to format. * @param options - Formatting options or a format string. * @returns The formatted date string. * @example * // Using default format * formatDate("2025-03-23") * * // Using a custom format string * formatDate("2025-03-23", "yyyy-MM-dd") * * // Using formatting options * formatDate("2025-03-23", { format: "MMMM do, yyyy", fallback: "Not available" }) */ export const formatDateWithFallback = ( date: string | Date | undefined | null, options: DateFormatOptions | DateFormatPattern = { format: "PPpp" } ): string => { if (!date) { return typeof options === "string" ? "-" : options.fallback || "-" } const dateObj = date instanceof Date ? date : new Date(date) // Handle invalid dates if (isNaN(dateObj.getTime())) { return typeof options === "string" ? "-" : options.fallback || "-" } if (typeof options === "string") { return format(dateObj, options) } const { format: formatPattern = "PPpp", locale } = options return locale ? format(dateObj, formatPattern, { locale }) : format(dateObj, formatPattern) } export const formatDateWithLocale = ( date: string | Date | undefined | null, options: DateFormatOptions | DateFormatPattern = { format: "PPpp" } ): string => { if (!date) { return typeof options === "string" ? "-" : options.fallback || "-" } const dateObj = date instanceof Date ? date : new Date(date) // Handle invalid dates if (isNaN(dateObj.getTime())) { return typeof options === "string" ? "-" : options.fallback || "-" } if (typeof options === "string") { return format(dateObj, options) } const { format: formatPattern = "PPpp", locale } = options return locale ? format(dateObj, formatPattern, { locale }) : format(dateObj, formatPattern) } /** * Formats a date string to a human-readable format with type safety. * @param date - The date string to format. * @param options - Formatting options or a format string. * @returns The formatted date string. * @example * // Using default format * formatDate("2025-03-23") * * // Using a custom format string * formatDate("2025-03-23", "yyyy-MM-dd") * * // Using formatting options * formatDate("2025-03-23", { format: "MMMM do, yyyy", fallback: "Not available" }) */ export const formatDateWithLocaleAndFallback = ( date: string | Date | undefined | null, options: DateFormatOptions | DateFormatPattern = { format: "PPpp" } ): string => { if (!date) { return typeof options === "string" ? "-" : options.fallback || "-" } const dateObj = date instanceof Date ? date : new Date(date) // Handle invalid dates if (isNaN(dateObj.getTime())) { return typeof options === "string" ? "-" : options.fallback || "-" } if (typeof options === "string") { return format(dateObj, options) } const { format: formatPattern = "PPpp", locale } = options return locale ? format(dateObj, formatPattern, { locale }) : format(dateObj, formatPattern) } /** * Generates a full name from first and last names. * @param firstName - The first name. * @param lastName - The last name. * @returns The full name or "User" if both names are empty. */ export const getFullName = ( firstName: string | null | undefined, lastName: string | null | undefined ): string => { return `${firstName || ""} ${lastName || ""}`.trim() || "User" } /** * Generates initials for a user based on their first and last names. * @param firstName - The first name. * @param lastName - The last name. * @param email - The email address. * @returns The initials or "U" if both names are empty. */ export const getInitials = ( firstName: string, lastName: string, email: string ): string => { if (firstName && lastName) { return `${firstName[0]}${lastName[0]}`.toUpperCase() } if (firstName) { return firstName[0].toUpperCase() } if (email) { return email[0].toUpperCase() } return "U" } export function calculateUserStats(users: IUserModel[] | undefined) { if (!users || !Array.isArray(users)) { return { totalUsers: 0, activeUsers: 0, inactiveUsers: 0, activePercentage: "0.0", inactivePercentage: "0.0", } } const totalUsers = users.length const activeUsers = users.filter( (user) => !user.banned_until && user.email_confirmed_at ).length const inactiveUsers = totalUsers - activeUsers return { totalUsers, activeUsers, inactiveUsers, activePercentage: totalUsers > 0 ? ((activeUsers / totalUsers) * 100).toFixed(1) : "0.0", inactivePercentage: totalUsers > 0 ? ((inactiveUsers / totalUsers) * 100).toFixed(1) : "0.0", } } /** * Generate route with query parameters * @param baseRoute - The base route path * @param params - Object containing query parameters * @returns Formatted route with query parameters */ export const createRoute = ( baseRoute: string, params?: Record ): string => { if (!params || Object.keys(params).length === 0) { return baseRoute } const queryString = Object.entries(params) .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) .join("&") return `${baseRoute}?${queryString}` } // Format date helper function function formatDateV2(date: Date, formatStr: string): string { const pad = (num: number) => String(num).padStart(2, "0") return formatStr .replace("yyyy", String(date.getFullYear())) .replace("MM", pad(date.getMonth() + 1)) .replace("dd", pad(date.getDate())) .replace("HH", pad(date.getHours())) .replace("mm", pad(date.getMinutes())) .replace("ss", pad(date.getSeconds())) } // /** // * Universal Custom ID Generator // * Creates structured, readable IDs for any system or entity // * // * @param {Object} options - Configuration options // * @param {string} options.prefix - Primary identifier prefix (e.g., "CRIME", "USER", "INVOICE") // * @param {Object} options.segments - Collection of ID segments to include // * @param {string[]} options.segments.codes - Array of short codes (e.g., region codes, department codes) // * @param {number} options.segments.year - Year to include in the ID // * @param {number} options.segments.sequentialDigits - Number of digits for sequential number // * @param {boolean} options.segments.includeDate - Whether to include current date // * @param {string} options.segments.dateFormat - Format for date (e.g., "yyyy-MM-dd", "dd/MM/yyyy") // * @param {boolean} options.segments.includeTime - Whether to include timestamp // * @param {string} options.format - Custom format string for ID structure // * @param {string} options.separator - Character to separate ID components // * @param {boolean} options.upperCase - Convert result to uppercase // * @returns {string} - Generated custom ID // */ // export function generateId( // options: { // prefix?: string; // segments?: { // codes?: string[]; // year?: number | boolean; // Year diubah menjadi number | boolean // sequentialDigits?: number; // includeDate?: boolean; // dateFormat?: string; // includeTime?: boolean; // includeMilliseconds?: boolean; // }; // format?: string | null; // separator?: string; // upperCase?: boolean; // randomSequence?: boolean; // uniquenessStrategy?: 'uuid' | 'timestamp' | 'counter' | 'hash'; // retryOnCollision?: boolean; // maxRetries?: number; // } = {} // ): string { // // Jika uniquenessStrategy tidak diatur dan randomSequence = false, // // gunakan counter sebagai strategi default // if (!options.uniquenessStrategy && options.randomSequence === false) { // options.uniquenessStrategy = 'counter'; // } // const config = { // prefix: options.prefix || 'ID', // segments: { // codes: options.segments?.codes || [], // year: options.segments?.year, // Akan diproses secara kondisional nanti // sequentialDigits: options.segments?.sequentialDigits || 6, // includeDate: options.segments?.includeDate ?? false, // dateFormat: options.segments?.dateFormat || 'yyyyMMdd', // includeTime: options.segments?.includeTime ?? false, // includeMilliseconds: options.segments?.includeMilliseconds ?? false, // }, // format: options.format || null, // separator: options.separator || '-', // upperCase: options.upperCase ?? false, // randomSequence: options.randomSequence ?? true, // uniquenessStrategy: options.uniquenessStrategy || 'timestamp', // retryOnCollision: options.retryOnCollision ?? true, // maxRetries: options.maxRetries || 10, // }; // // Initialize global counter if not exists // if (typeof globalThis.__idCounter === 'undefined') { // globalThis.__idCounter = 0; // } // const now = new Date(); // // Generate date string if needed // let dateString = ''; // if (config.segments.includeDate) { // dateString = format(now, config.segments.dateFormat); // } // // Generate time string if needed // let timeString = ''; // if (config.segments.includeTime) { // timeString = format(now, 'HHmmss'); // if (config.segments.includeMilliseconds) { // timeString += now.getMilliseconds().toString().padStart(3, '0'); // } // } // // Generate sequential number based on uniqueness strategy // let sequentialNum: string; // try { // switch (config.uniquenessStrategy) { // case 'uuid': // sequentialNum = uuidv4().split('-')[0]; // break; // case 'timestamp': // sequentialNum = `${now.getTime()}${Math.floor(Math.random() * 1000)}`; // sequentialNum = sequentialNum.slice(-config.segments.sequentialDigits); // break; // case 'counter': // sequentialNum = (++globalThis.__idCounter) // .toString() // .padStart(config.segments.sequentialDigits, '0'); // break; // case 'hash': // const hashSource = `${now.getTime()}-${JSON.stringify(options)}-${Math.random()}`; // const hash = crypto // .createHash('sha256') // .update(hashSource) // .digest('hex'); // sequentialNum = hash.substring(0, config.segments.sequentialDigits); // break; // default: // if (config.randomSequence) { // const randomBytes = crypto.randomBytes(4); // const randomNum = parseInt(randomBytes.toString('hex'), 16); // sequentialNum = ( // randomNum % Math.pow(10, config.segments.sequentialDigits) // ) // .toString() // .padStart(config.segments.sequentialDigits, '0'); // } else { // sequentialNum = (++globalThis.__idCounter) // .toString() // .padStart(config.segments.sequentialDigits, '0'); // } // } // } catch (error) { // console.error('Error generating sequential number:', error); // // Fallback to timestamp strategy if other methods fail // sequentialNum = `${now.getTime()}`.slice(-config.segments.sequentialDigits); // } // // Determine if year should be included and what value to use // let yearValue = null; // if (config.segments.year !== undefined || config.segments.year != false) { // if (typeof config.segments.year === 'number') { // yearValue = String(config.segments.year); // } else if (config.segments.year === true) { // yearValue = format(now, 'yyyy'); // } // // if year is false, yearValue remains null and won't be included // } else { // // Default behavior (backward compatibility) // yearValue = format(now, 'yyyy'); // } // // Prepare components for ID assembly // const components = { // prefix: config.prefix, // codes: // config.segments.codes.length > 0 // ? config.segments.codes.join(config.separator) // : '', // year: yearValue, // Added the year value to components // sequence: sequentialNum, // date: dateString, // time: timeString, // }; // let result: string; // // Use custom format if provided // if (config.format) { // let customID = config.format; // for (const [key, value] of Object.entries(components)) { // if (value) { // const placeholder = `{${key}}`; // customID = customID.replace( // new RegExp(placeholder, 'g'), // String(value) // ); // } // } // // Remove unused placeholders // customID = customID.replace(/{[^}]+}/g, ''); // // Clean up separators // const escapedSeparator = config.separator.replace( // /[-\/\\^$*+?.()|[\]{}]/g, // '\\$&' // ); // const separatorRegex = new RegExp(`${escapedSeparator}+`, 'g'); // customID = customID.replace(separatorRegex, config.separator); // customID = customID.replace( // new RegExp(`^${escapedSeparator}|${escapedSeparator}$`, 'g'), // '' // ); // result = config.upperCase ? customID.toUpperCase() : customID; // } else { // // Assemble ID from parts // const parts = []; // if (components.prefix) parts.push(components.prefix); // if (components.codes) parts.push(components.codes); // if (components.year) parts.push(components.year); // if (components.date) parts.push(components.date); // if (components.time) parts.push(components.time); // if (components.sequence) parts.push(components.sequence); // result = parts.join(config.separator); // if (config.upperCase) result = result.toUpperCase(); // } // // Handle collisions if required // if (config.retryOnCollision) { // let retryCount = 0; // let originalResult = result; // while (usedIdRegistry.has(result) && retryCount < config.maxRetries) { // retryCount++; // try { // const suffix = crypto.randomBytes(2).toString('hex'); // result = `${originalResult}${config.separator}${suffix}`; // } catch (error) { // console.error('Error generating collision suffix:', error); // // Simple fallback if crypto fails // result = `${originalResult}${config.separator}${Date.now().toString(36)}`; // } // } // if (retryCount >= config.maxRetries) { // console.warn( // `Warning: Max ID generation retries (${config.maxRetries}) reached for prefix ${config.prefix}` // ); // } // } // // Register the ID and maintain registry size // usedIdRegistry.add(result); // if (usedIdRegistry.size > 10000) { // const entriesToKeep = Array.from(usedIdRegistry).slice(-5000); // usedIdRegistry.clear(); // entriesToKeep.forEach((id) => usedIdRegistry.add(id)); // } // return result.trim(); // } /** * Generates a unique code based on the provided name. * @param name - The name to generate the code from. * @returns The generated code. */ export function generateCode(name: string): string { const words = name.toUpperCase().split(" ") let code = "" if (name.length <= 3) { code = name.toUpperCase() } else if (words.length > 1) { code = words .map((w) => w[0]) .join("") .padEnd(3, "X") .slice(0, 3) } else { const nameClean = name.replace(/[aeiou]/gi, "") code = (nameClean.slice(0, 3) || name.slice(0, 3)).toUpperCase() } return code } /** * Generates a unique sequential ID based on a base string and existing codes. * @param base - The base string to generate the ID from. * @param existingCodes - A set of existing codes to check against. * @returns The generated unique sequential ID. */ export function getLatestSequentialId( base: string, existingCodes: Set ): string { let attempt = 1 let newCode = base while (existingCodes.has(newCode)) { newCode = base.slice(0, 2) + attempt attempt++ } return newCode } /** * Get color and text for a crime rate level */ export function getCrimeRateInfo( rate?: "low" | "medium" | "high" | "critical" ) { switch (rate) { case "low": return { color: "bg-green-100 text-green-800", text: "Low" } case "medium": return { color: "bg-yellow-100 text-yellow-800", text: "Medium" } case "high": return { color: "bg-orange-100 text-orange-800", text: "High" } case "critical": return { color: "bg-red-100 text-red-800", text: "Critical" } default: return { color: "bg-gray-100 text-gray-800", text: "Unknown" } } } /** * Get month name from month number (1-12) */ export function getMonthName(month: string | number): string { const months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ] const monthNum = parseInt(month.toString()) if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) { return "Invalid Month" } return months[monthNum - 1] } /** * Format a date into a readable string */ export function formatDateString(date: Date | string): string { if (!date) return "Unknown Date" const d = typeof date === "string" ? new Date(date) : date return d.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", }) } // Helper function to get district name from district ID // export const getDistrictName = (districtId: string): string => { // const feature = districtsGeoJson.features.find( // (f) => f.properties?.kode_kec === districtId // ) // return ( // feature?.properties?.nama || // feature?.properties?.kecamatan || // "Unknown District" // ) // } /** * Format number with commas or abbreviate large numbers */ export function formatNumber(num?: number): string { if (num === undefined || num === null) return "N/A" // If number is in the thousands, abbreviate if (num >= 1_000_000) { return (num / 1_000_000).toFixed(1) + "M" } if (num >= 1_000) { return (num / 1_000).toFixed(1) + "K" } // Otherwise, format with commas return num.toLocaleString() } const CRIME_SEVERITY_MAP: Record< string, "Critical" | "High" | "Medium" | "Low" > = { // Critical - Highest risk of death Pembunuhan: "Critical", "Kelalaian Akibatkan Orang Mati": "Critical", "Lahgun Senpi/Handak/Sajam": "Critical", // Firearms/explosives/weapons misuse Penculikan: "Critical", Separatisme: "Critical", "Selundup Senpi": "Critical", // Firearms smuggling "Kebakaran / Meletus": "Critical", Pembakaran: "Critical", // High - Significant risk of death or serious harm Perkosaan: "High", PTPPO: "High", // Human trafficking "Trafficking In Person": "High", "Penganiayaan Berat": "High", Curas: "High", // Violent theft "Pemerasan Dan Pengancaman": "High", Premanisme: "High", "Membahayakan Kam Umum": "High", // Endangering public safety Pengeroyokan: "High", // Group assault "Konflik Etnis": "High", // Medium - Moderate risk, potential for physical harm PKDRT: "Medium", // Domestic violence "Penganiayaan Ringan": "Medium", "Kelalaian Akibatkan Orang Luka": "Medium", Curanmor: "Medium", // Vehicle theft Curat: "Medium", // Theft with aggravating circumstances "Pencurian Biasa": "Medium", "Perlindungan Anak": "Medium", "Kenakalan Remaja": "Medium", Pengrusakan: "Medium", "Perbuatan Tidak Menyenangkan": "Medium", "BBM Illegal": "Medium", "Illegal Mining": "Medium", "Illegal Logging": "Medium", "Illegal Fishing": "Medium", // Low - Little to no direct risk of physical harm/death "Pemalsuan Materai": "Low", "Pemalsuan Surat": "Low", Perzinahan: "Low", "Member Suap": "Low", Penipuan: "Low", Agraria: "Low", "Peradilan Anak": "Low", Upal: "Low", "Terhadap Ketertiban Umum": "Low", Penghinaan: "Low", "Sumpah Palsu": "Low", Perjudian: "Low", "Menerima Suap": "Low", "Pekerjakan Anak": "Low", Penggelapan: "Low", "Perlindungan Saksi – Korban": "Low", "Perlindungan TKI": "Low", Pornografi: "Low", Keimigrasian: "Low", Satwa: "Low", "Money Loudering": "Low", "Trans Ekonomi Crime": "Low", ITE: "Low", "Pemerintah Daerah": "Low", "Sistem Peradilan Anak": "Low", "Pidum Lainnya": "Low", "Penyelenggaraan Pemilu": "Low", "Niaga Pupuk": "Low", Ekstradisi: "Low", Fidusia: "Low", "Perlindungan Konsumen": "Low", Korupsi: "Low", "Pidter Lainnya": "Low", } export function getIncidentSeverity( incident: any ): "Low" | "Medium" | "High" | "Critical" { if (!incident) return "Low" const category = incident.category || "Unknown" // console.log(incident.category); const criticalSeverityCategories = [ "Pembunuhan", "Kelalaian Akibatkan Orang Mati", "Lahgun Senpi/Handak/Sajam", "Penculikan", "Separatisme", "Selundup Senpi", "Kebakaran / Meletus", "Pembakaran", "Perkosaan", ] const highSeverityCategories = [ "PTPPO", "Trafficking In Person", "Penganiayaan Berat", "Curas", "Pemerasan Dan Pengancaman", "Premanisme", "Membahayakan Kam Umum", "Pengeroyokan", "Konflik Etnis", ] const mediumSeverityCategories = [ "PKDRT", "Penganiayaan Ringan", "Kelalaian Akibatkan Orang Luka", "Curanmor", "Curat", "Pencurian Biasa", "Perlindungan Anak", "Kenakalan Remaja", "Pengrusakan", "Perbuatan Tidak Menyenangkan", "BBM Illegal", "Illegal Mining", "Illegal Logging", "Illegal Fishing", "Penadahan", "Curingan", ] const lowSeverityCategories = [ "Pemalsuan Materai", "Pemalsuan Surat", "Perzinahan", "Member Suap", "Penipuan", "Agraria", "Peradilan Anak", "Upal", "Terhadap Ketertiban Umum", "Penghinaan", "Sumpah Palsu", "Perjudian", "Menerima Suap", "Pekerjakan Anak", "Penggelapan", "Perlindungan Saksi – Korban", "Perlindungan TKI", "Pornografi", "Keimigrasian", "Satwa", "Money Loudering", "Trans Ekonomi Crime", "ITE", "Pemerintah Daerah", "Sistem Peradilan Anak", "Pidum Lainnya", "Penyelenggaraan Pemilu", "Niaga Pupuk", "Ekstradisi", "Fidusia", "Perlindungan Konsumen", "Korupsi", "Pidter Lainnya", ] if (criticalSeverityCategories.includes(category)) return "Critical" if (highSeverityCategories.includes(category)) return "High" if (mediumSeverityCategories.includes(category)) return "Medium" if (lowSeverityCategories.includes(category)) return "Low" return "Low" } export function formatMonthKey(monthKey: string): string { const [year, month] = monthKey.split("-").map(Number) return `${getMonthName(month)} ${year}` } export function getTimeAgo(timestamp: string | Date) { const now = new Date() const eventTime = new Date(timestamp) const diffMs = now.getTime() - eventTime.getTime() const diffMins = Math.floor(diffMs / 60000) const diffHours = Math.floor(diffMins / 60) const diffDays = Math.floor(diffHours / 24) if (diffDays > 0) return `${diffDays} day${diffDays > 1 ? "s" : ""} ago` if (diffHours > 0) return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago` if (diffMins > 0) return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago` return "just now" } // export const getBreadcrumbItems = ( // pathname: string // ): { label: string; href: string; isLast: boolean }[] => { // // Split the path and filter empty // const segments = pathname.split("/").filter(Boolean) // // Build up the path for each segment // let path = "" // return segments.map((seg: string, idx: number) => { // path += "/" + seg // // Try to find a matching nav item // let label = seg // // Search in navPreMain, navMain, and subItems recursively // const findLabel = (items: any[]): string | null => { // for (const item of items) { // if (item.url === path) return item.title // if (item.subItems) { // const found = findLabel(item.subItems) // if (found) return found // } // if (item.subSubItems) { // const found = findLabel(item.subSubItems) // if (found) return found // } // } // return null // } // label = // findLabel( // getNavData({ // id: "0", // name: "John Doe", // email: "john.doe@example.com", // avatar: "https://avatars.githubusercontent.com/u/1486366", // }).NavPreMain // ) || // findLabel( // getNavData({ // id: "0", // name: "John Doe", // email: "john.doe@example.com", // avatar: "https://avatars.githubusercontent.com/u/1486366", // }).reports // ) || // seg.charAt(0).toUpperCase() + seg.slice(1) // return { // label, // href: path, // isLast: idx === segments.length - 1, // } // }) // } export function generateFilterId( prefixOrOptions?: keyof typeof prefixes | IGenerateFilterIdOptions, inputOptions: IGenerateFilterIdOptions = {} ) { const finalOptions = typeof prefixOrOptions === "object" ? prefixOrOptions : inputOptions const prefix = typeof prefixOrOptions === "object" ? undefined : prefixOrOptions const { length = 12, separator = "_" } = finalOptions const id = customAlphabet( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", length )() return prefix ? `${prefixes[prefix]}${separator}${id}` : id } export function extractTimeRangeFilter( filters: any[] ): [number, number] | null { if (!Array.isArray(filters)) return null const timeRangeFilter = filters.find( (f) => f.id === "timeRange" && Array.isArray(f.value) && f.value.length === 2 ) return timeRangeFilter ? timeRangeFilter.value : null }