На графике
{selectedTags.length}
@@ -98,35 +55,44 @@ export function IndicatorsView({
{isError ? (
Не удалось загрузить текущие значения: {String(error)}
- ) : displayMode === 'overview' ? (
-
- {itemStatuses.map(({ item, statusInfo }) => (
-
- ))}
-
) : (
-
- {itemStatuses.map(({ item, statusInfo }) => (
-
- ))}
-
+ <>
+ {/* displayMode === 'overview' ? (
+
+ {itemStatuses.map(({ item, statusInfo }) => (
+
+ ))}
+
+ ) : (
+
+ {itemStatuses.map(({ item, statusInfo }) => (
+
+ ))}
+
+ ) */}
+
+ {items.map((item) => (
+
+ ))}
+
+ >
)}
);
diff --git a/src/shared/api/http.ts b/src/shared/api/http.ts
index a2a1776..0140511 100644
--- a/src/shared/api/http.ts
+++ b/src/shared/api/http.ts
@@ -1,5 +1,38 @@
export type QueryParamValue = string | number | string[] | undefined;
+function joinUrl(baseUrl: string, path: string): string {
+ const base = baseUrl.replace(/\/$/, '');
+ const endpoint = path.startsWith('/') ? path : `/${path}`;
+ return `${base}${endpoint}`;
+}
+
+function appendQueryParams(url: string, params?: Record
): string {
+ const searchParams = new URLSearchParams();
+
+ Object.entries(params ?? {}).forEach(([key, value]) => {
+ if (Array.isArray(value)) {
+ value.forEach((item) => searchParams.append(key, item));
+ return;
+ }
+
+ if (value !== undefined && value !== '') {
+ searchParams.set(key, String(value));
+ }
+ });
+
+ const query = searchParams.toString();
+ return query ? `${url}?${query}` : url;
+}
+
+/** Собирает URL API из base, пути и query-параметров. */
+export function buildApiUrl(
+ baseUrl: string,
+ path: string,
+ params?: Record,
+): string {
+ return appendQueryParams(joinUrl(baseUrl, path), params);
+}
+
/** Выполняет GET-запрос и добавляет query-параметры в едином формате для всех API. */
export async function getJson(
baseUrl: string,
@@ -7,18 +40,7 @@ export async function getJson(
params?: Record,
init?: RequestInit,
): Promise {
- const url = new URL(path, baseUrl);
-
- Object.entries(params ?? {}).forEach(([key, value]) => {
- if (Array.isArray(value)) {
- value.forEach((item) => url.searchParams.append(key, item));
- return;
- }
-
- if (value !== undefined && value !== '') {
- url.searchParams.set(key, String(value));
- }
- });
+ const url = buildApiUrl(baseUrl, path, params);
const response = await fetch(url, init);
if (!response.ok) {
diff --git a/src/styles/components/metrics.css b/src/styles/components/metrics.css
index 6a0db96..1cd3504 100644
--- a/src/styles/components/metrics.css
+++ b/src/styles/components/metrics.css
@@ -4,6 +4,53 @@
gap: 12px;
}
+.metric-widgets-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 10px;
+}
+
+.metric-widget {
+ min-width: 0;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 12px;
+ border: 1px solid rgba(88, 103, 121, 0.42);
+ border-radius: 8px;
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.03), transparent 28%),
+ linear-gradient(145deg, rgba(6, 9, 14, 0.98), rgba(2, 4, 8, 0.96));
+ box-shadow:
+ inset 0 1px 0 rgba(255, 255, 255, 0.05),
+ 0 6px 14px rgba(0, 0, 0, 0.28);
+}
+
+.metric-widget__tag {
+ flex: 0 0 auto;
+ min-width: 0;
+ color: #cbd5e1;
+ font-size: 0.74rem;
+ font-weight: 600;
+ line-height: 1.35;
+ white-space: normal;
+ overflow-wrap: anywhere;
+ word-break: break-word;
+}
+
+.metric-widget__value {
+ margin-top: auto;
+ align-self: flex-end;
+ min-width: 0;
+ color: #f8fafc;
+ font-size: 1.1rem;
+ font-weight: 700;
+ font-variant-numeric: tabular-nums;
+ line-height: 1.2;
+ text-align: right;
+}
+
.metric-mosaic {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(82px, 1fr));
diff --git a/vite.config.ts b/vite.config.ts
index 92806ff..bfacc8e 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -8,8 +8,12 @@ export default defineConfig({
},
server: {
port: 5173,
- },
- preview: {
- port: 4173,
+ proxy: {
+ '/api': {
+ target: 'https://beta.backend.drill.greact.ru',
+ changeOrigin: true,
+ rewrite: (path) => path.replace(/^\/api/, ''),
+ },
+ },
},
});