MIF_E31221222/sigap-website/app/_utils/common.ts

351 lines
9.6 KiB
TypeScript

import { format } from "date-fns";
import { redirect } from "next/navigation";
import { DateFormatOptions, DateFormatPattern } from "../_lib/types/date-format.interface";
import { toast } from "sonner";
import { IUserSchema } from "@/src/entities/models/users/users.model";
/**
* 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, lastName: string): 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: IUserSchema[] | 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, string>): 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}`;
};