@pattern-stack/frontend-patterns 0.2.0-alpha.5 → 0.2.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atoms/components/data/index.d.ts +1 -0
- package/dist/atoms/components/data/index.d.ts.map +1 -1
- package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts +16 -0
- package/dist/atoms/composed/ConnectionStatus/ConnectionStatus.d.ts.map +1 -0
- package/dist/atoms/composed/ConnectionStatus/index.d.ts +3 -0
- package/dist/atoms/composed/ConnectionStatus/index.d.ts.map +1 -0
- package/dist/atoms/hooks/index.d.ts +1 -0
- package/dist/atoms/hooks/index.d.ts.map +1 -1
- package/dist/atoms/hooks/useOnlineStatus.d.ts +16 -0
- package/dist/atoms/hooks/useOnlineStatus.d.ts.map +1 -0
- package/dist/atoms/services/api/client.d.ts.map +1 -1
- package/dist/atoms/types/auth.d.ts +6 -0
- package/dist/atoms/types/auth.d.ts.map +1 -1
- package/dist/atoms/utils/entity-card-mapping.d.ts +105 -0
- package/dist/atoms/utils/entity-card-mapping.d.ts.map +1 -0
- package/dist/atoms/utils/index.d.ts +1 -0
- package/dist/atoms/utils/index.d.ts.map +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
- package/dist/frontend-patterns.css +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +507 -12
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +506 -11
- package/dist/index.js.map +1 -1
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.es.js
CHANGED
|
@@ -9,7 +9,7 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
|
9
9
|
import * as React from "react";
|
|
10
10
|
import React__default, { useRef, useState, useEffect, useMemo, forwardRef, useCallback, useContext, createContext, useId, Component } from "react";
|
|
11
11
|
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
|
12
|
-
import { Map as Map$1, Calculator, Brain, User, Handshake, Truck, Building, HelpCircle, Info, AlertCircle, Check, ArrowDown, ArrowUp, ArrowLeft, ArrowRight, ChevronUp, ChevronLeft, ChevronDown, ChevronRight, Music, Video, Image, Folder, File, Flag, Tag, Bookmark, Heart, Star, MapPin, Clock, Calendar, Phone, Mail, Unlock, Lock, Share, Upload, Download, Eye, Trash2, Edit, Plus, Search, Bell, Settings, Home, Layout, TrendingUp, Database, BarChart3, Users, Shield, X, Menu, Palette, Loader2, EyeOff, InfoIcon,
|
|
12
|
+
import { Map as Map$1, Calculator, Brain, User, Handshake, Truck, Building, HelpCircle, Info, AlertCircle, Check, ArrowDown, ArrowUp, ArrowLeft, ArrowRight, ChevronUp, ChevronLeft, ChevronDown, ChevronRight, Music, Video, Image, Folder, File, Flag, Tag, Bookmark, Heart, Star, MapPin, Clock, Calendar, Phone, Mail, Unlock, Lock, Share, Upload, Download, Eye, Trash2, Edit, Plus, Search, Bell, Settings, Home, Layout, TrendingUp, Database, BarChart3, Users, Shield, X, Menu, Palette, Circle, Building2, Package, FileText, Loader2, EyeOff, InfoIcon, ChevronsUpDown, AreaChart, LineChart, TrendingDown, Minus, AlertTriangle, Activity, WifiOff, CloudOff, Cloud, CheckCircle, FileX, Sun, Moon, LogOut, Square, Waves, Zap, TreePine, Sparkles, Sunset, DollarSign, ShoppingCart, Target, ExternalLink, MoreHorizontal, Filter, Edit2, Undo2, RefreshCw, History, Save, Copy, Grid3X3, Layers, XCircle, Briefcase } from "lucide-react";
|
|
13
13
|
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
14
14
|
import { cva } from "class-variance-authority";
|
|
15
15
|
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
|
@@ -2009,6 +2009,362 @@ function renderField(value, fieldName, fieldType, breakpoint, item, customRender
|
|
|
2009
2009
|
function cn(...inputs) {
|
|
2010
2010
|
return twMerge(clsx(inputs));
|
|
2011
2011
|
}
|
|
2012
|
+
const STATUS_COLOR_MAP = {
|
|
2013
|
+
// Success states
|
|
2014
|
+
active: "success",
|
|
2015
|
+
completed: "success",
|
|
2016
|
+
approved: "success",
|
|
2017
|
+
paid: "success",
|
|
2018
|
+
delivered: "success",
|
|
2019
|
+
resolved: "success",
|
|
2020
|
+
closed: "success",
|
|
2021
|
+
// Warning states
|
|
2022
|
+
pending: "warning",
|
|
2023
|
+
processing: "warning",
|
|
2024
|
+
review: "warning",
|
|
2025
|
+
draft: "warning",
|
|
2026
|
+
waiting: "warning",
|
|
2027
|
+
// Error states
|
|
2028
|
+
failed: "error",
|
|
2029
|
+
rejected: "error",
|
|
2030
|
+
cancelled: "error",
|
|
2031
|
+
overdue: "error",
|
|
2032
|
+
blocked: "error",
|
|
2033
|
+
// Info states
|
|
2034
|
+
new: "info",
|
|
2035
|
+
open: "info",
|
|
2036
|
+
in_progress: "info",
|
|
2037
|
+
scheduled: "info",
|
|
2038
|
+
// Neutral (default)
|
|
2039
|
+
inactive: "neutral",
|
|
2040
|
+
archived: "neutral",
|
|
2041
|
+
unknown: "neutral"
|
|
2042
|
+
};
|
|
2043
|
+
function getStatusColor(value) {
|
|
2044
|
+
if (typeof value !== "string") return "neutral";
|
|
2045
|
+
const normalized = value.toLowerCase().replace(/[_-]/g, "_");
|
|
2046
|
+
return STATUS_COLOR_MAP[normalized] ?? "neutral";
|
|
2047
|
+
}
|
|
2048
|
+
const CATEGORY_MAP = {
|
|
2049
|
+
account: 1,
|
|
2050
|
+
customer: 1,
|
|
2051
|
+
client: 1,
|
|
2052
|
+
user: 2,
|
|
2053
|
+
contact: 2,
|
|
2054
|
+
person: 2,
|
|
2055
|
+
order: 3,
|
|
2056
|
+
sale: 3,
|
|
2057
|
+
transaction: 3,
|
|
2058
|
+
product: 4,
|
|
2059
|
+
item: 4,
|
|
2060
|
+
inventory: 4,
|
|
2061
|
+
task: 5,
|
|
2062
|
+
activity: 5,
|
|
2063
|
+
event: 5,
|
|
2064
|
+
file: 6,
|
|
2065
|
+
document: 6,
|
|
2066
|
+
report: 6,
|
|
2067
|
+
tag: 7,
|
|
2068
|
+
category: 7,
|
|
2069
|
+
label: 7,
|
|
2070
|
+
default: 8
|
|
2071
|
+
};
|
|
2072
|
+
function getCategoryForEntity(entityType) {
|
|
2073
|
+
if (!entityType) return 8;
|
|
2074
|
+
const normalized = entityType.toLowerCase();
|
|
2075
|
+
for (const [key, value] of Object.entries(CATEGORY_MAP)) {
|
|
2076
|
+
if (normalized.includes(key)) return value;
|
|
2077
|
+
}
|
|
2078
|
+
return 8;
|
|
2079
|
+
}
|
|
2080
|
+
function getIconForEntity(entityType) {
|
|
2081
|
+
if (!entityType) return /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" });
|
|
2082
|
+
const normalized = entityType.toLowerCase();
|
|
2083
|
+
if (normalized.includes("account") || normalized.includes("customer") || normalized.includes("client")) {
|
|
2084
|
+
return /* @__PURE__ */ jsx(Building2, { className: "w-4 h-4" });
|
|
2085
|
+
}
|
|
2086
|
+
if (normalized.includes("user") || normalized.includes("contact") || normalized.includes("person")) {
|
|
2087
|
+
return /* @__PURE__ */ jsx(User, { className: "w-4 h-4" });
|
|
2088
|
+
}
|
|
2089
|
+
if (normalized.includes("product") || normalized.includes("item")) {
|
|
2090
|
+
return /* @__PURE__ */ jsx(Package, { className: "w-4 h-4" });
|
|
2091
|
+
}
|
|
2092
|
+
if (normalized.includes("file") || normalized.includes("document")) {
|
|
2093
|
+
return /* @__PURE__ */ jsx(FileText, { className: "w-4 h-4" });
|
|
2094
|
+
}
|
|
2095
|
+
if (normalized.includes("tag") || normalized.includes("category")) {
|
|
2096
|
+
return /* @__PURE__ */ jsx(Tag, { className: "w-4 h-4" });
|
|
2097
|
+
}
|
|
2098
|
+
return /* @__PURE__ */ jsx(Circle, { className: "w-4 h-4" });
|
|
2099
|
+
}
|
|
2100
|
+
function normalizeImportance(importance) {
|
|
2101
|
+
switch (importance) {
|
|
2102
|
+
case "critical":
|
|
2103
|
+
case "high":
|
|
2104
|
+
case "primary":
|
|
2105
|
+
return "primary";
|
|
2106
|
+
case "medium":
|
|
2107
|
+
case "secondary":
|
|
2108
|
+
return "secondary";
|
|
2109
|
+
default:
|
|
2110
|
+
return "tertiary";
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
function isPrimaryImportance(importance) {
|
|
2114
|
+
return normalizeImportance(importance) === "primary";
|
|
2115
|
+
}
|
|
2116
|
+
function formatValueForCard(value, type, format) {
|
|
2117
|
+
if (value === null || value === void 0) return "—";
|
|
2118
|
+
switch (type) {
|
|
2119
|
+
case "money": {
|
|
2120
|
+
const num = Number(value);
|
|
2121
|
+
const currency = (format == null ? void 0 : format.currency) ?? "USD";
|
|
2122
|
+
if (num >= 1e6) {
|
|
2123
|
+
return new Intl.NumberFormat("en-US", {
|
|
2124
|
+
style: "currency",
|
|
2125
|
+
currency,
|
|
2126
|
+
notation: "compact",
|
|
2127
|
+
maximumFractionDigits: 1
|
|
2128
|
+
}).format(num);
|
|
2129
|
+
}
|
|
2130
|
+
if (num >= 1e3) {
|
|
2131
|
+
return new Intl.NumberFormat("en-US", {
|
|
2132
|
+
style: "currency",
|
|
2133
|
+
currency,
|
|
2134
|
+
notation: "compact",
|
|
2135
|
+
maximumFractionDigits: 1
|
|
2136
|
+
}).format(num);
|
|
2137
|
+
}
|
|
2138
|
+
return new Intl.NumberFormat("en-US", {
|
|
2139
|
+
style: "currency",
|
|
2140
|
+
currency,
|
|
2141
|
+
maximumFractionDigits: 0
|
|
2142
|
+
}).format(num);
|
|
2143
|
+
}
|
|
2144
|
+
case "percent": {
|
|
2145
|
+
const num = Number(value);
|
|
2146
|
+
return `${num.toFixed(0)}%`;
|
|
2147
|
+
}
|
|
2148
|
+
case "number": {
|
|
2149
|
+
const num = Number(value);
|
|
2150
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
2151
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
|
|
2152
|
+
return String(num);
|
|
2153
|
+
}
|
|
2154
|
+
case "date":
|
|
2155
|
+
case "datetime": {
|
|
2156
|
+
const date = new Date(String(value));
|
|
2157
|
+
if (isNaN(date.getTime())) return String(value);
|
|
2158
|
+
return date.toLocaleDateString("en-US", {
|
|
2159
|
+
month: "short",
|
|
2160
|
+
day: "numeric"
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
case "boolean":
|
|
2164
|
+
return value ? "Yes" : "No";
|
|
2165
|
+
default:
|
|
2166
|
+
return String(value);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
function autoDetectMapping(columns) {
|
|
2170
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2171
|
+
const primaryCols = columns.filter((c) => isPrimaryImportance(c.importance));
|
|
2172
|
+
const titleField = ((_a = primaryCols.find((c) => ["text", "entity", "user"].includes(c.type))) == null ? void 0 : _a.field) ?? ((_b = primaryCols[0]) == null ? void 0 : _b.field) ?? ((_c = columns[0]) == null ? void 0 : _c.field);
|
|
2173
|
+
const valueField = (_d = primaryCols.find((c) => c.type === "money")) == null ? void 0 : _d.field;
|
|
2174
|
+
const statusField = (_e = primaryCols.find(
|
|
2175
|
+
(c) => c.type === "status" || c.type === "badge"
|
|
2176
|
+
)) == null ? void 0 : _e.field;
|
|
2177
|
+
const usedForMain = new Set([titleField, valueField, statusField].filter(Boolean));
|
|
2178
|
+
const remainingPrimary = primaryCols.filter((c) => !usedForMain.has(c.field));
|
|
2179
|
+
const subtitleField = (_f = remainingPrimary[0]) == null ? void 0 : _f.field;
|
|
2180
|
+
const usedFields = new Set(
|
|
2181
|
+
[titleField, valueField, statusField, subtitleField].filter(Boolean)
|
|
2182
|
+
);
|
|
2183
|
+
const metadataFields = primaryCols.filter((c) => !usedFields.has(c.field)).slice(0, 3).map((c) => c.field);
|
|
2184
|
+
return {
|
|
2185
|
+
titleField: titleField ?? "id",
|
|
2186
|
+
subtitleField,
|
|
2187
|
+
valueField,
|
|
2188
|
+
statusField,
|
|
2189
|
+
metadataFields,
|
|
2190
|
+
iconConfig: void 0
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function entityToListCardProps(item, columns, mapping, entityType) {
|
|
2194
|
+
const autoMapping = autoDetectMapping(columns);
|
|
2195
|
+
const effectiveMapping = { ...autoMapping, ...mapping };
|
|
2196
|
+
const {
|
|
2197
|
+
titleField,
|
|
2198
|
+
subtitleField,
|
|
2199
|
+
valueField,
|
|
2200
|
+
statusField,
|
|
2201
|
+
metadataFields,
|
|
2202
|
+
iconConfig
|
|
2203
|
+
} = effectiveMapping;
|
|
2204
|
+
const getColumn = (field) => field ? columns.find((c) => c.field === field) : void 0;
|
|
2205
|
+
const title = item[titleField] != null ? String(item[titleField]) : "—";
|
|
2206
|
+
const subtitle = subtitleField && item[subtitleField] != null ? String(item[subtitleField]) : void 0;
|
|
2207
|
+
let value;
|
|
2208
|
+
if (valueField && item[valueField] != null) {
|
|
2209
|
+
const valueCol = getColumn(valueField);
|
|
2210
|
+
const formatted = formatValueForCard(
|
|
2211
|
+
item[valueField],
|
|
2212
|
+
(valueCol == null ? void 0 : valueCol.type) ?? "text",
|
|
2213
|
+
valueCol == null ? void 0 : valueCol.format
|
|
2214
|
+
);
|
|
2215
|
+
value = {
|
|
2216
|
+
text: formatted,
|
|
2217
|
+
variant: "success"
|
|
2218
|
+
// Money typically green
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
let badge;
|
|
2222
|
+
if (statusField && item[statusField] != null) {
|
|
2223
|
+
const statusValue = String(item[statusField]);
|
|
2224
|
+
badge = {
|
|
2225
|
+
text: statusValue,
|
|
2226
|
+
variant: "status",
|
|
2227
|
+
status: getStatusColor(statusValue),
|
|
2228
|
+
size: "sm"
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
const metadata = metadataFields == null ? void 0 : metadataFields.map((field) => {
|
|
2232
|
+
const col = getColumn(field);
|
|
2233
|
+
if (!col || item[field] == null) return null;
|
|
2234
|
+
return formatValueForCard(item[field], col.type, col.format);
|
|
2235
|
+
}).filter((v) => v !== null);
|
|
2236
|
+
let icon;
|
|
2237
|
+
if (iconConfig) {
|
|
2238
|
+
const iconValue = iconConfig.field ? item[iconConfig.field] : void 0;
|
|
2239
|
+
if (iconConfig.variant === "status" && iconValue) {
|
|
2240
|
+
icon = {
|
|
2241
|
+
icon: iconConfig.icon ?? getIconForEntity(entityType),
|
|
2242
|
+
variant: "status",
|
|
2243
|
+
status: getStatusColor(iconValue),
|
|
2244
|
+
size: "sm"
|
|
2245
|
+
};
|
|
2246
|
+
} else {
|
|
2247
|
+
icon = {
|
|
2248
|
+
icon: iconConfig.icon ?? getIconForEntity(entityType),
|
|
2249
|
+
variant: "category",
|
|
2250
|
+
category: getCategoryForEntity(entityType),
|
|
2251
|
+
size: "sm"
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
} else {
|
|
2255
|
+
icon = {
|
|
2256
|
+
icon: getIconForEntity(entityType),
|
|
2257
|
+
variant: "category",
|
|
2258
|
+
category: getCategoryForEntity(entityType),
|
|
2259
|
+
size: "sm"
|
|
2260
|
+
};
|
|
2261
|
+
}
|
|
2262
|
+
return {
|
|
2263
|
+
icon,
|
|
2264
|
+
title,
|
|
2265
|
+
subtitle,
|
|
2266
|
+
metadata,
|
|
2267
|
+
value,
|
|
2268
|
+
badge
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
const HEADER_ABBREVIATIONS = {
|
|
2272
|
+
customer: "Cust",
|
|
2273
|
+
account: "Acct",
|
|
2274
|
+
amount: "Amt",
|
|
2275
|
+
quantity: "Qty",
|
|
2276
|
+
priority: "Pri",
|
|
2277
|
+
status: "Status",
|
|
2278
|
+
created: "Created",
|
|
2279
|
+
updated: "Updated",
|
|
2280
|
+
description: "Desc",
|
|
2281
|
+
total: "Total",
|
|
2282
|
+
items: "Items",
|
|
2283
|
+
number: "#",
|
|
2284
|
+
date: "Date",
|
|
2285
|
+
time: "Time"
|
|
2286
|
+
};
|
|
2287
|
+
function abbreviateHeader(label) {
|
|
2288
|
+
const lower = label.toLowerCase();
|
|
2289
|
+
for (const [key, abbrev] of Object.entries(HEADER_ABBREVIATIONS)) {
|
|
2290
|
+
if (lower.includes(key)) return abbrev;
|
|
2291
|
+
}
|
|
2292
|
+
if (label.length > 8) return label.slice(0, 6) + "…";
|
|
2293
|
+
return label;
|
|
2294
|
+
}
|
|
2295
|
+
function generateCompactColumns(columns, options = {}) {
|
|
2296
|
+
const {
|
|
2297
|
+
maxColumns = 5,
|
|
2298
|
+
abbreviateHeaders = true,
|
|
2299
|
+
requiredFields = [],
|
|
2300
|
+
excludeFields = []
|
|
2301
|
+
} = options;
|
|
2302
|
+
const excludeSet = new Set(excludeFields);
|
|
2303
|
+
const requiredSet = new Set(requiredFields);
|
|
2304
|
+
const filteredColumns = columns.filter((col) => {
|
|
2305
|
+
if (excludeSet.has(col.field)) return false;
|
|
2306
|
+
return true;
|
|
2307
|
+
});
|
|
2308
|
+
const required = filteredColumns.filter((c) => requiredSet.has(c.field));
|
|
2309
|
+
const primary = filteredColumns.filter(
|
|
2310
|
+
(c) => !requiredSet.has(c.field) && normalizeImportance(c.importance) === "primary"
|
|
2311
|
+
);
|
|
2312
|
+
const secondary = filteredColumns.filter(
|
|
2313
|
+
(c) => !requiredSet.has(c.field) && normalizeImportance(c.importance) === "secondary"
|
|
2314
|
+
);
|
|
2315
|
+
const selectedColumns = [...required, ...primary, ...secondary].slice(
|
|
2316
|
+
0,
|
|
2317
|
+
maxColumns
|
|
2318
|
+
);
|
|
2319
|
+
return selectedColumns.map((col) => ({
|
|
2320
|
+
key: col.field,
|
|
2321
|
+
header: abbreviateHeaders ? abbreviateHeader(col.label) : col.label,
|
|
2322
|
+
sortable: col.sortable,
|
|
2323
|
+
type: col.type,
|
|
2324
|
+
format: col.format,
|
|
2325
|
+
// Compact widths
|
|
2326
|
+
width: getCompactWidth(col.type)
|
|
2327
|
+
}));
|
|
2328
|
+
}
|
|
2329
|
+
function getCompactWidth(type) {
|
|
2330
|
+
switch (type) {
|
|
2331
|
+
case "money":
|
|
2332
|
+
return "70px";
|
|
2333
|
+
case "number":
|
|
2334
|
+
case "percent":
|
|
2335
|
+
return "50px";
|
|
2336
|
+
case "status":
|
|
2337
|
+
case "badge":
|
|
2338
|
+
return "80px";
|
|
2339
|
+
case "date":
|
|
2340
|
+
return "70px";
|
|
2341
|
+
case "datetime":
|
|
2342
|
+
return "90px";
|
|
2343
|
+
case "boolean":
|
|
2344
|
+
return "50px";
|
|
2345
|
+
default:
|
|
2346
|
+
return "120px";
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function createMobileCardRenderer(columns, mapping, entityType) {
|
|
2350
|
+
const { ListCard: ListCard2 } = require("../components/data/ListCard");
|
|
2351
|
+
return ({ data, onItemClick }) => /* @__PURE__ */ jsx("div", { className: "space-y-2", children: data.map((item, index) => {
|
|
2352
|
+
const cardProps = entityToListCardProps(
|
|
2353
|
+
item,
|
|
2354
|
+
columns,
|
|
2355
|
+
mapping,
|
|
2356
|
+
entityType
|
|
2357
|
+
);
|
|
2358
|
+
return /* @__PURE__ */ jsx(
|
|
2359
|
+
ListCard2,
|
|
2360
|
+
{
|
|
2361
|
+
...cardProps,
|
|
2362
|
+
onClick: onItemClick ? () => onItemClick(item) : void 0
|
|
2363
|
+
},
|
|
2364
|
+
item.id ?? index
|
|
2365
|
+
);
|
|
2366
|
+
}) });
|
|
2367
|
+
}
|
|
2012
2368
|
let globalAuthService = null;
|
|
2013
2369
|
function setGlobalAuthService(authService) {
|
|
2014
2370
|
globalAuthService = authService;
|
|
@@ -2062,6 +2418,9 @@ class ApiClient {
|
|
|
2062
2418
|
async (error) => {
|
|
2063
2419
|
var _a, _b, _c, _d;
|
|
2064
2420
|
const status = (_a = error.response) == null ? void 0 : _a.status;
|
|
2421
|
+
if (!error.response) {
|
|
2422
|
+
return Promise.reject(error);
|
|
2423
|
+
}
|
|
2065
2424
|
if (status === 401 || status === 403) {
|
|
2066
2425
|
if (globalAuthService) {
|
|
2067
2426
|
const tokenData = globalAuthService.getTokenData();
|
|
@@ -2079,12 +2438,12 @@ class ApiClient {
|
|
|
2079
2438
|
const errorInfo = { status, message: (_c = (_b = error.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail };
|
|
2080
2439
|
const handled = (_d = globalAuthService.onAuthError) == null ? void 0 : _d.call(globalAuthService, errorInfo);
|
|
2081
2440
|
if (!handled) {
|
|
2082
|
-
window.location.
|
|
2441
|
+
window.location.href = "/login";
|
|
2083
2442
|
}
|
|
2084
2443
|
} else {
|
|
2085
2444
|
localStorage.removeItem("auth_token");
|
|
2086
2445
|
localStorage.removeItem("auth_user");
|
|
2087
|
-
window.location.
|
|
2446
|
+
window.location.href = "/login";
|
|
2088
2447
|
}
|
|
2089
2448
|
}
|
|
2090
2449
|
return Promise.reject(error);
|
|
@@ -2459,6 +2818,61 @@ function useEntityDetail(entity, id, options = {}) {
|
|
|
2459
2818
|
refetch
|
|
2460
2819
|
};
|
|
2461
2820
|
}
|
|
2821
|
+
function useOnlineStatus(healthEndpoint) {
|
|
2822
|
+
const [isOnline, setIsOnline] = useState(
|
|
2823
|
+
typeof navigator !== "undefined" ? navigator.onLine : true
|
|
2824
|
+
);
|
|
2825
|
+
const [isBackendReachable, setIsBackendReachable] = useState(true);
|
|
2826
|
+
const [lastBackendContact, setLastBackendContact] = useState(
|
|
2827
|
+
null
|
|
2828
|
+
);
|
|
2829
|
+
useEffect(() => {
|
|
2830
|
+
const handleOnline = () => setIsOnline(true);
|
|
2831
|
+
const handleOffline = () => {
|
|
2832
|
+
setIsOnline(false);
|
|
2833
|
+
setIsBackendReachable(false);
|
|
2834
|
+
};
|
|
2835
|
+
window.addEventListener("online", handleOnline);
|
|
2836
|
+
window.addEventListener("offline", handleOffline);
|
|
2837
|
+
return () => {
|
|
2838
|
+
window.removeEventListener("online", handleOnline);
|
|
2839
|
+
window.removeEventListener("offline", handleOffline);
|
|
2840
|
+
};
|
|
2841
|
+
}, []);
|
|
2842
|
+
const checkBackend = useCallback(async () => {
|
|
2843
|
+
if (!isOnline) {
|
|
2844
|
+
setIsBackendReachable(false);
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
try {
|
|
2848
|
+
const endpoint = healthEndpoint || "/api/v1/health";
|
|
2849
|
+
const response = await fetch(endpoint, {
|
|
2850
|
+
method: "GET",
|
|
2851
|
+
cache: "no-store"
|
|
2852
|
+
});
|
|
2853
|
+
const reachable = response.ok;
|
|
2854
|
+
setIsBackendReachable(reachable);
|
|
2855
|
+
if (reachable) {
|
|
2856
|
+
setLastBackendContact(Date.now());
|
|
2857
|
+
}
|
|
2858
|
+
return reachable;
|
|
2859
|
+
} catch {
|
|
2860
|
+
setIsBackendReachable(false);
|
|
2861
|
+
return false;
|
|
2862
|
+
}
|
|
2863
|
+
}, [isOnline, healthEndpoint]);
|
|
2864
|
+
useEffect(() => {
|
|
2865
|
+
if (isOnline) {
|
|
2866
|
+
checkBackend();
|
|
2867
|
+
}
|
|
2868
|
+
}, [isOnline, checkBackend]);
|
|
2869
|
+
return {
|
|
2870
|
+
isOnline,
|
|
2871
|
+
isBackendReachable,
|
|
2872
|
+
lastBackendContact,
|
|
2873
|
+
checkBackend
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2462
2876
|
const buttonVariants = cva(
|
|
2463
2877
|
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/20 focus-visible:ring-offset-0 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]",
|
|
2464
2878
|
{
|
|
@@ -5715,6 +6129,75 @@ const ActivityFeed = ({
|
|
|
5715
6129
|
] }) })
|
|
5716
6130
|
] });
|
|
5717
6131
|
};
|
|
6132
|
+
const statusConfig = {
|
|
6133
|
+
online: {
|
|
6134
|
+
icon: Cloud,
|
|
6135
|
+
label: "Online",
|
|
6136
|
+
color: "text-green-600 dark:text-green-400",
|
|
6137
|
+
bgColor: "bg-green-100 dark:bg-green-900/30"
|
|
6138
|
+
},
|
|
6139
|
+
degraded: {
|
|
6140
|
+
icon: CloudOff,
|
|
6141
|
+
label: "Server Offline",
|
|
6142
|
+
color: "text-yellow-600 dark:text-yellow-400",
|
|
6143
|
+
bgColor: "bg-yellow-100 dark:bg-yellow-900/30"
|
|
6144
|
+
},
|
|
6145
|
+
offline: {
|
|
6146
|
+
icon: WifiOff,
|
|
6147
|
+
label: "Offline",
|
|
6148
|
+
color: "text-red-600 dark:text-red-400",
|
|
6149
|
+
bgColor: "bg-red-100 dark:bg-red-900/30"
|
|
6150
|
+
}
|
|
6151
|
+
};
|
|
6152
|
+
function ConnectionStatus({
|
|
6153
|
+
healthEndpoint,
|
|
6154
|
+
variant = "badge",
|
|
6155
|
+
className,
|
|
6156
|
+
showWhenOnline = false
|
|
6157
|
+
}) {
|
|
6158
|
+
const { isOnline, isBackendReachable } = useOnlineStatus(healthEndpoint);
|
|
6159
|
+
const status = !isOnline ? "offline" : !isBackendReachable ? "degraded" : "online";
|
|
6160
|
+
if (status === "online" && !showWhenOnline) {
|
|
6161
|
+
return null;
|
|
6162
|
+
}
|
|
6163
|
+
const config = statusConfig[status];
|
|
6164
|
+
const Icon2 = config.icon;
|
|
6165
|
+
if (variant === "badge") {
|
|
6166
|
+
return /* @__PURE__ */ jsxs(
|
|
6167
|
+
"div",
|
|
6168
|
+
{
|
|
6169
|
+
className: cn(
|
|
6170
|
+
"inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium",
|
|
6171
|
+
config.bgColor,
|
|
6172
|
+
config.color,
|
|
6173
|
+
className
|
|
6174
|
+
),
|
|
6175
|
+
children: [
|
|
6176
|
+
/* @__PURE__ */ jsx(Icon2, { className: "h-3 w-3" }),
|
|
6177
|
+
/* @__PURE__ */ jsx("span", { children: config.label })
|
|
6178
|
+
]
|
|
6179
|
+
}
|
|
6180
|
+
);
|
|
6181
|
+
}
|
|
6182
|
+
return /* @__PURE__ */ jsxs(
|
|
6183
|
+
"div",
|
|
6184
|
+
{
|
|
6185
|
+
className: cn(
|
|
6186
|
+
"flex items-center gap-2 px-3 py-2 rounded-lg",
|
|
6187
|
+
config.bgColor,
|
|
6188
|
+
className
|
|
6189
|
+
),
|
|
6190
|
+
children: [
|
|
6191
|
+
/* @__PURE__ */ jsx(Icon2, { className: cn("h-4 w-4", config.color) }),
|
|
6192
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
6193
|
+
/* @__PURE__ */ jsx("span", { className: cn("text-sm font-medium", config.color), children: config.label }),
|
|
6194
|
+
status === "degraded" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Working with cached data" }),
|
|
6195
|
+
status === "offline" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Check your connection" })
|
|
6196
|
+
] })
|
|
6197
|
+
]
|
|
6198
|
+
}
|
|
6199
|
+
);
|
|
6200
|
+
}
|
|
5718
6201
|
const FormField = ({
|
|
5719
6202
|
label,
|
|
5720
6203
|
description,
|
|
@@ -9056,7 +9539,10 @@ const AppHeader = ({ className }) => {
|
|
|
9056
9539
|
placeholder: "Search models, tests, jobs, and components..."
|
|
9057
9540
|
}
|
|
9058
9541
|
) }),
|
|
9059
|
-
/* @__PURE__ */
|
|
9542
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-4", children: [
|
|
9543
|
+
/* @__PURE__ */ jsx(ConnectionStatus, {}),
|
|
9544
|
+
/* @__PURE__ */ jsx(UserMenu, { category: 8 })
|
|
9545
|
+
] })
|
|
9060
9546
|
] }) })
|
|
9061
9547
|
}
|
|
9062
9548
|
);
|
|
@@ -10409,10 +10895,12 @@ function AuthProvider({
|
|
|
10409
10895
|
try {
|
|
10410
10896
|
await authService.refreshToken();
|
|
10411
10897
|
setUser(storedUser);
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10898
|
+
if (!(config == null ? void 0 : config.offlineFirst)) {
|
|
10899
|
+
try {
|
|
10900
|
+
const freshUser = await authService.getCurrentUser();
|
|
10901
|
+
if (freshUser) setUser(freshUser);
|
|
10902
|
+
} catch {
|
|
10903
|
+
}
|
|
10416
10904
|
}
|
|
10417
10905
|
} catch {
|
|
10418
10906
|
authService.clearAuth();
|
|
@@ -10422,10 +10910,12 @@ function AuthProvider({
|
|
|
10422
10910
|
}
|
|
10423
10911
|
} else {
|
|
10424
10912
|
setUser(storedUser);
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
10913
|
+
if (!(config == null ? void 0 : config.offlineFirst)) {
|
|
10914
|
+
try {
|
|
10915
|
+
const freshUser = await authService.getCurrentUser();
|
|
10916
|
+
if (freshUser) setUser(freshUser);
|
|
10917
|
+
} catch {
|
|
10918
|
+
}
|
|
10429
10919
|
}
|
|
10430
10920
|
}
|
|
10431
10921
|
}
|
|
@@ -17108,6 +17598,7 @@ export {
|
|
|
17108
17598
|
ColorSwatch,
|
|
17109
17599
|
ComponentShowcasePage,
|
|
17110
17600
|
ComponentShowcaseTemplate,
|
|
17601
|
+
ConnectionStatus,
|
|
17111
17602
|
DASHBOARD_CHART_HEIGHTS,
|
|
17112
17603
|
DASHBOARD_CONTAINER_HEIGHTS,
|
|
17113
17604
|
DASHBOARD_HEIGHT_CLASSES,
|
|
@@ -17233,14 +17724,17 @@ export {
|
|
|
17233
17724
|
cn,
|
|
17234
17725
|
createAPIDataTemplate,
|
|
17235
17726
|
createApiClient,
|
|
17727
|
+
createMobileCardRenderer,
|
|
17236
17728
|
createReactApp,
|
|
17237
17729
|
createSimpleApp,
|
|
17238
17730
|
defaultFieldRenderers,
|
|
17239
17731
|
detectBulkOperationType,
|
|
17240
17732
|
detectUIConfig,
|
|
17733
|
+
entityToListCardProps,
|
|
17241
17734
|
env,
|
|
17242
17735
|
formatNumberWithTooltip,
|
|
17243
17736
|
generateBulkOperationName,
|
|
17737
|
+
generateCompactColumns,
|
|
17244
17738
|
getAnimationClasses,
|
|
17245
17739
|
getChartHeight,
|
|
17246
17740
|
getContainerHeightClass,
|
|
@@ -17282,6 +17776,7 @@ export {
|
|
|
17282
17776
|
useMediaQuery,
|
|
17283
17777
|
useMockAuth,
|
|
17284
17778
|
useNavigation,
|
|
17779
|
+
useOnlineStatus,
|
|
17285
17780
|
useOverflowDetection,
|
|
17286
17781
|
usePermissions,
|
|
17287
17782
|
useResponsiveClasses,
|