@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.js
CHANGED
|
@@ -2052,6 +2052,362 @@ function renderField(value, fieldName, fieldType, breakpoint, item, customRender
|
|
|
2052
2052
|
function cn(...inputs) {
|
|
2053
2053
|
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
2054
2054
|
}
|
|
2055
|
+
const STATUS_COLOR_MAP = {
|
|
2056
|
+
// Success states
|
|
2057
|
+
active: "success",
|
|
2058
|
+
completed: "success",
|
|
2059
|
+
approved: "success",
|
|
2060
|
+
paid: "success",
|
|
2061
|
+
delivered: "success",
|
|
2062
|
+
resolved: "success",
|
|
2063
|
+
closed: "success",
|
|
2064
|
+
// Warning states
|
|
2065
|
+
pending: "warning",
|
|
2066
|
+
processing: "warning",
|
|
2067
|
+
review: "warning",
|
|
2068
|
+
draft: "warning",
|
|
2069
|
+
waiting: "warning",
|
|
2070
|
+
// Error states
|
|
2071
|
+
failed: "error",
|
|
2072
|
+
rejected: "error",
|
|
2073
|
+
cancelled: "error",
|
|
2074
|
+
overdue: "error",
|
|
2075
|
+
blocked: "error",
|
|
2076
|
+
// Info states
|
|
2077
|
+
new: "info",
|
|
2078
|
+
open: "info",
|
|
2079
|
+
in_progress: "info",
|
|
2080
|
+
scheduled: "info",
|
|
2081
|
+
// Neutral (default)
|
|
2082
|
+
inactive: "neutral",
|
|
2083
|
+
archived: "neutral",
|
|
2084
|
+
unknown: "neutral"
|
|
2085
|
+
};
|
|
2086
|
+
function getStatusColor(value) {
|
|
2087
|
+
if (typeof value !== "string") return "neutral";
|
|
2088
|
+
const normalized = value.toLowerCase().replace(/[_-]/g, "_");
|
|
2089
|
+
return STATUS_COLOR_MAP[normalized] ?? "neutral";
|
|
2090
|
+
}
|
|
2091
|
+
const CATEGORY_MAP = {
|
|
2092
|
+
account: 1,
|
|
2093
|
+
customer: 1,
|
|
2094
|
+
client: 1,
|
|
2095
|
+
user: 2,
|
|
2096
|
+
contact: 2,
|
|
2097
|
+
person: 2,
|
|
2098
|
+
order: 3,
|
|
2099
|
+
sale: 3,
|
|
2100
|
+
transaction: 3,
|
|
2101
|
+
product: 4,
|
|
2102
|
+
item: 4,
|
|
2103
|
+
inventory: 4,
|
|
2104
|
+
task: 5,
|
|
2105
|
+
activity: 5,
|
|
2106
|
+
event: 5,
|
|
2107
|
+
file: 6,
|
|
2108
|
+
document: 6,
|
|
2109
|
+
report: 6,
|
|
2110
|
+
tag: 7,
|
|
2111
|
+
category: 7,
|
|
2112
|
+
label: 7,
|
|
2113
|
+
default: 8
|
|
2114
|
+
};
|
|
2115
|
+
function getCategoryForEntity(entityType) {
|
|
2116
|
+
if (!entityType) return 8;
|
|
2117
|
+
const normalized = entityType.toLowerCase();
|
|
2118
|
+
for (const [key, value] of Object.entries(CATEGORY_MAP)) {
|
|
2119
|
+
if (normalized.includes(key)) return value;
|
|
2120
|
+
}
|
|
2121
|
+
return 8;
|
|
2122
|
+
}
|
|
2123
|
+
function getIconForEntity(entityType) {
|
|
2124
|
+
if (!entityType) return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { className: "w-4 h-4" });
|
|
2125
|
+
const normalized = entityType.toLowerCase();
|
|
2126
|
+
if (normalized.includes("account") || normalized.includes("customer") || normalized.includes("client")) {
|
|
2127
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Building2, { className: "w-4 h-4" });
|
|
2128
|
+
}
|
|
2129
|
+
if (normalized.includes("user") || normalized.includes("contact") || normalized.includes("person")) {
|
|
2130
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "w-4 h-4" });
|
|
2131
|
+
}
|
|
2132
|
+
if (normalized.includes("product") || normalized.includes("item")) {
|
|
2133
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Package, { className: "w-4 h-4" });
|
|
2134
|
+
}
|
|
2135
|
+
if (normalized.includes("file") || normalized.includes("document")) {
|
|
2136
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "w-4 h-4" });
|
|
2137
|
+
}
|
|
2138
|
+
if (normalized.includes("tag") || normalized.includes("category")) {
|
|
2139
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Tag, { className: "w-4 h-4" });
|
|
2140
|
+
}
|
|
2141
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { className: "w-4 h-4" });
|
|
2142
|
+
}
|
|
2143
|
+
function normalizeImportance(importance) {
|
|
2144
|
+
switch (importance) {
|
|
2145
|
+
case "critical":
|
|
2146
|
+
case "high":
|
|
2147
|
+
case "primary":
|
|
2148
|
+
return "primary";
|
|
2149
|
+
case "medium":
|
|
2150
|
+
case "secondary":
|
|
2151
|
+
return "secondary";
|
|
2152
|
+
default:
|
|
2153
|
+
return "tertiary";
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
function isPrimaryImportance(importance) {
|
|
2157
|
+
return normalizeImportance(importance) === "primary";
|
|
2158
|
+
}
|
|
2159
|
+
function formatValueForCard(value, type, format) {
|
|
2160
|
+
if (value === null || value === void 0) return "—";
|
|
2161
|
+
switch (type) {
|
|
2162
|
+
case "money": {
|
|
2163
|
+
const num = Number(value);
|
|
2164
|
+
const currency = (format == null ? void 0 : format.currency) ?? "USD";
|
|
2165
|
+
if (num >= 1e6) {
|
|
2166
|
+
return new Intl.NumberFormat("en-US", {
|
|
2167
|
+
style: "currency",
|
|
2168
|
+
currency,
|
|
2169
|
+
notation: "compact",
|
|
2170
|
+
maximumFractionDigits: 1
|
|
2171
|
+
}).format(num);
|
|
2172
|
+
}
|
|
2173
|
+
if (num >= 1e3) {
|
|
2174
|
+
return new Intl.NumberFormat("en-US", {
|
|
2175
|
+
style: "currency",
|
|
2176
|
+
currency,
|
|
2177
|
+
notation: "compact",
|
|
2178
|
+
maximumFractionDigits: 1
|
|
2179
|
+
}).format(num);
|
|
2180
|
+
}
|
|
2181
|
+
return new Intl.NumberFormat("en-US", {
|
|
2182
|
+
style: "currency",
|
|
2183
|
+
currency,
|
|
2184
|
+
maximumFractionDigits: 0
|
|
2185
|
+
}).format(num);
|
|
2186
|
+
}
|
|
2187
|
+
case "percent": {
|
|
2188
|
+
const num = Number(value);
|
|
2189
|
+
return `${num.toFixed(0)}%`;
|
|
2190
|
+
}
|
|
2191
|
+
case "number": {
|
|
2192
|
+
const num = Number(value);
|
|
2193
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(1)}M`;
|
|
2194
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(1)}K`;
|
|
2195
|
+
return String(num);
|
|
2196
|
+
}
|
|
2197
|
+
case "date":
|
|
2198
|
+
case "datetime": {
|
|
2199
|
+
const date = new Date(String(value));
|
|
2200
|
+
if (isNaN(date.getTime())) return String(value);
|
|
2201
|
+
return date.toLocaleDateString("en-US", {
|
|
2202
|
+
month: "short",
|
|
2203
|
+
day: "numeric"
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
case "boolean":
|
|
2207
|
+
return value ? "Yes" : "No";
|
|
2208
|
+
default:
|
|
2209
|
+
return String(value);
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
function autoDetectMapping(columns) {
|
|
2213
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2214
|
+
const primaryCols = columns.filter((c) => isPrimaryImportance(c.importance));
|
|
2215
|
+
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);
|
|
2216
|
+
const valueField = (_d = primaryCols.find((c) => c.type === "money")) == null ? void 0 : _d.field;
|
|
2217
|
+
const statusField = (_e = primaryCols.find(
|
|
2218
|
+
(c) => c.type === "status" || c.type === "badge"
|
|
2219
|
+
)) == null ? void 0 : _e.field;
|
|
2220
|
+
const usedForMain = new Set([titleField, valueField, statusField].filter(Boolean));
|
|
2221
|
+
const remainingPrimary = primaryCols.filter((c) => !usedForMain.has(c.field));
|
|
2222
|
+
const subtitleField = (_f = remainingPrimary[0]) == null ? void 0 : _f.field;
|
|
2223
|
+
const usedFields = new Set(
|
|
2224
|
+
[titleField, valueField, statusField, subtitleField].filter(Boolean)
|
|
2225
|
+
);
|
|
2226
|
+
const metadataFields = primaryCols.filter((c) => !usedFields.has(c.field)).slice(0, 3).map((c) => c.field);
|
|
2227
|
+
return {
|
|
2228
|
+
titleField: titleField ?? "id",
|
|
2229
|
+
subtitleField,
|
|
2230
|
+
valueField,
|
|
2231
|
+
statusField,
|
|
2232
|
+
metadataFields,
|
|
2233
|
+
iconConfig: void 0
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
function entityToListCardProps(item, columns, mapping, entityType) {
|
|
2237
|
+
const autoMapping = autoDetectMapping(columns);
|
|
2238
|
+
const effectiveMapping = { ...autoMapping, ...mapping };
|
|
2239
|
+
const {
|
|
2240
|
+
titleField,
|
|
2241
|
+
subtitleField,
|
|
2242
|
+
valueField,
|
|
2243
|
+
statusField,
|
|
2244
|
+
metadataFields,
|
|
2245
|
+
iconConfig
|
|
2246
|
+
} = effectiveMapping;
|
|
2247
|
+
const getColumn = (field) => field ? columns.find((c) => c.field === field) : void 0;
|
|
2248
|
+
const title = item[titleField] != null ? String(item[titleField]) : "—";
|
|
2249
|
+
const subtitle = subtitleField && item[subtitleField] != null ? String(item[subtitleField]) : void 0;
|
|
2250
|
+
let value;
|
|
2251
|
+
if (valueField && item[valueField] != null) {
|
|
2252
|
+
const valueCol = getColumn(valueField);
|
|
2253
|
+
const formatted = formatValueForCard(
|
|
2254
|
+
item[valueField],
|
|
2255
|
+
(valueCol == null ? void 0 : valueCol.type) ?? "text",
|
|
2256
|
+
valueCol == null ? void 0 : valueCol.format
|
|
2257
|
+
);
|
|
2258
|
+
value = {
|
|
2259
|
+
text: formatted,
|
|
2260
|
+
variant: "success"
|
|
2261
|
+
// Money typically green
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
2264
|
+
let badge;
|
|
2265
|
+
if (statusField && item[statusField] != null) {
|
|
2266
|
+
const statusValue = String(item[statusField]);
|
|
2267
|
+
badge = {
|
|
2268
|
+
text: statusValue,
|
|
2269
|
+
variant: "status",
|
|
2270
|
+
status: getStatusColor(statusValue),
|
|
2271
|
+
size: "sm"
|
|
2272
|
+
};
|
|
2273
|
+
}
|
|
2274
|
+
const metadata = metadataFields == null ? void 0 : metadataFields.map((field) => {
|
|
2275
|
+
const col = getColumn(field);
|
|
2276
|
+
if (!col || item[field] == null) return null;
|
|
2277
|
+
return formatValueForCard(item[field], col.type, col.format);
|
|
2278
|
+
}).filter((v) => v !== null);
|
|
2279
|
+
let icon;
|
|
2280
|
+
if (iconConfig) {
|
|
2281
|
+
const iconValue = iconConfig.field ? item[iconConfig.field] : void 0;
|
|
2282
|
+
if (iconConfig.variant === "status" && iconValue) {
|
|
2283
|
+
icon = {
|
|
2284
|
+
icon: iconConfig.icon ?? getIconForEntity(entityType),
|
|
2285
|
+
variant: "status",
|
|
2286
|
+
status: getStatusColor(iconValue),
|
|
2287
|
+
size: "sm"
|
|
2288
|
+
};
|
|
2289
|
+
} else {
|
|
2290
|
+
icon = {
|
|
2291
|
+
icon: iconConfig.icon ?? getIconForEntity(entityType),
|
|
2292
|
+
variant: "category",
|
|
2293
|
+
category: getCategoryForEntity(entityType),
|
|
2294
|
+
size: "sm"
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
} else {
|
|
2298
|
+
icon = {
|
|
2299
|
+
icon: getIconForEntity(entityType),
|
|
2300
|
+
variant: "category",
|
|
2301
|
+
category: getCategoryForEntity(entityType),
|
|
2302
|
+
size: "sm"
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
return {
|
|
2306
|
+
icon,
|
|
2307
|
+
title,
|
|
2308
|
+
subtitle,
|
|
2309
|
+
metadata,
|
|
2310
|
+
value,
|
|
2311
|
+
badge
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
const HEADER_ABBREVIATIONS = {
|
|
2315
|
+
customer: "Cust",
|
|
2316
|
+
account: "Acct",
|
|
2317
|
+
amount: "Amt",
|
|
2318
|
+
quantity: "Qty",
|
|
2319
|
+
priority: "Pri",
|
|
2320
|
+
status: "Status",
|
|
2321
|
+
created: "Created",
|
|
2322
|
+
updated: "Updated",
|
|
2323
|
+
description: "Desc",
|
|
2324
|
+
total: "Total",
|
|
2325
|
+
items: "Items",
|
|
2326
|
+
number: "#",
|
|
2327
|
+
date: "Date",
|
|
2328
|
+
time: "Time"
|
|
2329
|
+
};
|
|
2330
|
+
function abbreviateHeader(label) {
|
|
2331
|
+
const lower = label.toLowerCase();
|
|
2332
|
+
for (const [key, abbrev] of Object.entries(HEADER_ABBREVIATIONS)) {
|
|
2333
|
+
if (lower.includes(key)) return abbrev;
|
|
2334
|
+
}
|
|
2335
|
+
if (label.length > 8) return label.slice(0, 6) + "…";
|
|
2336
|
+
return label;
|
|
2337
|
+
}
|
|
2338
|
+
function generateCompactColumns(columns, options = {}) {
|
|
2339
|
+
const {
|
|
2340
|
+
maxColumns = 5,
|
|
2341
|
+
abbreviateHeaders = true,
|
|
2342
|
+
requiredFields = [],
|
|
2343
|
+
excludeFields = []
|
|
2344
|
+
} = options;
|
|
2345
|
+
const excludeSet = new Set(excludeFields);
|
|
2346
|
+
const requiredSet = new Set(requiredFields);
|
|
2347
|
+
const filteredColumns = columns.filter((col) => {
|
|
2348
|
+
if (excludeSet.has(col.field)) return false;
|
|
2349
|
+
return true;
|
|
2350
|
+
});
|
|
2351
|
+
const required = filteredColumns.filter((c) => requiredSet.has(c.field));
|
|
2352
|
+
const primary = filteredColumns.filter(
|
|
2353
|
+
(c) => !requiredSet.has(c.field) && normalizeImportance(c.importance) === "primary"
|
|
2354
|
+
);
|
|
2355
|
+
const secondary = filteredColumns.filter(
|
|
2356
|
+
(c) => !requiredSet.has(c.field) && normalizeImportance(c.importance) === "secondary"
|
|
2357
|
+
);
|
|
2358
|
+
const selectedColumns = [...required, ...primary, ...secondary].slice(
|
|
2359
|
+
0,
|
|
2360
|
+
maxColumns
|
|
2361
|
+
);
|
|
2362
|
+
return selectedColumns.map((col) => ({
|
|
2363
|
+
key: col.field,
|
|
2364
|
+
header: abbreviateHeaders ? abbreviateHeader(col.label) : col.label,
|
|
2365
|
+
sortable: col.sortable,
|
|
2366
|
+
type: col.type,
|
|
2367
|
+
format: col.format,
|
|
2368
|
+
// Compact widths
|
|
2369
|
+
width: getCompactWidth(col.type)
|
|
2370
|
+
}));
|
|
2371
|
+
}
|
|
2372
|
+
function getCompactWidth(type) {
|
|
2373
|
+
switch (type) {
|
|
2374
|
+
case "money":
|
|
2375
|
+
return "70px";
|
|
2376
|
+
case "number":
|
|
2377
|
+
case "percent":
|
|
2378
|
+
return "50px";
|
|
2379
|
+
case "status":
|
|
2380
|
+
case "badge":
|
|
2381
|
+
return "80px";
|
|
2382
|
+
case "date":
|
|
2383
|
+
return "70px";
|
|
2384
|
+
case "datetime":
|
|
2385
|
+
return "90px";
|
|
2386
|
+
case "boolean":
|
|
2387
|
+
return "50px";
|
|
2388
|
+
default:
|
|
2389
|
+
return "120px";
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
function createMobileCardRenderer(columns, mapping, entityType) {
|
|
2393
|
+
const { ListCard: ListCard2 } = require("../components/data/ListCard");
|
|
2394
|
+
return ({ data, onItemClick }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: data.map((item, index) => {
|
|
2395
|
+
const cardProps = entityToListCardProps(
|
|
2396
|
+
item,
|
|
2397
|
+
columns,
|
|
2398
|
+
mapping,
|
|
2399
|
+
entityType
|
|
2400
|
+
);
|
|
2401
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2402
|
+
ListCard2,
|
|
2403
|
+
{
|
|
2404
|
+
...cardProps,
|
|
2405
|
+
onClick: onItemClick ? () => onItemClick(item) : void 0
|
|
2406
|
+
},
|
|
2407
|
+
item.id ?? index
|
|
2408
|
+
);
|
|
2409
|
+
}) });
|
|
2410
|
+
}
|
|
2055
2411
|
let globalAuthService = null;
|
|
2056
2412
|
function setGlobalAuthService(authService) {
|
|
2057
2413
|
globalAuthService = authService;
|
|
@@ -2105,6 +2461,9 @@ class ApiClient {
|
|
|
2105
2461
|
async (error) => {
|
|
2106
2462
|
var _a, _b, _c, _d;
|
|
2107
2463
|
const status = (_a = error.response) == null ? void 0 : _a.status;
|
|
2464
|
+
if (!error.response) {
|
|
2465
|
+
return Promise.reject(error);
|
|
2466
|
+
}
|
|
2108
2467
|
if (status === 401 || status === 403) {
|
|
2109
2468
|
if (globalAuthService) {
|
|
2110
2469
|
const tokenData = globalAuthService.getTokenData();
|
|
@@ -2122,12 +2481,12 @@ class ApiClient {
|
|
|
2122
2481
|
const errorInfo = { status, message: (_c = (_b = error.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail };
|
|
2123
2482
|
const handled = (_d = globalAuthService.onAuthError) == null ? void 0 : _d.call(globalAuthService, errorInfo);
|
|
2124
2483
|
if (!handled) {
|
|
2125
|
-
window.location.
|
|
2484
|
+
window.location.href = "/login";
|
|
2126
2485
|
}
|
|
2127
2486
|
} else {
|
|
2128
2487
|
localStorage.removeItem("auth_token");
|
|
2129
2488
|
localStorage.removeItem("auth_user");
|
|
2130
|
-
window.location.
|
|
2489
|
+
window.location.href = "/login";
|
|
2131
2490
|
}
|
|
2132
2491
|
}
|
|
2133
2492
|
return Promise.reject(error);
|
|
@@ -2502,6 +2861,61 @@ function useEntityDetail(entity, id, options = {}) {
|
|
|
2502
2861
|
refetch
|
|
2503
2862
|
};
|
|
2504
2863
|
}
|
|
2864
|
+
function useOnlineStatus(healthEndpoint) {
|
|
2865
|
+
const [isOnline, setIsOnline] = React.useState(
|
|
2866
|
+
typeof navigator !== "undefined" ? navigator.onLine : true
|
|
2867
|
+
);
|
|
2868
|
+
const [isBackendReachable, setIsBackendReachable] = React.useState(true);
|
|
2869
|
+
const [lastBackendContact, setLastBackendContact] = React.useState(
|
|
2870
|
+
null
|
|
2871
|
+
);
|
|
2872
|
+
React.useEffect(() => {
|
|
2873
|
+
const handleOnline = () => setIsOnline(true);
|
|
2874
|
+
const handleOffline = () => {
|
|
2875
|
+
setIsOnline(false);
|
|
2876
|
+
setIsBackendReachable(false);
|
|
2877
|
+
};
|
|
2878
|
+
window.addEventListener("online", handleOnline);
|
|
2879
|
+
window.addEventListener("offline", handleOffline);
|
|
2880
|
+
return () => {
|
|
2881
|
+
window.removeEventListener("online", handleOnline);
|
|
2882
|
+
window.removeEventListener("offline", handleOffline);
|
|
2883
|
+
};
|
|
2884
|
+
}, []);
|
|
2885
|
+
const checkBackend = React.useCallback(async () => {
|
|
2886
|
+
if (!isOnline) {
|
|
2887
|
+
setIsBackendReachable(false);
|
|
2888
|
+
return false;
|
|
2889
|
+
}
|
|
2890
|
+
try {
|
|
2891
|
+
const endpoint = healthEndpoint || "/api/v1/health";
|
|
2892
|
+
const response = await fetch(endpoint, {
|
|
2893
|
+
method: "GET",
|
|
2894
|
+
cache: "no-store"
|
|
2895
|
+
});
|
|
2896
|
+
const reachable = response.ok;
|
|
2897
|
+
setIsBackendReachable(reachable);
|
|
2898
|
+
if (reachable) {
|
|
2899
|
+
setLastBackendContact(Date.now());
|
|
2900
|
+
}
|
|
2901
|
+
return reachable;
|
|
2902
|
+
} catch {
|
|
2903
|
+
setIsBackendReachable(false);
|
|
2904
|
+
return false;
|
|
2905
|
+
}
|
|
2906
|
+
}, [isOnline, healthEndpoint]);
|
|
2907
|
+
React.useEffect(() => {
|
|
2908
|
+
if (isOnline) {
|
|
2909
|
+
checkBackend();
|
|
2910
|
+
}
|
|
2911
|
+
}, [isOnline, checkBackend]);
|
|
2912
|
+
return {
|
|
2913
|
+
isOnline,
|
|
2914
|
+
isBackendReachable,
|
|
2915
|
+
lastBackendContact,
|
|
2916
|
+
checkBackend
|
|
2917
|
+
};
|
|
2918
|
+
}
|
|
2505
2919
|
const buttonVariants = classVarianceAuthority.cva(
|
|
2506
2920
|
"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]",
|
|
2507
2921
|
{
|
|
@@ -5758,6 +6172,75 @@ const ActivityFeed = ({
|
|
|
5758
6172
|
] }) })
|
|
5759
6173
|
] });
|
|
5760
6174
|
};
|
|
6175
|
+
const statusConfig = {
|
|
6176
|
+
online: {
|
|
6177
|
+
icon: lucideReact.Cloud,
|
|
6178
|
+
label: "Online",
|
|
6179
|
+
color: "text-green-600 dark:text-green-400",
|
|
6180
|
+
bgColor: "bg-green-100 dark:bg-green-900/30"
|
|
6181
|
+
},
|
|
6182
|
+
degraded: {
|
|
6183
|
+
icon: lucideReact.CloudOff,
|
|
6184
|
+
label: "Server Offline",
|
|
6185
|
+
color: "text-yellow-600 dark:text-yellow-400",
|
|
6186
|
+
bgColor: "bg-yellow-100 dark:bg-yellow-900/30"
|
|
6187
|
+
},
|
|
6188
|
+
offline: {
|
|
6189
|
+
icon: lucideReact.WifiOff,
|
|
6190
|
+
label: "Offline",
|
|
6191
|
+
color: "text-red-600 dark:text-red-400",
|
|
6192
|
+
bgColor: "bg-red-100 dark:bg-red-900/30"
|
|
6193
|
+
}
|
|
6194
|
+
};
|
|
6195
|
+
function ConnectionStatus({
|
|
6196
|
+
healthEndpoint,
|
|
6197
|
+
variant = "badge",
|
|
6198
|
+
className,
|
|
6199
|
+
showWhenOnline = false
|
|
6200
|
+
}) {
|
|
6201
|
+
const { isOnline, isBackendReachable } = useOnlineStatus(healthEndpoint);
|
|
6202
|
+
const status = !isOnline ? "offline" : !isBackendReachable ? "degraded" : "online";
|
|
6203
|
+
if (status === "online" && !showWhenOnline) {
|
|
6204
|
+
return null;
|
|
6205
|
+
}
|
|
6206
|
+
const config = statusConfig[status];
|
|
6207
|
+
const Icon2 = config.icon;
|
|
6208
|
+
if (variant === "badge") {
|
|
6209
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6210
|
+
"div",
|
|
6211
|
+
{
|
|
6212
|
+
className: cn(
|
|
6213
|
+
"inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium",
|
|
6214
|
+
config.bgColor,
|
|
6215
|
+
config.color,
|
|
6216
|
+
className
|
|
6217
|
+
),
|
|
6218
|
+
children: [
|
|
6219
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: "h-3 w-3" }),
|
|
6220
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: config.label })
|
|
6221
|
+
]
|
|
6222
|
+
}
|
|
6223
|
+
);
|
|
6224
|
+
}
|
|
6225
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6226
|
+
"div",
|
|
6227
|
+
{
|
|
6228
|
+
className: cn(
|
|
6229
|
+
"flex items-center gap-2 px-3 py-2 rounded-lg",
|
|
6230
|
+
config.bgColor,
|
|
6231
|
+
className
|
|
6232
|
+
),
|
|
6233
|
+
children: [
|
|
6234
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon2, { className: cn("h-4 w-4", config.color) }),
|
|
6235
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
6236
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("text-sm font-medium", config.color), children: config.label }),
|
|
6237
|
+
status === "degraded" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Working with cached data" }),
|
|
6238
|
+
status === "offline" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Check your connection" })
|
|
6239
|
+
] })
|
|
6240
|
+
]
|
|
6241
|
+
}
|
|
6242
|
+
);
|
|
6243
|
+
}
|
|
5761
6244
|
const FormField = ({
|
|
5762
6245
|
label,
|
|
5763
6246
|
description,
|
|
@@ -9099,7 +9582,10 @@ const AppHeader = ({ className }) => {
|
|
|
9099
9582
|
placeholder: "Search models, tests, jobs, and components..."
|
|
9100
9583
|
}
|
|
9101
9584
|
) }),
|
|
9102
|
-
/* @__PURE__ */ jsxRuntime.
|
|
9585
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center space-x-4", children: [
|
|
9586
|
+
/* @__PURE__ */ jsxRuntime.jsx(ConnectionStatus, {}),
|
|
9587
|
+
/* @__PURE__ */ jsxRuntime.jsx(UserMenu, { category: 8 })
|
|
9588
|
+
] })
|
|
9103
9589
|
] }) })
|
|
9104
9590
|
}
|
|
9105
9591
|
);
|
|
@@ -10452,10 +10938,12 @@ function AuthProvider({
|
|
|
10452
10938
|
try {
|
|
10453
10939
|
await authService.refreshToken();
|
|
10454
10940
|
setUser(storedUser);
|
|
10455
|
-
|
|
10456
|
-
|
|
10457
|
-
|
|
10458
|
-
|
|
10941
|
+
if (!(config == null ? void 0 : config.offlineFirst)) {
|
|
10942
|
+
try {
|
|
10943
|
+
const freshUser = await authService.getCurrentUser();
|
|
10944
|
+
if (freshUser) setUser(freshUser);
|
|
10945
|
+
} catch {
|
|
10946
|
+
}
|
|
10459
10947
|
}
|
|
10460
10948
|
} catch {
|
|
10461
10949
|
authService.clearAuth();
|
|
@@ -10465,10 +10953,12 @@ function AuthProvider({
|
|
|
10465
10953
|
}
|
|
10466
10954
|
} else {
|
|
10467
10955
|
setUser(storedUser);
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10956
|
+
if (!(config == null ? void 0 : config.offlineFirst)) {
|
|
10957
|
+
try {
|
|
10958
|
+
const freshUser = await authService.getCurrentUser();
|
|
10959
|
+
if (freshUser) setUser(freshUser);
|
|
10960
|
+
} catch {
|
|
10961
|
+
}
|
|
10472
10962
|
}
|
|
10473
10963
|
}
|
|
10474
10964
|
}
|
|
@@ -17150,6 +17640,7 @@ exports.Checkbox = Checkbox;
|
|
|
17150
17640
|
exports.ColorSwatch = ColorSwatch;
|
|
17151
17641
|
exports.ComponentShowcasePage = ComponentShowcasePage;
|
|
17152
17642
|
exports.ComponentShowcaseTemplate = ComponentShowcaseTemplate;
|
|
17643
|
+
exports.ConnectionStatus = ConnectionStatus;
|
|
17153
17644
|
exports.DASHBOARD_CHART_HEIGHTS = DASHBOARD_CHART_HEIGHTS;
|
|
17154
17645
|
exports.DASHBOARD_CONTAINER_HEIGHTS = DASHBOARD_CONTAINER_HEIGHTS;
|
|
17155
17646
|
exports.DASHBOARD_HEIGHT_CLASSES = DASHBOARD_HEIGHT_CLASSES;
|
|
@@ -17275,14 +17766,17 @@ exports.breakpoints = breakpoints;
|
|
|
17275
17766
|
exports.cn = cn;
|
|
17276
17767
|
exports.createAPIDataTemplate = createAPIDataTemplate;
|
|
17277
17768
|
exports.createApiClient = createApiClient;
|
|
17769
|
+
exports.createMobileCardRenderer = createMobileCardRenderer;
|
|
17278
17770
|
exports.createReactApp = createReactApp;
|
|
17279
17771
|
exports.createSimpleApp = createSimpleApp;
|
|
17280
17772
|
exports.defaultFieldRenderers = defaultFieldRenderers;
|
|
17281
17773
|
exports.detectBulkOperationType = detectBulkOperationType;
|
|
17282
17774
|
exports.detectUIConfig = detectUIConfig;
|
|
17775
|
+
exports.entityToListCardProps = entityToListCardProps;
|
|
17283
17776
|
exports.env = env;
|
|
17284
17777
|
exports.formatNumberWithTooltip = formatNumberWithTooltip;
|
|
17285
17778
|
exports.generateBulkOperationName = generateBulkOperationName;
|
|
17779
|
+
exports.generateCompactColumns = generateCompactColumns;
|
|
17286
17780
|
exports.getAnimationClasses = getAnimationClasses;
|
|
17287
17781
|
exports.getChartHeight = getChartHeight;
|
|
17288
17782
|
exports.getContainerHeightClass = getContainerHeightClass;
|
|
@@ -17324,6 +17818,7 @@ exports.useMaxMediaQuery = useMaxMediaQuery;
|
|
|
17324
17818
|
exports.useMediaQuery = useMediaQuery;
|
|
17325
17819
|
exports.useMockAuth = useMockAuth;
|
|
17326
17820
|
exports.useNavigation = useNavigation;
|
|
17821
|
+
exports.useOnlineStatus = useOnlineStatus;
|
|
17327
17822
|
exports.useOverflowDetection = useOverflowDetection;
|
|
17328
17823
|
exports.usePermissions = usePermissions;
|
|
17329
17824
|
exports.useResponsiveClasses = useResponsiveClasses;
|