All files / components/transcript utils.ts

0% Statements 0/111
0% Branches 0/111
0% Functions 0/31
0% Lines 0/102

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211                                                                                                                                                                                                                                                                                                                                                                                                                                     
import { parseIsoDateUtc, formatDateWithYear, formatHumanDateRange } from "@/utils/date";
import { leaveTypeLabel } from "@/utils/leave";
 
export type UnknownRecord = Record<string, unknown>;
 
export function isRecord(value: unknown): value is UnknownRecord {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
 
export function asRecord(value: unknown): UnknownRecord | null {
  return isRecord(value) ? value : null;
}
 
export function asRecordArray(value: unknown): UnknownRecord[] {
  return Array.isArray(value) ? value.filter(isRecord) : [];
}
 
export function asOptionalString(value: unknown) {
  return typeof value === "string" ? value : undefined;
}
 
export function asString(value: unknown, fallback = "—") {
  return typeof value === "string" ? value : fallback;
}
 
export function asNumber(value: unknown, fallback = "—") {
  return typeof value === "number" && Number.isFinite(value)
    ? String(value)
    : fallback;
}
 
export function formatStatus(status: string) {
  if (!status) return "—";
  return status
    .replaceAll("_", " ")
    .replace(/\b\w/g, (char) => char.toUpperCase());
}
 
export function compactLeaveTypeLabel(label: string) {
  const normalized = label.trim();
  if (!normalized) return "—";
 
  return normalized.replace(/\s+leave$/i, "");
}
 
export function normalizeRequestStatus(status: unknown) {
  return asString(status, "").trim().toLowerCase();
}
 
export function buildRequestQueryText(request: UnknownRecord) {
  const leaveType = compactLeaveTypeLabel(
    asString(request.leaveTypeLabel, asString(request.leaveType, "")),
  );
 
  const startDate = asOptionalString(request.startDate)?.trim();
  const endDate = asOptionalString(request.endDate)?.trim();
  const datePart = startDate && endDate && startDate !== endDate
    ? `${startDate} ${endDate}`
    : startDate || endDate || null;
 
  const parts = [
    asOptionalString(request.employeeName)?.trim(),
    leaveType && leaveType !== "—" ? leaveType : null,
    datePart,
  ].filter((part): part is string => Boolean(part));
 
  return parts.join(" ").trim();
}
 
export function getRequestRowSummary(request: UnknownRecord) {
  const employeeName = asOptionalString(request.employeeName)?.trim();
  const leaveType = compactLeaveTypeLabel(
    asString(request.leaveTypeLabel, asString(request.leaveType, "")),
  );
  const dateRange = formatHumanDateRange(
    asString(request.startDate, ""),
    asString(request.endDate, ""),
  );
 
  return [employeeName, leaveType && leaveType !== "—" ? leaveType : null, dateRange]
    .filter((part): part is string => Boolean(part))
    .join(" • ");
}
 
export function getBalanceRowSummary(balance: UnknownRecord) {
  const leaveType = compactLeaveTypeLabel(
    leaveTypeLabel(asString(balance.leaveType, "annual")),
  );
  const remaining = asNumber(balance.remaining, "");
 
  return remaining
    ? `${leaveType} • Remaining ${remaining}`
    : leaveType;
}
 
export function getMemberRowSummary(member: UnknownRecord) {
  return asOptionalString(member.employeeName)?.trim() ?? "";
}
 
export function isFutureOrTodayDate(dateStr: unknown): boolean {
  if (typeof dateStr !== "string" || !dateStr.trim()) return false;
  const start = parseIsoDateUtc(dateStr.trim());
  if (!start) return false;
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  return start >= today;
}
 
export function isPrimitiveValue(value: unknown): value is string | number | boolean | null {
  return (
    value === null ||
    typeof value === "string" ||
    typeof value === "number" ||
    typeof value === "boolean"
  );
}
 
export function canRenderCellValue(value: unknown) {
  if (isPrimitiveValue(value)) return true;
  return Array.isArray(value) && value.every((item) => isPrimitiveValue(item));
}
 
export function hasRenderableColumnValue(rows: UnknownRecord[], key: string) {
  return rows.some((row) => {
    const value = row[key];
    if (value === undefined || value === null || value === "") {
      return false;
    }
 
    return canRenderCellValue(value);
  });
}
 
export function isNumericColumn(rows: UnknownRecord[], key: string) {
  if (GENERIC_NUMERIC_COLUMN_KEY_REGEX.test(key)) {
    return true;
  }
 
  const values = rows
    .map((row) => row[key])
    .filter((value) => value !== null && value !== undefined && value !== "");
 
  if (values.length === 0) {
    return false;
  }
 
  return values.every((value) => typeof value === "number" && Number.isFinite(value));
}
 
export const GENERIC_NUMERIC_COLUMN_KEY_REGEX =
  /(^|_)(count|total|days?|allowance|used|pending|remaining|hours?)$/i;
 
export function formatGenericCellValue(value: unknown, key: string) {
  if (value === null || value === undefined || value === "") {
    return "—";
  }
 
  if (typeof value === "number") {
    return Number.isFinite(value) ? String(value) : "—";
  }
 
  if (typeof value === "boolean") {
    return value ? "Yes" : "No";
  }
 
  if (typeof value === "string") {
    const normalizedValue = value.trim();
    if (!normalizedValue) return "—";
 
    if (key.toLowerCase().endsWith("date")) {
      const parsedDate = parseIsoDateUtc(normalizedValue);
      if (parsedDate) {
        return formatDateWithYear(parsedDate);
      }
    }
 
    return normalizedValue;
  }
 
  if (Array.isArray(value)) {
    const primitiveItems = value.filter((item) => isPrimitiveValue(item));
    if (primitiveItems.length === value.length) {
      const joinedValue = primitiveItems
        .map((item) => (item === null ? "—" : String(item)))
        .join(", ");
 
      return joinedValue.length > 0 ? joinedValue : "—";
    }
  }
 
  return "—";
}
 
export function isBalanceLikeRecord(row: UnknownRecord) {
  return (
    "leaveType" in row &&
    "allowance" in row &&
    "used" in row &&
    "pending" in row &&
    "remaining" in row
  );
}
 
export function isRequestLikeRecord(row: UnknownRecord) {
  const hasLeaveType = "leaveType" in row || "leaveTypeLabel" in row;
  const hasDateRange = "dateRange" in row || "startDate" in row || "endDate" in row;
  const hasStatus = "status" in row;
 
  return hasLeaveType && hasDateRange && hasStatus;
}