4FBIL6IZUDNCXTM6EUHTEOJRHVI4LIIX4BU2IXPXKR362GKIAJMQC XEXJLBOH6HQAUZRUNH3CCPNUD4HRNCKMRZ5UJ6UUCO76KV6WUJAAC PVQBFR72OCQGYF2G2KDWNKBHWJ24N6D653X6KARBGUSYBIHIXPRQC YX7LU4WRAUDMWS3DEDXZDSF6DXBHLYDWVSMSRK6KIW3MO6GRXSVQC M3JUJ2WWZGCVMBITKRM5FUJMHFYL2QRMXJUVRUE4AC2RF74AOL5AC 2OYSY7VNMQ4C3CQMX7VKKD2JSZM72TJ3LL4GEDUQYG6RJTNIROLAC O7W4FZVRKDQDAAXEW4T7P262PPRILRCSSACODMUTQZ6VNR36PVCQC M4UG5FMI5ICQRLJCCNSLV3ZKCGIXDQ65ECJM4PBE3NZYHT3CJLDAC DOQBQX4IQSDBYYSBP4BEMTMJUKZPSC33KKXPAGZ3A5BRJMMKHCRQC HM75N4NTZ4BBSSDC7TUSYOQ4SIF3G6KPZA5QRYCVCVRSKQVTJAXAC 4M3EBLTLSS2BRCM42ZP7WVD4YMRRLGV2P2XF47IAV5XHHJD52HTQC CVW5G63BAFGQBR5YTZIHTZVVVECP2U3XI76APTKKOODGWHRBEIBQC RLH37YB4D7O42IFM2T7GJG4AVVAURWBZ7AOTHAWR7YJZRG3JOPLQC ROQGXQWL2V363K3W7TVVYKIAX4N4IWRERN5BJ7NYJRRVB6OMIJ4QC 4RBE543WLHA7PIYT4W7YEJPF6XKZ2UGKPJBQ3CTLJ44AOMGHCEYQC LYPSC7BOH6T45FCPRHSCXILAJSJ74D5WSQTUIKPWD5ECXOYGUY5AC J2RLNDEXTGAV4BB6ANIIR7XJLJBHSB4NFQWSBWHNAFB6DMLGS5RAC HBM7XFBGVMKW3P3VZFNQTJMINZ4DC3D4TZMT4TPTJRXC62HKZMMQC POIBWSL3JFHT2KN3STFSJX3INSYKEJTX6KSW3N7BVEKWX2GJ6T7QC /*** UI utility functions*/import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";/*** Combines class names with Tailwind's merge function* This utility is used by shadcn components for conditional class handling*/export function cn(...inputs: ClassValue[]) {return twMerge(clsx(inputs));}
/*** Re-export all utility functions from a single entry point*/export * from './ui';export * from './formatting';export * from './data-processing';
/*** Convert seconds to a formatted duration string (MM:SS)** @param durationSec - Duration in seconds* @returns Formatted duration string in MM:SS format*/export function formatDuration(durationSec: number): string {const minutes = Math.floor(durationSec / 60);const seconds = Math.floor(durationSec % 60);return `${minutes}:${seconds.toString().padStart(2, '0')}`;}/*** Format moon phase value as a fixed decimal string** @param phase - Moon phase value (0-1) or null/undefined* @returns Formatted moon phase string with 2 decimal places or "—" if not available*/export function formatMoonPhase(phase: number | null | undefined): string {if (phase === null || phase === undefined) return "—";try {return Number(phase).toFixed(2);} catch {return "—";}}/*** Format battery voltage with units** @param volts - Battery voltage value or null/undefined* @returns Formatted voltage string with units or "—" if not available*/export function formatBatteryVoltage(volts: number | null | undefined): string {if (volts === null || volts === undefined) return "—";try {return Number(volts).toFixed(1) + 'V';} catch {return "—";}}/*** Format temperature with units** @param temp - Temperature in Celsius or null/undefined* @returns Formatted temperature string with units or "—" if not available*/export function formatTemperature(temp: number | null | undefined): string {if (temp === null || temp === undefined) return "—";try {return Number(temp).toFixed(1) + '°C';} catch {return "—";}}/*** Format date and time in a human-readable format** @param isoString - ISO date string* @returns Formatted date and time string*/export function formatDateTime(isoString: string): string {try {const date = new Date(isoString);return new Intl.DateTimeFormat('en-GB', {year: 'numeric',month: 'short',day: '2-digit',hour: '2-digit',minute: '2-digit',hour12: false}).format(date);} catch {return isoString;}}/*** Format a sample rate value with kHz unit** @param rate - Sample rate in Hz* @returns Formatted sample rate with kHz unit*/export function formatSampleRate(rate: number): string {return `${(rate / 1000).toFixed(1)} kHz`;}
/*** Process JSON metadata string to handle various formats and edge cases** @param jsonData - Metadata in string or object format* @returns Properly processed metadata object or the original if parsing fails*/export function processJsonMetadata(jsonData: unknown): unknown {if (!jsonData) return null;let processedJson = jsonData;try {// If it's already a string that looks like a JSON string, try to parse itif (typeof jsonData === 'string' &&(jsonData.startsWith('{') || jsonData.startsWith('['))) {processedJson = JSON.parse(jsonData);}// Some databases might return the json as a string with escaped quoteselse if (typeof jsonData === 'string' && jsonData.includes('\\"')) {// Handle double-escaped JSONconst unescaped = jsonData.replace(/\\"/g, '"');processedJson = JSON.parse(unescaped);}} catch (e) {console.error("Error processing metadata JSON:", e);// Keep the original if parsing failsprocessedJson = jsonData;}return processedJson;}/*** Create pagination metadata for API responses** @param currentPage - Current page number* @param pageSize - Items per page* @param totalItems - Total number of items* @returns Pagination metadata object*/export function createPaginationMetadata(currentPage: number, pageSize: number, totalItems: number) {const totalPages = Math.ceil(totalItems / pageSize);return {currentPage,pageSize,totalPages,totalItems,hasNextPage: currentPage < totalPages,hasPreviousPage: currentPage > 1,};}/*** Check if valid metadata is present in any file** @param files - Array of files to check* @returns Boolean indicating if any file has valid metadata*/export function hasValidMetadata(files: Array<{ metadata: unknown }>): boolean {return files.some(file => {if (!file.metadata) return false;try {const meta = typeof file.metadata === 'string'? JSON.parse(file.metadata): file.metadata;return meta && typeof meta === 'object' && Object.keys(meta).length > 0;} catch {return false;}});}/*** Check if any files have moth metadata** @param files - Array of files to check* @returns Boolean indicating if any file has moth metadata*/export function hasValidMothMetadata(files: Array<{ mothMetadata?: { gain: string | null; batteryV: number | null; tempC: number | null; } | null }>): boolean {return files.some(file =>file.mothMetadata &&(file.mothMetadata.gain !== null ||file.mothMetadata.batteryV !== null ||file.mothMetadata.tempC !== null));}/*** Check if any files have species information** @param files - Array of files to check* @returns Boolean indicating if any file has species data*/export function hasSpeciesData(files: Array<{ species?: Array<unknown> }>): boolean {return files.some(file => file.species && file.species.length > 0);}
/*** Species-related types*/export interface CallType {id: string;label: string;}export interface Species {id: string;label: string;ebirdCode: string | null;description: string | null;callTypes: CallType[];}export interface SpeciesResponse {data: Species[];}
/*** Shared pagination types used across components*/export interface PaginationMetadata {currentPage: number;pageSize: number;totalPages: number;totalItems: number;hasNextPage: boolean;hasPreviousPage: boolean;}
/*** Location-related types*/import { PaginationMetadata } from './pagination';export interface Location {id: string;datasetId: string;name: string;latitude: number | null;longitude: number | null;description: string | null;createdBy: string;createdAt: string;lastModified: string;modifiedBy: string;active: boolean;}export interface LocationsResponse {data: Location[];pagination?: PaginationMetadata;}
/*** Re-export all types from a single entry point*/export * from './pagination';export * from './file';export * from './dataset';export * from './location';export * from './cluster';export type { CallType, Species, SpeciesResponse } from './species';
/*** Common file-related types used across components*/import { PaginationMetadata } from './pagination';export interface FileMetadata {noiseType?: string[];noiseLevel?: string;[key: string]: unknown;}export interface MothMetadata {gain: string | null;batteryV: number | null;tempC: number | null;}export interface FileSpecies {id: string;label: string;ebirdCode: string | null;description: string | null;}export interface File {id: string;fileName: string;path: string | null;timestampLocal: string;duration: number;sampleRate: number;locationId: string;clusterId?: string;description: string | null;maybeSolarNight: boolean | null;maybeCivilNight: boolean | null;moonPhase: number | null;metadata: FileMetadata | null;mothMetadata?: MothMetadata | null;species?: FileSpecies[];}export interface FilesFilters {solarNight: boolean | null;civilNight: boolean | null;speciesId: string | null;datasetId?: string;}export interface FilesResponse {data: File[];pagination: PaginationMetadata;filters?: FilesFilters;}export type NightFilter = 'none' | 'solarNight' | 'solarDay' | 'civilNight' | 'civilDay';export interface SpeciesOption {id: string;label: string;}
/*** Dataset-related interfaces*/export interface Dataset {id: string;name: string;public: boolean;description?: string;createdBy: string;createdAt: string;lastModified: string;modifiedBy: string;owner: string;active: boolean;type: string;}export interface DatasetsResponse {data: Dataset[];}
/*** Cluster-related types*/export interface RecordingPattern {recordS: number;sleepS: number;}export interface Cluster {id: string;locationId: string;name: string;description: string | null;createdBy: string;createdAt: string;lastModified: string;modifiedBy: string;active: boolean;timezoneId: string | null;cyclicRecordingPatternId: string | null;recordingPattern: RecordingPattern | null;sampleRate: number;}export interface ClustersResponse {data: Cluster[];}
interface FileMetadata {noiseType?: string[];noiseLevel?: string;[key: string]: unknown;}interface MothMetadata {gain: string | null;batteryV: number | null;tempC: number | null;}interface Species {id: string;label: string;ebirdCode: string | null;description: string | null;}interface File {id: string;fileName: string;path: string | null;timestampLocal: string;duration: number;sampleRate: number;locationId: string;clusterId: string;description: string | null;maybeSolarNight: boolean | null;maybeCivilNight: boolean | null;moonPhase: number | null;metadata: FileMetadata | null;mothMetadata?: MothMetadata | null;species?: Species[];}
import {File,FilesResponse,NightFilter,PaginationMetadata,SpeciesOption} from "../types";
interface PaginationMetadata {currentPage: number;pageSize: number;totalPages: number;totalItems: number;hasNextPage: boolean;hasPreviousPage: boolean;}interface FilesFilters {datasetId: string;speciesId: string;solarNight: boolean | null;civilNight: boolean | null;}interface FilesResponse {data: File[];pagination: PaginationMetadata;filters?: FilesFilters;}type NightFilter = 'none' | 'solarNight' | 'solarDay' | 'civilNight' | 'civilDay';// Species data type for the dropdowninterface SpeciesOption {id: string;label: string;}
// interface Dataset {// id: string;// name: string;// }interface LocationsResponse {data: Location[];pagination?: {currentPage: number;pageSize: number;totalPages: number;totalItems: number;hasNextPage: boolean;hasPreviousPage: boolean;};}// Removing unused interface
interface FileMetadata {noiseType?: string[];noiseLevel?: string;[key: string]: unknown;}interface MothMetadata {gain: string | null;batteryV: number | null;tempC: number | null;}interface Species {id: string;label: string;ebirdCode: string | null;description: string | null;}interface File {id: string;fileName: string;path: string | null;timestampLocal: string;duration: number;sampleRate: number;locationId: string;description: string | null;maybeSolarNight: boolean | null;maybeCivilNight: boolean | null;moonPhase: number | null;metadata: FileMetadata | null;mothMetadata?: MothMetadata | null;species?: Species[];}interface PaginationMetadata {currentPage: number;pageSize: number;totalPages: number;totalItems: number;hasNextPage: boolean;hasPreviousPage: boolean;}interface FilesFilters {solarNight: boolean | null;civilNight: boolean | null;speciesId: string | null;}interface FilesResponse {data: File[];pagination: PaginationMetadata;filters?: FilesFilters;}type NightFilter = 'none' | 'solarNight' | 'solarDay' | 'civilNight' | 'civilDay';// Species data type for the dropdowninterface SpeciesOption {id: string;label: string;}
import {File,FilesResponse,NightFilter,PaginationMetadata,SpeciesOption} from "../types";
interface RecordingPattern {recordS: number;sleepS: number;}interface Cluster {id: string;locationId: string;name: string;description: string | null;createdBy: string;createdAt: string;lastModified: string;modifiedBy: string;active: boolean;timezoneId: string | null;cyclicRecordingPatternId: string | null;recordingPattern: RecordingPattern | null;sampleRate: number;}// Removed unused Location interface
import { Cluster, ClustersResponse } from "../types";