@pattern-stack/frontend-patterns 0.2.0-alpha.4 → 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/DataTable/DataTable.d.ts +1 -1
- package/dist/atoms/components/data/DataTable/DataTable.d.ts.map +1 -1
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts +28 -0
- package/dist/atoms/components/data/DataTable/DataTable.types.d.ts.map +1 -1
- 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 +2 -0
- package/dist/atoms/hooks/index.d.ts.map +1 -1
- package/dist/atoms/hooks/useEntityDetail.d.ts +43 -0
- package/dist/atoms/hooks/useEntityDetail.d.ts.map +1 -0
- package/dist/atoms/hooks/useOnlineStatus.d.ts +16 -0
- package/dist/atoms/hooks/useOnlineStatus.d.ts.map +1 -0
- package/dist/atoms/primitives/table.d.ts.map +1 -1
- package/dist/atoms/services/api/client.d.ts +12 -2
- package/dist/atoms/services/api/client.d.ts.map +1 -1
- package/dist/atoms/services/auth-service.d.ts +15 -0
- package/dist/atoms/services/auth-service.d.ts.map +1 -1
- package/dist/atoms/services/index.d.ts +2 -2
- package/dist/atoms/services/index.d.ts.map +1 -1
- package/dist/atoms/types/auth.d.ts +44 -2
- package/dist/atoms/types/auth.d.ts.map +1 -1
- package/dist/atoms/types/ui-config.d.ts +11 -0
- package/dist/atoms/types/ui-config.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/components/ProtectedRoute.d.ts +3 -1
- package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -1
- package/dist/features/auth/hooks/useAuth.d.ts.map +1 -1
- package/dist/features/auth/providers/NoAuthProvider.d.ts +17 -0
- package/dist/features/auth/providers/NoAuthProvider.d.ts.map +1 -0
- package/dist/features/auth/providers/index.d.ts +1 -0
- package/dist/features/auth/providers/index.d.ts.map +1 -1
- package/dist/frontend-patterns.css +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +857 -91
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +854 -88
- package/dist/index.js.map +1 -1
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -1
- package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts +61 -0
- package/dist/molecules/layout/FieldGrid/FieldGrid.d.ts.map +1 -0
- package/dist/molecules/layout/FieldGrid/index.d.ts +2 -0
- package/dist/molecules/layout/FieldGrid/index.d.ts.map +1 -0
- package/dist/molecules/layout/index.d.ts +1 -0
- package/dist/molecules/layout/index.d.ts.map +1 -1
- package/dist/templates/factory.d.ts.map +1 -1
- package/package.json +4 -5
package/dist/index.es.js
CHANGED
|
@@ -9,15 +9,15 @@ 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";
|
|
16
16
|
import { useQuery, useQueryClient, useMutation, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
17
17
|
import { Slot } from "@radix-ui/react-slot";
|
|
18
|
-
import { createPortal } from "react-dom";
|
|
18
|
+
import { createPortal, flushSync } from "react-dom";
|
|
19
19
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
20
|
-
import { useLocation, useNavigate, useSearchParams, Outlet, BrowserRouter, Routes, Route } from "react-router-dom";
|
|
20
|
+
import { useLocation, useNavigate, useSearchParams, Outlet, Navigate, BrowserRouter, Routes, Route } from "react-router-dom";
|
|
21
21
|
import { createRoot } from "react-dom/client";
|
|
22
22
|
const APP_NAME = "Design System Showcase";
|
|
23
23
|
const API_ENDPOINTS = {
|
|
@@ -34,8 +34,8 @@ const UI_CONFIG = {
|
|
|
34
34
|
TOAST_DURATION: 5e3
|
|
35
35
|
};
|
|
36
36
|
const env = {
|
|
37
|
-
API_BASE_URL: "
|
|
38
|
-
OPENAPI_URL: "
|
|
37
|
+
API_BASE_URL: "",
|
|
38
|
+
OPENAPI_URL: "/openapi.json",
|
|
39
39
|
NODE_ENV: "development",
|
|
40
40
|
IS_DEVELOPMENT: false,
|
|
41
41
|
IS_PRODUCTION: false,
|
|
@@ -2009,16 +2009,374 @@ function renderField(value, fieldName, fieldType, breakpoint, item, customRender
|
|
|
2009
2009
|
function cn(...inputs) {
|
|
2010
2010
|
return twMerge(clsx(inputs));
|
|
2011
2011
|
}
|
|
2012
|
-
const
|
|
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
|
+
}
|
|
2013
2368
|
let globalAuthService = null;
|
|
2014
2369
|
function setGlobalAuthService(authService) {
|
|
2015
2370
|
globalAuthService = authService;
|
|
2016
2371
|
}
|
|
2372
|
+
function getGlobalAuthService() {
|
|
2373
|
+
return globalAuthService;
|
|
2374
|
+
}
|
|
2017
2375
|
class ApiClient {
|
|
2018
2376
|
constructor(config = {}) {
|
|
2019
2377
|
__publicField(this, "instance");
|
|
2020
2378
|
this.instance = axios.create({
|
|
2021
|
-
baseURL: config.baseURL ||
|
|
2379
|
+
baseURL: config.baseURL || "",
|
|
2022
2380
|
timeout: config.timeout || 1e4,
|
|
2023
2381
|
headers: {
|
|
2024
2382
|
"Content-Type": "application/json",
|
|
@@ -2058,11 +2416,15 @@ class ApiClient {
|
|
|
2058
2416
|
this.instance.interceptors.response.use(
|
|
2059
2417
|
(response) => response,
|
|
2060
2418
|
async (error) => {
|
|
2061
|
-
var _a;
|
|
2062
|
-
|
|
2419
|
+
var _a, _b, _c, _d;
|
|
2420
|
+
const status = (_a = error.response) == null ? void 0 : _a.status;
|
|
2421
|
+
if (!error.response) {
|
|
2422
|
+
return Promise.reject(error);
|
|
2423
|
+
}
|
|
2424
|
+
if (status === 401 || status === 403) {
|
|
2063
2425
|
if (globalAuthService) {
|
|
2064
2426
|
const tokenData = globalAuthService.getTokenData();
|
|
2065
|
-
if ((tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
|
|
2427
|
+
if (status === 401 && (tokenData == null ? void 0 : tokenData.refreshToken) && !error.config._retry) {
|
|
2066
2428
|
error.config._retry = true;
|
|
2067
2429
|
try {
|
|
2068
2430
|
await globalAuthService.refreshToken();
|
|
@@ -2070,17 +2432,18 @@ class ApiClient {
|
|
|
2070
2432
|
error.config.headers.Authorization = `Bearer ${newTokenData == null ? void 0 : newTokenData.token}`;
|
|
2071
2433
|
return this.instance.request(error.config);
|
|
2072
2434
|
} catch {
|
|
2073
|
-
globalAuthService.clearAuth();
|
|
2074
|
-
window.location.reload();
|
|
2075
2435
|
}
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2436
|
+
}
|
|
2437
|
+
globalAuthService.clearAuth();
|
|
2438
|
+
const errorInfo = { status, message: (_c = (_b = error.response) == null ? void 0 : _b.data) == null ? void 0 : _c.detail };
|
|
2439
|
+
const handled = (_d = globalAuthService.onAuthError) == null ? void 0 : _d.call(globalAuthService, errorInfo);
|
|
2440
|
+
if (!handled) {
|
|
2441
|
+
window.location.href = "/login";
|
|
2079
2442
|
}
|
|
2080
2443
|
} else {
|
|
2081
2444
|
localStorage.removeItem("auth_token");
|
|
2082
2445
|
localStorage.removeItem("auth_user");
|
|
2083
|
-
window.location.
|
|
2446
|
+
window.location.href = "/login";
|
|
2084
2447
|
}
|
|
2085
2448
|
}
|
|
2086
2449
|
return Promise.reject(error);
|
|
@@ -2120,6 +2483,9 @@ class ApiClient {
|
|
|
2120
2483
|
return response.data;
|
|
2121
2484
|
}
|
|
2122
2485
|
}
|
|
2486
|
+
function createApiClient(config = {}) {
|
|
2487
|
+
return new ApiClient(config);
|
|
2488
|
+
}
|
|
2123
2489
|
const apiClient = new ApiClient();
|
|
2124
2490
|
function useToast() {
|
|
2125
2491
|
const [toasts, setToasts] = useState([]);
|
|
@@ -2390,7 +2756,7 @@ function useEntityData(entity, options = {}) {
|
|
|
2390
2756
|
const dataQuery = useQuery({
|
|
2391
2757
|
queryKey: [entity, "list", { limit, offset, filters }],
|
|
2392
2758
|
queryFn: () => apiClient.get(
|
|
2393
|
-
`/api/v1/${entity}
|
|
2759
|
+
`/api/v1/${entity}?${dataParams.toString()}`
|
|
2394
2760
|
),
|
|
2395
2761
|
enabled
|
|
2396
2762
|
});
|
|
@@ -2418,6 +2784,95 @@ function useEntityData(entity, options = {}) {
|
|
|
2418
2784
|
refetch
|
|
2419
2785
|
};
|
|
2420
2786
|
}
|
|
2787
|
+
function useEntityDetail(entity, id, options = {}) {
|
|
2788
|
+
var _a;
|
|
2789
|
+
const { view = "detail", sourceId, enabled = true } = options;
|
|
2790
|
+
const metadataParams = new URLSearchParams();
|
|
2791
|
+
metadataParams.set("view", view);
|
|
2792
|
+
if (sourceId) metadataParams.set("source_id", sourceId);
|
|
2793
|
+
const dataQuery = useQuery({
|
|
2794
|
+
queryKey: [entity, "detail", id],
|
|
2795
|
+
queryFn: () => apiClient.get(`/api/v1/${entity}/${id}`),
|
|
2796
|
+
enabled: enabled && !!id
|
|
2797
|
+
});
|
|
2798
|
+
const metadataQuery = useQuery({
|
|
2799
|
+
queryKey: [entity, "metadata", { view, sourceId }],
|
|
2800
|
+
queryFn: () => apiClient.get(
|
|
2801
|
+
`/api/v1/${entity}/fields/metadata?${metadataParams.toString()}`
|
|
2802
|
+
),
|
|
2803
|
+
enabled,
|
|
2804
|
+
staleTime: 5 * 60 * 1e3
|
|
2805
|
+
// 5 minutes
|
|
2806
|
+
});
|
|
2807
|
+
const refetch = () => {
|
|
2808
|
+
dataQuery.refetch();
|
|
2809
|
+
metadataQuery.refetch();
|
|
2810
|
+
};
|
|
2811
|
+
return {
|
|
2812
|
+
data: dataQuery.data ?? null,
|
|
2813
|
+
fields: ((_a = metadataQuery.data) == null ? void 0 : _a.columns) ?? [],
|
|
2814
|
+
isLoading: dataQuery.isLoading || metadataQuery.isLoading,
|
|
2815
|
+
isLoadingData: dataQuery.isLoading,
|
|
2816
|
+
isLoadingMetadata: metadataQuery.isLoading,
|
|
2817
|
+
error: dataQuery.error ?? metadataQuery.error ?? null,
|
|
2818
|
+
refetch
|
|
2819
|
+
};
|
|
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
|
+
}
|
|
2421
2876
|
const buttonVariants = cva(
|
|
2422
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]",
|
|
2423
2878
|
{
|
|
@@ -4108,7 +4563,9 @@ const TableHead$1 = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
4108
4563
|
{
|
|
4109
4564
|
ref,
|
|
4110
4565
|
className: cn(
|
|
4111
|
-
"h-12 px-4 xs:px-2 2xs:px-1 text-left align-middle font-semibold text-gray-700 text-xs uppercase tracking-wider
|
|
4566
|
+
"h-12 px-4 xs:px-2 2xs:px-1 text-left align-middle font-semibold text-gray-700 text-xs uppercase tracking-wider",
|
|
4567
|
+
// Compact padding for checkbox columns
|
|
4568
|
+
"[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
|
|
4112
4569
|
className
|
|
4113
4570
|
),
|
|
4114
4571
|
...props
|
|
@@ -4120,7 +4577,9 @@ const TableCell$1 = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
4120
4577
|
{
|
|
4121
4578
|
ref,
|
|
4122
4579
|
className: cn(
|
|
4123
|
-
"p-4 xs:p-2 2xs:p-1 align-middle
|
|
4580
|
+
"p-4 xs:p-2 2xs:p-1 align-middle",
|
|
4581
|
+
// Compact padding for checkbox columns
|
|
4582
|
+
"[&:has(input[type=checkbox])]:px-2 [&:has(input[type=checkbox])]:w-8",
|
|
4124
4583
|
className
|
|
4125
4584
|
),
|
|
4126
4585
|
...props
|
|
@@ -4170,7 +4629,12 @@ function DataTable({
|
|
|
4170
4629
|
ui,
|
|
4171
4630
|
error = null,
|
|
4172
4631
|
errorTitle,
|
|
4173
|
-
errorRetry
|
|
4632
|
+
errorRetry,
|
|
4633
|
+
selectable = false,
|
|
4634
|
+
selectedIds,
|
|
4635
|
+
onSelectionChange,
|
|
4636
|
+
getRowId,
|
|
4637
|
+
selectAllMode = "page"
|
|
4174
4638
|
}) {
|
|
4175
4639
|
const [searchTerm, setSearchTerm] = useState("");
|
|
4176
4640
|
const [sortColumn, setSortColumn] = useState(null);
|
|
@@ -4233,6 +4697,85 @@ function DataTable({
|
|
|
4233
4697
|
return true;
|
|
4234
4698
|
});
|
|
4235
4699
|
}, [normalizedColumns, currentBreakpoint, responsive]);
|
|
4700
|
+
const defaultGetRowId = useCallback((row) => {
|
|
4701
|
+
if ("id" in row && typeof row.id === "string") return row.id;
|
|
4702
|
+
if ("id" in row && typeof row.id === "number") return String(row.id);
|
|
4703
|
+
throw new Error("DataTable: selectable requires getRowId prop or row.id field");
|
|
4704
|
+
}, []);
|
|
4705
|
+
const effectiveGetRowId = getRowId ?? defaultGetRowId;
|
|
4706
|
+
const selectableData = selectAllMode === "all" ? sortedData : paginatedData;
|
|
4707
|
+
const selectableIds = useMemo(
|
|
4708
|
+
() => {
|
|
4709
|
+
if (!selectable) return /* @__PURE__ */ new Set();
|
|
4710
|
+
return new Set(selectableData.map((row) => effectiveGetRowId(row)));
|
|
4711
|
+
},
|
|
4712
|
+
[selectable, selectableData, effectiveGetRowId]
|
|
4713
|
+
);
|
|
4714
|
+
const selectedInScope = useMemo(
|
|
4715
|
+
() => {
|
|
4716
|
+
if (!selectable || !selectedIds) return /* @__PURE__ */ new Set();
|
|
4717
|
+
return new Set([...selectedIds].filter((id) => selectableIds.has(id)));
|
|
4718
|
+
},
|
|
4719
|
+
[selectable, selectedIds, selectableIds]
|
|
4720
|
+
);
|
|
4721
|
+
const isAllSelected = selectableIds.size > 0 && selectedInScope.size === selectableIds.size;
|
|
4722
|
+
const isIndeterminate = selectedInScope.size > 0 && selectedInScope.size < selectableIds.size;
|
|
4723
|
+
const handleSelectAll = useCallback(() => {
|
|
4724
|
+
if (!onSelectionChange) return;
|
|
4725
|
+
if (isAllSelected) {
|
|
4726
|
+
const newSelection = new Set([...selectedIds ?? []].filter((id) => !selectableIds.has(id)));
|
|
4727
|
+
onSelectionChange(newSelection);
|
|
4728
|
+
} else {
|
|
4729
|
+
const newSelection = /* @__PURE__ */ new Set([...selectedIds ?? [], ...selectableIds]);
|
|
4730
|
+
onSelectionChange(newSelection);
|
|
4731
|
+
}
|
|
4732
|
+
}, [isAllSelected, selectedIds, selectableIds, onSelectionChange]);
|
|
4733
|
+
const handleSelectRow = useCallback((rowId) => {
|
|
4734
|
+
if (!onSelectionChange) return;
|
|
4735
|
+
const newSelection = new Set(selectedIds);
|
|
4736
|
+
if (newSelection.has(rowId)) {
|
|
4737
|
+
newSelection.delete(rowId);
|
|
4738
|
+
} else {
|
|
4739
|
+
newSelection.add(rowId);
|
|
4740
|
+
}
|
|
4741
|
+
onSelectionChange(newSelection);
|
|
4742
|
+
}, [selectedIds, onSelectionChange]);
|
|
4743
|
+
const selectionColumn = useMemo(() => {
|
|
4744
|
+
return {
|
|
4745
|
+
key: "__selection__",
|
|
4746
|
+
header: /* @__PURE__ */ jsx(
|
|
4747
|
+
Checkbox$1,
|
|
4748
|
+
{
|
|
4749
|
+
checked: isAllSelected,
|
|
4750
|
+
indeterminate: isIndeterminate,
|
|
4751
|
+
onCheckedChange: handleSelectAll,
|
|
4752
|
+
"aria-label": isAllSelected ? "Deselect all rows" : "Select all rows",
|
|
4753
|
+
size: "sm"
|
|
4754
|
+
}
|
|
4755
|
+
),
|
|
4756
|
+
cell: (row) => {
|
|
4757
|
+
const rowId = effectiveGetRowId(row);
|
|
4758
|
+
const isSelected = (selectedIds == null ? void 0 : selectedIds.has(rowId)) ?? false;
|
|
4759
|
+
return /* @__PURE__ */ jsx(
|
|
4760
|
+
Checkbox$1,
|
|
4761
|
+
{
|
|
4762
|
+
checked: isSelected,
|
|
4763
|
+
onCheckedChange: () => handleSelectRow(rowId),
|
|
4764
|
+
"aria-label": isSelected ? `Deselect row ${rowId}` : `Select row ${rowId}`,
|
|
4765
|
+
size: "sm",
|
|
4766
|
+
onClick: (e) => e.stopPropagation()
|
|
4767
|
+
}
|
|
4768
|
+
);
|
|
4769
|
+
},
|
|
4770
|
+
width: "32px",
|
|
4771
|
+
sortable: false,
|
|
4772
|
+
filterable: false
|
|
4773
|
+
};
|
|
4774
|
+
}, [isAllSelected, isIndeterminate, selectedIds, effectiveGetRowId, handleSelectAll, handleSelectRow]);
|
|
4775
|
+
const effectiveColumns = useMemo(() => {
|
|
4776
|
+
if (!selectable) return visibleColumns;
|
|
4777
|
+
return [selectionColumn, ...visibleColumns];
|
|
4778
|
+
}, [selectable, selectionColumn, visibleColumns]);
|
|
4236
4779
|
const handleSort = (columnKey) => {
|
|
4237
4780
|
if (sortColumn === columnKey) {
|
|
4238
4781
|
if (sortDirection === "asc") {
|
|
@@ -4295,8 +4838,8 @@ function DataTable({
|
|
|
4295
4838
|
children: [
|
|
4296
4839
|
showSearch && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx("div", { className: "relative flex-1 max-w-sm", children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-10 w-full" }) }) }),
|
|
4297
4840
|
/* @__PURE__ */ jsx("div", { className: "rounded border overflow-hidden", children: /* @__PURE__ */ jsxs(Table$1, { children: [
|
|
4298
|
-
/* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children:
|
|
4299
|
-
/* @__PURE__ */ jsx(TableBody$1, { children: Array.from({ length: loadingItemCount }, (_, index) => /* @__PURE__ */ jsx(TableRow$1, { children:
|
|
4841
|
+
/* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableHead$1, { style: { width: column.width }, children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-20" }) }) }, column.key)) }) }),
|
|
4842
|
+
/* @__PURE__ */ jsx(TableBody$1, { children: Array.from({ length: loadingItemCount }, (_, index) => /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: /* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-full max-w-32" }) }, column.key)) }, index)) })
|
|
4300
4843
|
] }) }),
|
|
4301
4844
|
showPagination && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4302
4845
|
/* @__PURE__ */ jsx(Skeleton$1, { className: "h-4 w-48" }),
|
|
@@ -4358,7 +4901,7 @@ function DataTable({
|
|
|
4358
4901
|
data: paginatedData,
|
|
4359
4902
|
onItemClick: onRowClick
|
|
4360
4903
|
}) }) : /* @__PURE__ */ jsx("div", { className: "rounded border overflow-hidden", children: /* @__PURE__ */ jsxs(Table$1, { children: [
|
|
4361
|
-
/* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children:
|
|
4904
|
+
/* @__PURE__ */ jsx(TableHeader$1, { children: /* @__PURE__ */ jsx(TableRow$1, { children: effectiveColumns.map((column) => {
|
|
4362
4905
|
const width = column.width;
|
|
4363
4906
|
return /* @__PURE__ */ jsx(
|
|
4364
4907
|
TableHead$1,
|
|
@@ -4381,7 +4924,7 @@ function DataTable({
|
|
|
4381
4924
|
/* @__PURE__ */ jsx(TableBody$1, { children: paginatedData.length === 0 ? /* @__PURE__ */ jsx(TableRow$1, { children: /* @__PURE__ */ jsx(
|
|
4382
4925
|
TableCell$1,
|
|
4383
4926
|
{
|
|
4384
|
-
colSpan:
|
|
4927
|
+
colSpan: effectiveColumns.length,
|
|
4385
4928
|
className: "text-center py-8 text-muted-foreground",
|
|
4386
4929
|
children: emptyMessage
|
|
4387
4930
|
}
|
|
@@ -4393,7 +4936,7 @@ function DataTable({
|
|
|
4393
4936
|
(hover || onRowClick) && "hover:bg-muted/50"
|
|
4394
4937
|
),
|
|
4395
4938
|
onClick: () => onRowClick == null ? void 0 : onRowClick(item),
|
|
4396
|
-
children:
|
|
4939
|
+
children: effectiveColumns.map((column) => /* @__PURE__ */ jsx(TableCell$1, { children: renderCell(column, item) }, column.key))
|
|
4397
4940
|
},
|
|
4398
4941
|
index
|
|
4399
4942
|
)) })
|
|
@@ -5586,6 +6129,75 @@ const ActivityFeed = ({
|
|
|
5586
6129
|
] }) })
|
|
5587
6130
|
] });
|
|
5588
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
|
+
}
|
|
5589
6201
|
const FormField = ({
|
|
5590
6202
|
label,
|
|
5591
6203
|
description,
|
|
@@ -8927,7 +9539,10 @@ const AppHeader = ({ className }) => {
|
|
|
8927
9539
|
placeholder: "Search models, tests, jobs, and components..."
|
|
8928
9540
|
}
|
|
8929
9541
|
) }),
|
|
8930
|
-
/* @__PURE__ */
|
|
9542
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-4", children: [
|
|
9543
|
+
/* @__PURE__ */ jsx(ConnectionStatus, {}),
|
|
9544
|
+
/* @__PURE__ */ jsx(UserMenu, { category: 8 })
|
|
9545
|
+
] })
|
|
8931
9546
|
] }) })
|
|
8932
9547
|
}
|
|
8933
9548
|
);
|
|
@@ -9218,6 +9833,55 @@ const ListToolbar = ({
|
|
|
9218
9833
|
}
|
|
9219
9834
|
);
|
|
9220
9835
|
};
|
|
9836
|
+
const FieldGrid = ({
|
|
9837
|
+
data,
|
|
9838
|
+
fields,
|
|
9839
|
+
sections,
|
|
9840
|
+
columns = 2,
|
|
9841
|
+
showLabels = true,
|
|
9842
|
+
compact = false,
|
|
9843
|
+
showEmpty = true,
|
|
9844
|
+
className
|
|
9845
|
+
}) => {
|
|
9846
|
+
const gridCols = {
|
|
9847
|
+
1: "grid-cols-1",
|
|
9848
|
+
2: "grid-cols-1 md:grid-cols-2",
|
|
9849
|
+
3: "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
|
9850
|
+
};
|
|
9851
|
+
const renderValue = (field) => {
|
|
9852
|
+
const value = data[field.field];
|
|
9853
|
+
if (value === null || value === void 0 || value === "") {
|
|
9854
|
+
if (!showEmpty) return null;
|
|
9855
|
+
return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
9856
|
+
}
|
|
9857
|
+
const format = field.format;
|
|
9858
|
+
return renderField(value, field.field, field.type, "lg", data, void 0, format);
|
|
9859
|
+
};
|
|
9860
|
+
const renderField_ = (field) => {
|
|
9861
|
+
const value = data[field.field];
|
|
9862
|
+
if (!showEmpty && (value === null || value === void 0 || value === "")) {
|
|
9863
|
+
return null;
|
|
9864
|
+
}
|
|
9865
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1", compact ? "py-1" : "py-2"), children: [
|
|
9866
|
+
showLabels && /* @__PURE__ */ jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: field.label }),
|
|
9867
|
+
/* @__PURE__ */ jsx("dd", { className: "text-sm text-foreground", children: renderValue(field) })
|
|
9868
|
+
] }, field.field);
|
|
9869
|
+
};
|
|
9870
|
+
const renderFields = (fieldList) => /* @__PURE__ */ jsx("dl", { className: cn("grid gap-x-6", gridCols[columns]), children: fieldList.map(renderField_) });
|
|
9871
|
+
if (sections && sections.length > 0) {
|
|
9872
|
+
return /* @__PURE__ */ jsx("div", { className: cn("space-y-6", className), children: sections.map((section) => /* @__PURE__ */ jsxs(Card, { className: cn(compact ? "p-4" : "p-6"), children: [
|
|
9873
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4", children: [
|
|
9874
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-foreground", children: section.title }),
|
|
9875
|
+
section.description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-1", children: section.description })
|
|
9876
|
+
] }),
|
|
9877
|
+
renderFields(section.fields)
|
|
9878
|
+
] }, section.title)) });
|
|
9879
|
+
}
|
|
9880
|
+
if (fields && fields.length > 0) {
|
|
9881
|
+
return /* @__PURE__ */ jsx(Card, { className: cn(compact ? "p-4" : "p-6", className), children: renderFields(fields) });
|
|
9882
|
+
}
|
|
9883
|
+
return null;
|
|
9884
|
+
};
|
|
9221
9885
|
const Pagination = ({
|
|
9222
9886
|
currentPage,
|
|
9223
9887
|
totalPages,
|
|
@@ -9897,9 +10561,11 @@ function ProtectedRoute({
|
|
|
9897
10561
|
children,
|
|
9898
10562
|
requirePermission,
|
|
9899
10563
|
requireRole,
|
|
9900
|
-
fallback
|
|
10564
|
+
fallback,
|
|
10565
|
+
loginPath = "/login"
|
|
9901
10566
|
}) {
|
|
9902
10567
|
const { isAuthenticated, isLoading, hasPermission, hasRole } = useAuthContext();
|
|
10568
|
+
const location = useLocation();
|
|
9903
10569
|
if (isLoading) {
|
|
9904
10570
|
return /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
9905
10571
|
/* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto" }),
|
|
@@ -9907,7 +10573,7 @@ function ProtectedRoute({
|
|
|
9907
10573
|
] }) });
|
|
9908
10574
|
}
|
|
9909
10575
|
if (!isAuthenticated) {
|
|
9910
|
-
return /* @__PURE__ */ jsx(
|
|
10576
|
+
return /* @__PURE__ */ jsx(Navigate, { to: loginPath, state: { from: location }, replace: true });
|
|
9911
10577
|
}
|
|
9912
10578
|
if (requirePermission && !hasPermission(requirePermission)) {
|
|
9913
10579
|
return fallback || /* @__PURE__ */ jsx("div", { className: "min-h-screen flex items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
|
|
@@ -9934,17 +10600,39 @@ function LogoutButton() {
|
|
|
9934
10600
|
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: logout, children: "Logout" })
|
|
9935
10601
|
] });
|
|
9936
10602
|
}
|
|
10603
|
+
const AUTH_TOKEN_KEY = "auth_token";
|
|
10604
|
+
const DEFAULT_ENDPOINTS = {
|
|
10605
|
+
login: "/auth/login",
|
|
10606
|
+
register: "/auth/register",
|
|
10607
|
+
refresh: "/auth/refresh",
|
|
10608
|
+
me: "/auth/me",
|
|
10609
|
+
logout: "/auth/logout"
|
|
10610
|
+
};
|
|
9937
10611
|
class AuthService {
|
|
9938
10612
|
constructor(config) {
|
|
9939
10613
|
__publicField(this, "config");
|
|
10614
|
+
__publicField(this, "apiClient");
|
|
9940
10615
|
__publicField(this, "refreshPromise", null);
|
|
10616
|
+
__publicField(this, "endpoints");
|
|
9941
10617
|
this.config = {
|
|
10618
|
+
mode: "pattern-stack",
|
|
9942
10619
|
tokenStorage: "localStorage",
|
|
9943
10620
|
tokenRefreshBuffer: 5,
|
|
9944
10621
|
// 5 minutes before expiry
|
|
9945
10622
|
autoRefresh: true,
|
|
9946
10623
|
...config
|
|
9947
10624
|
};
|
|
10625
|
+
this.endpoints = {
|
|
10626
|
+
...DEFAULT_ENDPOINTS,
|
|
10627
|
+
...config.endpoints
|
|
10628
|
+
};
|
|
10629
|
+
this.apiClient = createApiClient({ baseURL: this.config.apiUrl || "" });
|
|
10630
|
+
}
|
|
10631
|
+
/**
|
|
10632
|
+
* Get the auth mode
|
|
10633
|
+
*/
|
|
10634
|
+
get mode() {
|
|
10635
|
+
return this.config.mode || "pattern-stack";
|
|
9948
10636
|
}
|
|
9949
10637
|
getStorageKey(key) {
|
|
9950
10638
|
return `auth_${key}`;
|
|
@@ -10028,21 +10716,33 @@ class AuthService {
|
|
|
10028
10716
|
this.removeItem("user");
|
|
10029
10717
|
}
|
|
10030
10718
|
async login(credentials) {
|
|
10031
|
-
const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.login}`, credentials);
|
|
10032
10719
|
let token;
|
|
10033
10720
|
let refreshToken;
|
|
10034
10721
|
let expiresIn;
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10722
|
+
let user;
|
|
10723
|
+
if (this.mode === "pattern-stack") {
|
|
10724
|
+
const response = await this.apiClient.post(
|
|
10725
|
+
this.endpoints.login,
|
|
10726
|
+
credentials
|
|
10727
|
+
);
|
|
10728
|
+
token = response.access_token;
|
|
10729
|
+
refreshToken = response.refresh_token;
|
|
10730
|
+
user = response.user;
|
|
10731
|
+
} else if (this.mode === "custom" && this.config.responseMapper) {
|
|
10732
|
+
const rawResponse = await this.apiClient.post(
|
|
10733
|
+
this.endpoints.login,
|
|
10734
|
+
credentials
|
|
10735
|
+
);
|
|
10736
|
+
const mapped = this.config.responseMapper(rawResponse);
|
|
10737
|
+
token = mapped.token;
|
|
10738
|
+
refreshToken = mapped.refreshToken;
|
|
10739
|
+
expiresIn = mapped.expiresIn;
|
|
10740
|
+
user = mapped.user;
|
|
10039
10741
|
} else {
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
expiresIn = oldResponse.expiresIn;
|
|
10742
|
+
throw new Error(
|
|
10743
|
+
`Invalid auth mode: ${this.mode}. Use 'pattern-stack' or 'custom' with responseMapper.`
|
|
10744
|
+
);
|
|
10044
10745
|
}
|
|
10045
|
-
const user = response.user;
|
|
10046
10746
|
const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
|
|
10047
10747
|
this.setTokenData({ token, refreshToken, expiresAt });
|
|
10048
10748
|
this.setStoredUser(user);
|
|
@@ -10068,21 +10768,27 @@ class AuthService {
|
|
|
10068
10768
|
}
|
|
10069
10769
|
async performTokenRefresh(currentRefreshToken) {
|
|
10070
10770
|
try {
|
|
10071
|
-
const response = await apiClient.post(`${this.config.apiUrl}${this.config.endpoints.refresh}`, {
|
|
10072
|
-
refresh_token: currentRefreshToken
|
|
10073
|
-
});
|
|
10074
10771
|
let token;
|
|
10075
10772
|
let newRefreshToken;
|
|
10076
10773
|
let expiresIn;
|
|
10077
|
-
if ("
|
|
10078
|
-
const
|
|
10079
|
-
|
|
10774
|
+
if (this.mode === "pattern-stack") {
|
|
10775
|
+
const response = await this.apiClient.post(
|
|
10776
|
+
this.endpoints.refresh,
|
|
10777
|
+
{ refresh_token: currentRefreshToken }
|
|
10778
|
+
);
|
|
10779
|
+
token = response.access_token;
|
|
10080
10780
|
newRefreshToken = currentRefreshToken;
|
|
10781
|
+
} else if (this.mode === "custom" && this.config.responseMapper) {
|
|
10782
|
+
const rawResponse = await this.apiClient.post(
|
|
10783
|
+
this.endpoints.refresh,
|
|
10784
|
+
{ refresh_token: currentRefreshToken }
|
|
10785
|
+
);
|
|
10786
|
+
const mapped = this.config.responseMapper(rawResponse);
|
|
10787
|
+
token = mapped.token;
|
|
10788
|
+
newRefreshToken = mapped.refreshToken || currentRefreshToken;
|
|
10789
|
+
expiresIn = mapped.expiresIn;
|
|
10081
10790
|
} else {
|
|
10082
|
-
|
|
10083
|
-
token = oldResponse.token;
|
|
10084
|
-
newRefreshToken = oldResponse.refreshToken;
|
|
10085
|
-
expiresIn = oldResponse.expiresIn;
|
|
10791
|
+
throw new Error(`Invalid auth mode for token refresh: ${this.mode}`);
|
|
10086
10792
|
}
|
|
10087
10793
|
const expiresAt = expiresIn ? Date.now() + expiresIn * 1e3 : void 0;
|
|
10088
10794
|
this.setTokenData({
|
|
@@ -10124,36 +10830,39 @@ class AuthService {
|
|
|
10124
10830
|
const tokenData = this.getTokenData();
|
|
10125
10831
|
if (!(tokenData == null ? void 0 : tokenData.token)) return null;
|
|
10126
10832
|
try {
|
|
10127
|
-
return await apiClient.get(
|
|
10128
|
-
`${this.config.apiUrl}${this.config.endpoints.me}`
|
|
10129
|
-
);
|
|
10833
|
+
return await this.apiClient.get(this.endpoints.me);
|
|
10130
10834
|
} catch {
|
|
10131
10835
|
return null;
|
|
10132
10836
|
}
|
|
10133
10837
|
}
|
|
10134
10838
|
async logout() {
|
|
10135
10839
|
const tokenData = this.getTokenData();
|
|
10136
|
-
if (this.
|
|
10840
|
+
if (this.endpoints.logout && (tokenData == null ? void 0 : tokenData.token)) {
|
|
10137
10841
|
try {
|
|
10138
|
-
await apiClient.post(
|
|
10139
|
-
`${this.config.apiUrl}${this.config.endpoints.logout}`
|
|
10140
|
-
);
|
|
10842
|
+
await this.apiClient.post(this.endpoints.logout);
|
|
10141
10843
|
} catch {
|
|
10142
10844
|
}
|
|
10143
10845
|
}
|
|
10144
10846
|
this.clearAuth();
|
|
10145
10847
|
}
|
|
10848
|
+
/**
|
|
10849
|
+
* Get the configured onAuthError handler
|
|
10850
|
+
*/
|
|
10851
|
+
get onAuthError() {
|
|
10852
|
+
return this.config.onAuthError;
|
|
10853
|
+
}
|
|
10146
10854
|
}
|
|
10147
10855
|
function AuthProvider({
|
|
10148
10856
|
children,
|
|
10149
10857
|
config
|
|
10150
10858
|
}) {
|
|
10859
|
+
var _a;
|
|
10151
10860
|
const [user, setUser] = useState(null);
|
|
10152
10861
|
const [isLoading, setIsLoading] = useState(true);
|
|
10153
10862
|
const [permissions, setPermissions] = useState([]);
|
|
10154
10863
|
const authService = useMemo(() => {
|
|
10155
10864
|
const defaultConfig = {
|
|
10156
|
-
apiUrl:
|
|
10865
|
+
apiUrl: (config == null ? void 0 : config.apiUrl) ?? "",
|
|
10157
10866
|
endpoints: {
|
|
10158
10867
|
login: "/auth/login",
|
|
10159
10868
|
register: "/auth/register",
|
|
@@ -10168,7 +10877,7 @@ function AuthProvider({
|
|
|
10168
10877
|
enabled: false
|
|
10169
10878
|
}
|
|
10170
10879
|
};
|
|
10171
|
-
const mergedConfig = config ? { ...defaultConfig, ...config } : defaultConfig;
|
|
10880
|
+
const mergedConfig = config ? { ...defaultConfig, ...config, apiUrl: config.apiUrl ?? defaultConfig.apiUrl } : defaultConfig;
|
|
10172
10881
|
const service = new AuthService(mergedConfig);
|
|
10173
10882
|
setGlobalAuthService(service);
|
|
10174
10883
|
return service;
|
|
@@ -10186,10 +10895,12 @@ function AuthProvider({
|
|
|
10186
10895
|
try {
|
|
10187
10896
|
await authService.refreshToken();
|
|
10188
10897
|
setUser(storedUser);
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10898
|
+
if (!(config == null ? void 0 : config.offlineFirst)) {
|
|
10899
|
+
try {
|
|
10900
|
+
const freshUser = await authService.getCurrentUser();
|
|
10901
|
+
if (freshUser) setUser(freshUser);
|
|
10902
|
+
} catch {
|
|
10903
|
+
}
|
|
10193
10904
|
}
|
|
10194
10905
|
} catch {
|
|
10195
10906
|
authService.clearAuth();
|
|
@@ -10199,10 +10910,12 @@ function AuthProvider({
|
|
|
10199
10910
|
}
|
|
10200
10911
|
} else {
|
|
10201
10912
|
setUser(storedUser);
|
|
10202
|
-
|
|
10203
|
-
|
|
10204
|
-
|
|
10205
|
-
|
|
10913
|
+
if (!(config == null ? void 0 : config.offlineFirst)) {
|
|
10914
|
+
try {
|
|
10915
|
+
const freshUser = await authService.getCurrentUser();
|
|
10916
|
+
if (freshUser) setUser(freshUser);
|
|
10917
|
+
} catch {
|
|
10918
|
+
}
|
|
10206
10919
|
}
|
|
10207
10920
|
}
|
|
10208
10921
|
}
|
|
@@ -10215,48 +10928,56 @@ function AuthProvider({
|
|
|
10215
10928
|
};
|
|
10216
10929
|
initAuth();
|
|
10217
10930
|
}, [authService]);
|
|
10218
|
-
const login = async (credentials) => {
|
|
10219
|
-
var _a;
|
|
10931
|
+
const login = useCallback(async (credentials) => {
|
|
10220
10932
|
setIsLoading(true);
|
|
10221
10933
|
try {
|
|
10222
|
-
const
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
|
|
10227
|
-
|
|
10228
|
-
|
|
10229
|
-
|
|
10230
|
-
|
|
10934
|
+
const loggedInUser = await authService.login(credentials);
|
|
10935
|
+
flushSync(() => {
|
|
10936
|
+
var _a2;
|
|
10937
|
+
setUser(loggedInUser);
|
|
10938
|
+
if (((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled) && "permissions" in loggedInUser) {
|
|
10939
|
+
const userPermissions = loggedInUser.permissions;
|
|
10940
|
+
setPermissions(
|
|
10941
|
+
Array.isArray(userPermissions) ? userPermissions : []
|
|
10942
|
+
);
|
|
10943
|
+
}
|
|
10944
|
+
setIsLoading(false);
|
|
10945
|
+
});
|
|
10946
|
+
} catch (error) {
|
|
10231
10947
|
setIsLoading(false);
|
|
10948
|
+
throw error;
|
|
10232
10949
|
}
|
|
10233
|
-
};
|
|
10234
|
-
const logout = async () => {
|
|
10950
|
+
}, [authService, (_a = config == null ? void 0 : config.permissions) == null ? void 0 : _a.enabled]);
|
|
10951
|
+
const logout = useCallback(async () => {
|
|
10235
10952
|
setIsLoading(true);
|
|
10236
10953
|
try {
|
|
10237
10954
|
await authService.logout();
|
|
10238
|
-
|
|
10239
|
-
|
|
10955
|
+
flushSync(() => {
|
|
10956
|
+
setUser(null);
|
|
10957
|
+
setPermissions([]);
|
|
10958
|
+
setIsLoading(false);
|
|
10959
|
+
});
|
|
10240
10960
|
} catch (error) {
|
|
10241
10961
|
console.error("Logout error:", error);
|
|
10242
10962
|
authService.clearAuth();
|
|
10243
|
-
|
|
10244
|
-
|
|
10245
|
-
|
|
10246
|
-
|
|
10963
|
+
flushSync(() => {
|
|
10964
|
+
setUser(null);
|
|
10965
|
+
setPermissions([]);
|
|
10966
|
+
setIsLoading(false);
|
|
10967
|
+
});
|
|
10247
10968
|
}
|
|
10248
|
-
};
|
|
10969
|
+
}, [authService]);
|
|
10249
10970
|
const refreshToken = async () => {
|
|
10250
10971
|
await authService.refreshToken();
|
|
10251
10972
|
};
|
|
10252
10973
|
const hasPermission = (permission) => {
|
|
10253
|
-
var
|
|
10254
|
-
if (!((
|
|
10974
|
+
var _a2;
|
|
10975
|
+
if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
|
|
10255
10976
|
return permissions.includes(permission);
|
|
10256
10977
|
};
|
|
10257
10978
|
const hasRole = (role) => {
|
|
10258
|
-
var
|
|
10259
|
-
if (!((
|
|
10979
|
+
var _a2;
|
|
10980
|
+
if (!((_a2 = config == null ? void 0 : config.permissions) == null ? void 0 : _a2.enabled)) return true;
|
|
10260
10981
|
if (user && "role" in user) {
|
|
10261
10982
|
return user.role === role;
|
|
10262
10983
|
}
|
|
@@ -10465,6 +11186,32 @@ function MockAuthProvider({
|
|
|
10465
11186
|
};
|
|
10466
11187
|
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
10467
11188
|
}
|
|
11189
|
+
function NoAuthProvider({ children }) {
|
|
11190
|
+
const noOpLogin = async () => {
|
|
11191
|
+
console.warn("NoAuthProvider: login() called but auth is disabled");
|
|
11192
|
+
};
|
|
11193
|
+
const noOpLogout = async () => {
|
|
11194
|
+
console.warn("NoAuthProvider: logout() called but auth is disabled");
|
|
11195
|
+
};
|
|
11196
|
+
const noOpRefresh = async () => {
|
|
11197
|
+
console.warn("NoAuthProvider: refreshToken() called but auth is disabled");
|
|
11198
|
+
};
|
|
11199
|
+
const value = {
|
|
11200
|
+
user: null,
|
|
11201
|
+
isAuthenticated: true,
|
|
11202
|
+
// Always authenticated in no-auth mode
|
|
11203
|
+
isLoading: false,
|
|
11204
|
+
permissions: [],
|
|
11205
|
+
login: noOpLogin,
|
|
11206
|
+
logout: noOpLogout,
|
|
11207
|
+
refreshToken: noOpRefresh,
|
|
11208
|
+
hasPermission: () => true,
|
|
11209
|
+
// All permissions granted
|
|
11210
|
+
hasRole: () => true
|
|
11211
|
+
// All roles granted
|
|
11212
|
+
};
|
|
11213
|
+
return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
|
|
11214
|
+
}
|
|
10468
11215
|
const MockAuthContext = createContext(
|
|
10469
11216
|
void 0
|
|
10470
11217
|
);
|
|
@@ -13604,7 +14351,13 @@ function createReactApp(config) {
|
|
|
13604
14351
|
tree = /* @__PURE__ */ jsx(NavigationProvider, { initialNavigation: navigation, children: /* @__PURE__ */ jsx(SidebarProvider, { children: tree }) });
|
|
13605
14352
|
}
|
|
13606
14353
|
if (enableAuth) {
|
|
13607
|
-
|
|
14354
|
+
const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
|
|
14355
|
+
if (authMode === "none") {
|
|
14356
|
+
setGlobalAuthService(null);
|
|
14357
|
+
tree = /* @__PURE__ */ jsx(NoAuthProvider, { children: tree });
|
|
14358
|
+
} else {
|
|
14359
|
+
tree = /* @__PURE__ */ jsx(AuthProvider, { config: auth, children: tree });
|
|
14360
|
+
}
|
|
13608
14361
|
}
|
|
13609
14362
|
if (enableQuery) {
|
|
13610
14363
|
tree = /* @__PURE__ */ jsxs(QueryClientProvider, { client: queryClient, children: [
|
|
@@ -13622,7 +14375,8 @@ function createReactApp(config) {
|
|
|
13622
14375
|
routes.push({ path, component });
|
|
13623
14376
|
},
|
|
13624
14377
|
render() {
|
|
13625
|
-
const
|
|
14378
|
+
const authMode = (auth == null ? void 0 : auth.mode) || "pattern-stack";
|
|
14379
|
+
const shouldProtect = enableAuth && authMode !== "none" && (auth == null ? void 0 : auth.requireAuth) !== false;
|
|
13626
14380
|
const publicPaths = (auth == null ? void 0 : auth.publicPaths) ?? ["/login", "/register", "/forgot-password"];
|
|
13627
14381
|
const isPublicPath = (path) => {
|
|
13628
14382
|
return publicPaths.some((p) => path === p || path.startsWith(p + "/"));
|
|
@@ -16810,12 +17564,14 @@ export {
|
|
|
16810
17564
|
APIDataTemplate,
|
|
16811
17565
|
API_ENDPOINTS,
|
|
16812
17566
|
APP_NAME,
|
|
17567
|
+
AUTH_TOKEN_KEY,
|
|
16813
17568
|
Accordion,
|
|
16814
17569
|
ActivityFeed,
|
|
16815
17570
|
AdminCRUDTemplate,
|
|
16816
17571
|
AdminDashboardTemplate,
|
|
16817
17572
|
AdminDetailTemplate,
|
|
16818
17573
|
Alert,
|
|
17574
|
+
ApiClient,
|
|
16819
17575
|
AppHeader,
|
|
16820
17576
|
AppLayout,
|
|
16821
17577
|
AuthDivider,
|
|
@@ -16842,6 +17598,7 @@ export {
|
|
|
16842
17598
|
ColorSwatch,
|
|
16843
17599
|
ComponentShowcasePage,
|
|
16844
17600
|
ComponentShowcaseTemplate,
|
|
17601
|
+
ConnectionStatus,
|
|
16845
17602
|
DASHBOARD_CHART_HEIGHTS,
|
|
16846
17603
|
DASHBOARD_CONTAINER_HEIGHTS,
|
|
16847
17604
|
DASHBOARD_HEIGHT_CLASSES,
|
|
@@ -16882,6 +17639,7 @@ export {
|
|
|
16882
17639
|
EnhancedDataTemplate,
|
|
16883
17640
|
EntityIcon,
|
|
16884
17641
|
ErrorBoundary2 as ErrorBoundary,
|
|
17642
|
+
FieldGrid,
|
|
16885
17643
|
FileUpload,
|
|
16886
17644
|
FormField,
|
|
16887
17645
|
FormGroup,
|
|
@@ -16901,6 +17659,7 @@ export {
|
|
|
16901
17659
|
Modal,
|
|
16902
17660
|
NavMenu,
|
|
16903
17661
|
NavigationProvider,
|
|
17662
|
+
NoAuthProvider,
|
|
16904
17663
|
PageTemplate,
|
|
16905
17664
|
PageTitle,
|
|
16906
17665
|
Pagination,
|
|
@@ -16964,19 +17723,24 @@ export {
|
|
|
16964
17723
|
breakpoints,
|
|
16965
17724
|
cn,
|
|
16966
17725
|
createAPIDataTemplate,
|
|
17726
|
+
createApiClient,
|
|
17727
|
+
createMobileCardRenderer,
|
|
16967
17728
|
createReactApp,
|
|
16968
17729
|
createSimpleApp,
|
|
16969
17730
|
defaultFieldRenderers,
|
|
16970
17731
|
detectBulkOperationType,
|
|
16971
17732
|
detectUIConfig,
|
|
17733
|
+
entityToListCardProps,
|
|
16972
17734
|
env,
|
|
16973
17735
|
formatNumberWithTooltip,
|
|
16974
17736
|
generateBulkOperationName,
|
|
17737
|
+
generateCompactColumns,
|
|
16975
17738
|
getAnimationClasses,
|
|
16976
17739
|
getChartHeight,
|
|
16977
17740
|
getContainerHeightClass,
|
|
16978
17741
|
getCurrentBreakpoint,
|
|
16979
17742
|
getFieldType,
|
|
17743
|
+
getGlobalAuthService,
|
|
16980
17744
|
getIcon,
|
|
16981
17745
|
getNavigationItems,
|
|
16982
17746
|
getResponsiveClasses,
|
|
@@ -17000,6 +17764,7 @@ export {
|
|
|
17000
17764
|
useCreateExample,
|
|
17001
17765
|
useDeleteExample,
|
|
17002
17766
|
useEntityData,
|
|
17767
|
+
useEntityDetail,
|
|
17003
17768
|
useErrorBoundary,
|
|
17004
17769
|
useFieldMetadata,
|
|
17005
17770
|
useGetExample,
|
|
@@ -17011,6 +17776,7 @@ export {
|
|
|
17011
17776
|
useMediaQuery,
|
|
17012
17777
|
useMockAuth,
|
|
17013
17778
|
useNavigation,
|
|
17779
|
+
useOnlineStatus,
|
|
17014
17780
|
useOverflowDetection,
|
|
17015
17781
|
usePermissions,
|
|
17016
17782
|
useResponsiveClasses,
|