@boxa-devops/depo-shared 1.0.1
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/README.md +72 -0
- package/dist/LangSwitch.d.ts +22 -0
- package/dist/LangSwitch.d.ts.map +1 -0
- package/dist/LangSwitch.js +26 -0
- package/dist/LangSwitch.js.map +1 -0
- package/dist/PartPhoto.d.ts +13 -0
- package/dist/PartPhoto.d.ts.map +1 -0
- package/dist/PartPhoto.js +21 -0
- package/dist/PartPhoto.js.map +1 -0
- package/dist/api.d.ts +20739 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +6 -0
- package/dist/api.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/i18nGuard.d.ts +29 -0
- package/dist/i18nGuard.d.ts.map +1 -0
- package/dist/i18nGuard.js +75 -0
- package/dist/i18nGuard.js.map +1 -0
- package/dist/i18nKeys.d.ts +16 -0
- package/dist/i18nKeys.d.ts.map +1 -0
- package/dist/i18nKeys.js +16 -0
- package/dist/i18nKeys.js.map +1 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/lang.d.ts +7 -0
- package/dist/lang.d.ts.map +1 -0
- package/dist/lang.js +24 -0
- package/dist/lang.js.map +1 -0
- package/dist/status.d.ts +3 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +23 -0
- package/dist/status.js.map +1 -0
- package/error_codes.json +699 -0
- package/openapi.json +42042 -0
- package/package.json +53 -0
- package/permissions.json +70 -0
- package/src/LangSwitch.tsx +44 -0
- package/src/PartPhoto.tsx +48 -0
- package/src/api.ts +20739 -0
- package/src/errors.ts +25 -0
- package/src/i18nGuard.ts +77 -0
- package/src/i18nKeys.ts +15 -0
- package/src/index.ts +168 -0
- package/src/lang.ts +26 -0
- package/src/status.ts +23 -0
- package/tokens.css +141 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Error body shape used by all API 4xx responses: {code, message, detail?}. */
|
|
2
|
+
export interface ApiErrorDetail {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
detail?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Thrown for any non-2xx response. status 0 = network failure. */
|
|
9
|
+
export class ApiError extends Error {
|
|
10
|
+
status: number;
|
|
11
|
+
code: string;
|
|
12
|
+
detail?: string;
|
|
13
|
+
/** The whole refusal payload. Some refusals carry structured context the
|
|
14
|
+
* UI must render (e.g. 409 ROLE_SCOPE_CHANGE lists the affected grants).
|
|
15
|
+
* Defaults to `err` when the caller has no wider envelope to hand over. */
|
|
16
|
+
body: unknown;
|
|
17
|
+
|
|
18
|
+
constructor(status: number, err: ApiErrorDetail, body?: unknown) {
|
|
19
|
+
super(err.message);
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.code = err.code;
|
|
22
|
+
this.detail = err.detail;
|
|
23
|
+
this.body = body ?? err;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/i18nGuard.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guard that would have caught it: no key the code CALLS may be missing
|
|
3
|
+
* from the dictionaries.
|
|
4
|
+
*
|
|
5
|
+
* On 2026-09-03 the admin locale files lost 141 keys and every screen that used
|
|
6
|
+
* them rendered its key verbatim — «orders.reasonPlanned» in the orders filter,
|
|
7
|
+
* «passport.stage.ANY» on the Texpasport tab. The suites went red one screen at
|
|
8
|
+
* a time, and the parity test (i18n.test.tsx) stayed GREEN throughout: it
|
|
9
|
+
* compares ru against uz, so a key missing from BOTH is perfectly symmetrical.
|
|
10
|
+
*
|
|
11
|
+
* This one reads the other direction — from the source to the dictionaries —
|
|
12
|
+
* and is deliberately static: it needs no screen to be rendered, so it covers
|
|
13
|
+
* pages no render test mounts. Its runtime twin lives in each app's
|
|
14
|
+
* test-setup.ts, where i18next is told to THROW on a miss; that one catches the
|
|
15
|
+
* keys a screen builds at runtime out of an enum code, which no scanner can
|
|
16
|
+
* resolve.
|
|
17
|
+
*
|
|
18
|
+
* The body lives here, the globs stay per app: each app feeds its own `src`
|
|
19
|
+
* PLUS this package, so a `t()` inside a shared component is checked against
|
|
20
|
+
* both dictionaries that render it.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type Dict = { [k: string]: string | Dict };
|
|
24
|
+
|
|
25
|
+
/** `t("orders.reason")` / `i18n.t("...")` — a key spelled out in full. */
|
|
26
|
+
const LITERAL = /\bt\(\s*"([a-z][a-zA-Z0-9_]*(?:\.[a-zA-Z0-9_]+)+)"/g;
|
|
27
|
+
/** t(`passport.stage.${row.stages}`) — only the fixed head is knowable here. */
|
|
28
|
+
const TEMPLATE = /\bt\(\s*`([a-z][a-zA-Z0-9_]*(?:\.[a-zA-Z0-9_]+)*)\.\$\{/g;
|
|
29
|
+
|
|
30
|
+
function flat(d: Dict, prefix = ""): Record<string, string> {
|
|
31
|
+
const out: Record<string, string> = {};
|
|
32
|
+
for (const [k, v] of Object.entries(d)) {
|
|
33
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
34
|
+
if (typeof v === "string") out[key] = v;
|
|
35
|
+
else Object.assign(out, flat(v, key));
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Does this path name a group of keys (an object), rather than one string? */
|
|
41
|
+
function group(d: Dict, dotted: string): boolean {
|
|
42
|
+
let node: string | Dict | undefined = d;
|
|
43
|
+
for (const part of dotted.split(".")) {
|
|
44
|
+
if (typeof node !== "object" || node === null) return false;
|
|
45
|
+
node = node[part];
|
|
46
|
+
}
|
|
47
|
+
return typeof node === "object" && node !== null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function used(sources: Record<string, string>, re: RegExp): Map<string, string[]> {
|
|
51
|
+
const out = new Map<string, string[]>();
|
|
52
|
+
for (const [path, text] of Object.entries(sources)) {
|
|
53
|
+
// the guard and the setup spell example keys out in their own regexes
|
|
54
|
+
if (path.endsWith("i18nKeys.test.ts") || path.endsWith("i18nGuard.ts")) continue;
|
|
55
|
+
if (path.endsWith("test-setup.ts")) continue;
|
|
56
|
+
for (const m of text.matchAll(re)) {
|
|
57
|
+
out.set(m[1], [...(out.get(m[1]) ?? []), path.replace("./", "")]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Keys called in `sources` that are absent from any of `dicts`. */
|
|
64
|
+
export function missingKeys(sources: Record<string, string>, dicts: Dict[]): string[] {
|
|
65
|
+
const flats = dicts.map((d) => flat(d));
|
|
66
|
+
return [...used(sources, LITERAL)]
|
|
67
|
+
.filter(([key]) => flats.some((f) => !(key in f)))
|
|
68
|
+
// named, not counted: the failure has to say WHICH screen prints a raw key
|
|
69
|
+
.map(([key, files]) => `${key} (${[...new Set(files)].join(", ")})`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Heads of dynamic keys — t(`x.y.${code}`) — whose group is absent. */
|
|
73
|
+
export function missingKeyGroups(sources: Record<string, string>, dicts: Dict[]): string[] {
|
|
74
|
+
return [...used(sources, TEMPLATE)]
|
|
75
|
+
.filter(([key]) => dicts.some((d) => !group(d, key)))
|
|
76
|
+
.map(([key, files]) => `${key}.* (${[...new Set(files)].join(", ")})`);
|
|
77
|
+
}
|
package/src/i18nKeys.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every i18n key the components in this package resolve against the
|
|
3
|
+
* CONSUMER's dictionary. The components carry no dictionary of their own —
|
|
4
|
+
* they call the host app's `t()`, so a key that exists here and nowhere in
|
|
5
|
+
* depo-terminal / depo-admin throws at runtime (both apps configure i18next
|
|
6
|
+
* to throw on a miss).
|
|
7
|
+
*
|
|
8
|
+
* Before the split each app's i18nKeys.test.ts globbed
|
|
9
|
+
* ../../../packages/shared/src to catch this. That relative path cannot
|
|
10
|
+
* survive separate repos, so the list is explicit and guarded on the
|
|
11
|
+
* PRODUCING side instead: i18nKeys.test.ts here fails if a component calls
|
|
12
|
+
* t() with a key missing from this array, and each app asserts every key in
|
|
13
|
+
* the array exists in its own ru.json and uz.json.
|
|
14
|
+
*/
|
|
15
|
+
export const SHARED_I18N_KEYS = ["lang.switch"] as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Generated API types land in src/api.ts via `pnpm generate` (openapi-typescript).
|
|
2
|
+
import type { components } from "./api.js";
|
|
3
|
+
|
|
4
|
+
export type Schemas = components["schemas"];
|
|
5
|
+
|
|
6
|
+
export type TokenOut = Schemas["TokenOut"];
|
|
7
|
+
export type EventOut = Schemas["EventOut"];
|
|
8
|
+
|
|
9
|
+
// Admin web auth (B8): password + TOTP 2FA, httpOnly cookie session.
|
|
10
|
+
export type LoginIn = Schemas["LoginIn"];
|
|
11
|
+
export type EnrollIn = Schemas["EnrollIn"];
|
|
12
|
+
export type EnrollConfirmIn = Schemas["EnrollConfirmIn"];
|
|
13
|
+
export type EnrollBeginOut = Schemas["EnrollBeginOut"];
|
|
14
|
+
export type SessionOut = Schemas["AdminSessionOut"];
|
|
15
|
+
|
|
16
|
+
// Item card + history + work order (admin star screens, B8).
|
|
17
|
+
export type ItemCard = Schemas["ItemCard"];
|
|
18
|
+
export type ItemCandidate = Schemas["ItemCandidate"];
|
|
19
|
+
export type HistoryEvent = Schemas["HistoryEvent"];
|
|
20
|
+
export type HistoryPage = Schemas["HistoryPage"];
|
|
21
|
+
export type DefectHistoryRow = Schemas["DefectHistoryRow"];
|
|
22
|
+
export type DefectProposalRow = Schemas["DefectProposalRow"];
|
|
23
|
+
export type NomenclatureReviewRow = Schemas["NomenclatureReviewRow"];
|
|
24
|
+
export type StateAtOut = Schemas["StateAtOut"];
|
|
25
|
+
|
|
26
|
+
// Part passport (B15): the FORM of a part type — every field the depot's
|
|
27
|
+
// formular asks, measured or not — and the answers one instance carries.
|
|
28
|
+
export type NomenclatureFieldRef = Schemas["NomenclatureFieldRef"];
|
|
29
|
+
export type PassportOut = Schemas["PassportOut"];
|
|
30
|
+
export type PassportValue = Schemas["PassportValue"];
|
|
31
|
+
export type AttrsUpdateIn = Schemas["AttrsUpdateIn"];
|
|
32
|
+
export type AttrsUpdateOut = Schemas["AttrsUpdateOut"];
|
|
33
|
+
export type MeasurementAddIn = Schemas["MeasurementAddIn"];
|
|
34
|
+
export type MeasurementAddOut = Schemas["MeasurementAddOut"];
|
|
35
|
+
export type NomenclatureOperationRef = Schemas["NomenclatureOperationRef"];
|
|
36
|
+
export type MeasurementValueIn = Schemas["MeasurementValueIn"];
|
|
37
|
+
export type ItemMeasurementRow = Schemas["ItemMeasurementRow"];
|
|
38
|
+
export type ItemMeasurements = Schemas["ItemMeasurementsOut"];
|
|
39
|
+
export type ServiceHistoryRow = Schemas["ServiceHistoryRow"];
|
|
40
|
+
export type NomenclatureFieldOut = Schemas["NomenclatureFieldOut"];
|
|
41
|
+
export type NomenclatureOperationOut = Schemas["NomenclatureOperationOut"];
|
|
42
|
+
export type NomenclatureRuleOut = Schemas["NomenclatureRuleOut"];
|
|
43
|
+
export type RuleParamOut = Schemas["RuleParamOut"];
|
|
44
|
+
export type RulePreviewOut = Schemas["RulePreviewOut"];
|
|
45
|
+
export type RecommendationOut = Schemas["RecommendationOut"];
|
|
46
|
+
export type WorkOrderOut = Schemas["WorkOrderOut"];
|
|
47
|
+
export type WorkOrderDetail = Schemas["WorkOrderDetail"];
|
|
48
|
+
export type TaskOut = Schemas["TaskOut"];
|
|
49
|
+
export type AssignmentOut = Schemas["AssignmentOut"];
|
|
50
|
+
export type SignatureOut = Schemas["SignatureOut"];
|
|
51
|
+
export type SearchResults = Schemas["SearchResults"];
|
|
52
|
+
|
|
53
|
+
// Staff card (B: person + role grants + badge saved as one unit).
|
|
54
|
+
export type StaffOut = Schemas["StaffOut"];
|
|
55
|
+
export type StaffSave = Schemas["StaffSave"];
|
|
56
|
+
export type RoleGrantIn = Schemas["RoleGrantIn"];
|
|
57
|
+
export type RoleGrantOut = Schemas["RoleGrantOut"];
|
|
58
|
+
export type RoleTemplateOut = Schemas["RoleTemplateOut"];
|
|
59
|
+
export type StaffImportRow = Schemas["ImportRowOut"];
|
|
60
|
+
export type StaffImportResult = Schemas["ImportResultOut"];
|
|
61
|
+
|
|
62
|
+
// Dictionary tabs: one xlsx importer and one record lifecycle for all of them
|
|
63
|
+
// (services/dict_import, services/dict_refs).
|
|
64
|
+
export type DictImportRow = Schemas["DictImportRow"];
|
|
65
|
+
export type DictImportResult = Schemas["DictImportResult"];
|
|
66
|
+
export type Deletable = Schemas["DeletableOut"];
|
|
67
|
+
|
|
68
|
+
// Locomotive card (read-only admin card page).
|
|
69
|
+
export type LocomotiveCard = Schemas["LocomotiveCard"];
|
|
70
|
+
export type LocoComposition = Schemas["CompositionOut"];
|
|
71
|
+
export type LocoCompositionAt = Schemas["CompositionAtOut"];
|
|
72
|
+
export type LocoCompositionGroup = Schemas["CompositionGroup"];
|
|
73
|
+
export type LocoCompositionItem = Schemas["CompositionItem"];
|
|
74
|
+
export type LocoHistoryEvent = Schemas["LocoHistoryEvent"];
|
|
75
|
+
export type LocoHistoryPage = Schemas["LocoHistoryPage"];
|
|
76
|
+
|
|
77
|
+
// A «machine» is the (series, number) group of section rows — no such table
|
|
78
|
+
// exists, the API groups on read (routers/loco_units.py).
|
|
79
|
+
export type LocoUnitRow = Schemas["LocoUnitRow"];
|
|
80
|
+
export type LocoUnitCard = Schemas["LocoUnitCard"];
|
|
81
|
+
export type LocoUnitSection = Schemas["UnitSection"];
|
|
82
|
+
|
|
83
|
+
// Terminal v2 master mode (B10): grid, field-register, co-signatures.
|
|
84
|
+
export type LocoOverviewRow = Schemas["LocoOverviewRow"];
|
|
85
|
+
export type LocoSeriesRow = Schemas["LocoSeriesOut"];
|
|
86
|
+
export type FieldRegisterIn = Schemas["FieldRegisterIn"];
|
|
87
|
+
export type FieldRegisterOut = Schemas["FieldRegisterOut"];
|
|
88
|
+
export type WorkSignIn = Schemas["WorkSignIn"];
|
|
89
|
+
export type WorkSignOut = Schemas["WorkSignOut"];
|
|
90
|
+
export type NomenclatureSuggestRow = Schemas["NomenclatureSuggestRow"];
|
|
91
|
+
export type NomenclatureCategoryRef = Schemas["NomenclatureCategoryRef"];
|
|
92
|
+
export type NomenclatureRef = Schemas["NomenclatureRef"];
|
|
93
|
+
export type NomenclatureQuickCreateOut = Schemas["NomenclatureQuickCreateOut"];
|
|
94
|
+
|
|
95
|
+
// B13 «Ro'yxatga olish»: a whole pass over a machine under one signature.
|
|
96
|
+
export type FieldRegisterBatchIn = Schemas["FieldRegisterBatchIn"];
|
|
97
|
+
export type StockReceiveBatchIn = Schemas["StockReceiveBatchIn"];
|
|
98
|
+
export type FieldRegisterBatchOut = Schemas["FieldRegisterBatchOut"];
|
|
99
|
+
export type FieldRegisterEntry = Schemas["FieldRegisterEntry"];
|
|
100
|
+
|
|
101
|
+
// Two-act custody handover (TZ 3.7): give (HANDOVER_GIVE) then accept/decline.
|
|
102
|
+
export type HandoverGiveIn = Schemas["HandoverGiveIn"];
|
|
103
|
+
export type HandoverAcceptIn = Schemas["HandoverAcceptIn"];
|
|
104
|
+
export type HandoverDeclineIn = Schemas["HandoverDeclineIn"];
|
|
105
|
+
export type HandoverOut = Schemas["HandoverOut"];
|
|
106
|
+
export type PendingHandoverOut = Schemas["PendingHandoverOut"];
|
|
107
|
+
|
|
108
|
+
/** Discriminated union of POST /api/v1/events command bodies. */
|
|
109
|
+
export type EventCommand =
|
|
110
|
+
| Schemas["InstallCommand"]
|
|
111
|
+
| Schemas["MoveCommand"]
|
|
112
|
+
| Schemas["HandoverCommand"]
|
|
113
|
+
| Schemas["RemoveCommand"]
|
|
114
|
+
| Schemas["ReceiveOpeningCommand"]
|
|
115
|
+
| Schemas["FoundCommand"]
|
|
116
|
+
| Schemas["InspectCommand"]
|
|
117
|
+
| Schemas["DispositionCommand"]
|
|
118
|
+
| Schemas["StornoCommand"]
|
|
119
|
+
| Schemas["RepairStartCommand"]
|
|
120
|
+
| Schemas["RepairPauseCommand"]
|
|
121
|
+
| Schemas["RepairResumeCommand"]
|
|
122
|
+
| Schemas["RepairFinishCommand"]
|
|
123
|
+
| Schemas["GenericCommand"];
|
|
124
|
+
|
|
125
|
+
export type EventType = EventCommand["event_type"];
|
|
126
|
+
|
|
127
|
+
/** Runtime list of all journal event types (mirrors the EventCommand union). */
|
|
128
|
+
export const EVENT_TYPES: readonly EventType[] = [
|
|
129
|
+
"INSTALL",
|
|
130
|
+
"MOVE",
|
|
131
|
+
"HANDOVER",
|
|
132
|
+
"REMOVE",
|
|
133
|
+
"RECEIVE_OPENING",
|
|
134
|
+
"FOUND",
|
|
135
|
+
"INSPECT",
|
|
136
|
+
"DISPOSITION",
|
|
137
|
+
"STORNO",
|
|
138
|
+
"RECEIVE_NEW",
|
|
139
|
+
"ISSUE_OUT",
|
|
140
|
+
"SEND_TO_REPAIR",
|
|
141
|
+
"REPAIR_START",
|
|
142
|
+
"REPAIR_PAUSE",
|
|
143
|
+
"REPAIR_RESUME",
|
|
144
|
+
"REPAIR_FINISH",
|
|
145
|
+
"QC_ACCEPT",
|
|
146
|
+
"QC_REJECT",
|
|
147
|
+
"STORE_IN",
|
|
148
|
+
"ASSEMBLE",
|
|
149
|
+
"DISASSEMBLE",
|
|
150
|
+
"SEND_TO_PLANT",
|
|
151
|
+
"RETURN_FROM_PLANT",
|
|
152
|
+
"PLANT_REJECT",
|
|
153
|
+
"COUNT",
|
|
154
|
+
"SCRAP",
|
|
155
|
+
"LOST",
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
export { ApiError } from "./errors.js";
|
|
159
|
+
export type { ApiErrorDetail } from "./errors.js";
|
|
160
|
+
export { statusDot } from "./status.js";
|
|
161
|
+
export { LANGS, setLanguage, storedLanguage } from "./lang.js";
|
|
162
|
+
export type { Lang } from "./lang.js";
|
|
163
|
+
export { LangSwitch } from "./LangSwitch.js";
|
|
164
|
+
export { PartPhoto } from "./PartPhoto.js";
|
|
165
|
+
export { missingKeys, missingKeyGroups } from "./i18nGuard.js";
|
|
166
|
+
export type { Dict } from "./i18nGuard.js";
|
|
167
|
+
// i18n keys the components here resolve against the CONSUMER's dictionary
|
|
168
|
+
export { SHARED_I18N_KEYS } from "./i18nKeys.js";
|
package/src/lang.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import i18n from "i18next";
|
|
2
|
+
|
|
3
|
+
export const LANGS = ["uz", "ru"] as const;
|
|
4
|
+
export type Lang = (typeof LANGS)[number];
|
|
5
|
+
|
|
6
|
+
const STORAGE_KEY = "depo.lang";
|
|
7
|
+
|
|
8
|
+
/** The remembered choice, per browser. localStorage throws in a locked-down
|
|
9
|
+
* profile (a kiosk is one) — the caller's default covers that. */
|
|
10
|
+
export function storedLanguage(): Lang | null {
|
|
11
|
+
try {
|
|
12
|
+
const v = localStorage.getItem(STORAGE_KEY);
|
|
13
|
+
return LANGS.includes(v as Lang) ? (v as Lang) : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function setLanguage(lng: Lang): void {
|
|
20
|
+
try {
|
|
21
|
+
localStorage.setItem(STORAGE_KEY, lng);
|
|
22
|
+
} catch {
|
|
23
|
+
// private / locked-down mode: the choice just does not survive a reload
|
|
24
|
+
}
|
|
25
|
+
void i18n.changeLanguage(lng);
|
|
26
|
+
}
|
package/src/status.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Hue per part status, shared by the registry and the monoblock: the same
|
|
2
|
+
* status keeps the same color everywhere. Tokens live in tokens.css
|
|
3
|
+
* (--color-st-*); labels stay per-app, they differ by product wording. */
|
|
4
|
+
const DOT: Record<string, string> = {
|
|
5
|
+
IN_STOCK_NEW: "bg-st-blue",
|
|
6
|
+
IN_STOCK_OK: "bg-st-green",
|
|
7
|
+
IN_STOCK_DEFECT: "bg-st-amber",
|
|
8
|
+
INSTALLED: "bg-st-teal",
|
|
9
|
+
REMOVED: "bg-st-slate",
|
|
10
|
+
IN_TRANSIT: "bg-st-sky",
|
|
11
|
+
IN_REPAIR: "bg-st-amber",
|
|
12
|
+
AWAITING_QC: "bg-st-purple",
|
|
13
|
+
AWAITING_DISPOSITION: "bg-st-orange",
|
|
14
|
+
PLANT_REPAIR: "bg-st-indigo",
|
|
15
|
+
SCRAPPED: "bg-st-red",
|
|
16
|
+
LOST: "bg-st-red-dark",
|
|
17
|
+
REPLACED: "bg-st-stone",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** Dot class for a part status; unknown statuses render neutral. */
|
|
21
|
+
export function statusDot(status: string): string {
|
|
22
|
+
return DOT[status] ?? "bg-st-slate";
|
|
23
|
+
}
|
package/tokens.css
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Design tokens — «технический формуляр депо».
|
|
3
|
+
THE ONLY place where color / radius / shadow / duration values live.
|
|
4
|
+
A hex code anywhere else in apps/admin is a review error.
|
|
5
|
+
|
|
6
|
+
WCAG AA contrast (computed, normal text needs >= 4.5):
|
|
7
|
+
ink #1A2332 on steel #EEF0F2 = 13.82 ink on paper #FFF = 15.78
|
|
8
|
+
ink-2 #44506A on paper = 8.07 ink-2 on steel = 7.07
|
|
9
|
+
ink-3 #66718A on paper = 4.89
|
|
10
|
+
accent #1E4D8C on paper = 8.42 accent on steel = 7.37
|
|
11
|
+
white on accent #1E4D8C = 8.42 white on ink = 15.78
|
|
12
|
+
white on danger #C62828 = 5.62 white on ok #2E7D32= 5.13
|
|
13
|
+
ink on warn #F59F00 = 7.40 (amber tiles keep dark text)
|
|
14
|
+
warn-ink #7A4F00 on warn-tint = 6.47
|
|
15
|
+
danger-ink #B71C1C on danger-tint = 5.65
|
|
16
|
+
ok-ink #1B5E20 on ok-tint = 6.86
|
|
17
|
+
sidebar text #C9D2E0 on ink = 10.36
|
|
18
|
+
link #2A5FA8 on paper = 6.36
|
|
19
|
+
========================================================================= */
|
|
20
|
+
|
|
21
|
+
/* --- Self-hosted fonts (depot may live without internet: no CDN, ever) --- */
|
|
22
|
+
@font-face {
|
|
23
|
+
font-family: "Inter";
|
|
24
|
+
font-style: normal;
|
|
25
|
+
font-weight: 100 900;
|
|
26
|
+
font-display: swap;
|
|
27
|
+
src: url("/fonts/inter-latin-wght.woff2") format("woff2-variations");
|
|
28
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
|
29
|
+
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
|
|
30
|
+
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
31
|
+
}
|
|
32
|
+
@font-face {
|
|
33
|
+
font-family: "Inter";
|
|
34
|
+
font-style: normal;
|
|
35
|
+
font-weight: 100 900;
|
|
36
|
+
font-display: swap;
|
|
37
|
+
src: url("/fonts/inter-cyrillic-wght.woff2") format("woff2-variations");
|
|
38
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
39
|
+
}
|
|
40
|
+
@font-face {
|
|
41
|
+
font-family: "Inter";
|
|
42
|
+
font-style: normal;
|
|
43
|
+
font-weight: 100 900;
|
|
44
|
+
font-display: swap;
|
|
45
|
+
src: url("/fonts/inter-cyrillic-ext-wght.woff2") format("woff2-variations");
|
|
46
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F,
|
|
47
|
+
U+FE2E-FE2F;
|
|
48
|
+
}
|
|
49
|
+
@font-face {
|
|
50
|
+
font-family: "JetBrains Mono";
|
|
51
|
+
font-style: normal;
|
|
52
|
+
font-weight: 100 800;
|
|
53
|
+
font-display: swap;
|
|
54
|
+
src: url("/fonts/jetbrains-mono-latin-wght.woff2") format("woff2-variations");
|
|
55
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
|
56
|
+
U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
|
|
57
|
+
U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
58
|
+
}
|
|
59
|
+
@font-face {
|
|
60
|
+
font-family: "JetBrains Mono";
|
|
61
|
+
font-style: normal;
|
|
62
|
+
font-weight: 100 800;
|
|
63
|
+
font-display: swap;
|
|
64
|
+
src: url("/fonts/jetbrains-mono-cyrillic-wght.woff2") format("woff2-variations");
|
|
65
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@theme {
|
|
69
|
+
/* Wipe the stock Tailwind palette: bg-gray-50 / text-indigo-600 & co stop
|
|
70
|
+
compiling, so every color goes through the tokens below. */
|
|
71
|
+
--color-*: initial;
|
|
72
|
+
|
|
73
|
+
/* --- Neutrals ---------------------------------------------------------- */
|
|
74
|
+
--color-steel: #eef0f2; /* app background — cold light grey */
|
|
75
|
+
--color-paper: #ffffff; /* surfaces: cards, tables, header */
|
|
76
|
+
--color-ink: #1a2332; /* primary text; also the sidebar fill */
|
|
77
|
+
--color-ink-2: #44506a; /* secondary text */
|
|
78
|
+
--color-ink-3: #66718a; /* muted text, placeholders (AA on paper) */
|
|
79
|
+
--color-line: #d5dae1; /* hairline borders */
|
|
80
|
+
--color-line-strong: #aeb6c2; /* emphasized rules (form header frame) */
|
|
81
|
+
--color-hover: #f4f6f8; /* row hover — half a tone off paper */
|
|
82
|
+
--color-white: #ffffff;
|
|
83
|
+
--color-transparent: transparent;
|
|
84
|
+
|
|
85
|
+
/* --- Accent: deep railway blue. Decor NEVER uses signal colors. -------- */
|
|
86
|
+
--color-accent: #1e4d8c;
|
|
87
|
+
--color-accent-2: #2a5fa8; /* links, hover of primary */
|
|
88
|
+
--color-accent-tint: #e8eef6; /* selected chip / active nav tint */
|
|
89
|
+
--color-accent-lt: #7fa8d9; /* accent lightened for dark (sidebar) surfaces */
|
|
90
|
+
|
|
91
|
+
/* --- Signals — reserved for statuses & alarms only --------------------- */
|
|
92
|
+
--color-danger: #c62828;
|
|
93
|
+
--color-danger-ink: #b71c1c;
|
|
94
|
+
--color-danger-tint: #fbeaea;
|
|
95
|
+
--color-warn: #f59f00;
|
|
96
|
+
--color-warn-ink: #7a4f00;
|
|
97
|
+
--color-warn-tint: #fdf3df;
|
|
98
|
+
--color-ok: #2e7d32;
|
|
99
|
+
--color-ok-ink: #1b5e20;
|
|
100
|
+
--color-ok-tint: #e8f2e9;
|
|
101
|
+
|
|
102
|
+
/* --- Status hues (badge dots, chart fills). Same semantics as before the
|
|
103
|
+
restyle: hue per status unchanged, execution unified. ------------- */
|
|
104
|
+
--color-st-blue: #1565c0; /* IN_STOCK_NEW */
|
|
105
|
+
--color-st-green: #2e7d32; /* IN_STOCK_OK */
|
|
106
|
+
--color-st-teal: #00796b; /* INSTALLED */
|
|
107
|
+
--color-st-slate: #64748b; /* REMOVED, neutral/unknown */
|
|
108
|
+
--color-st-sky: #0288d1; /* IN_TRANSIT, ACCEPTED */
|
|
109
|
+
--color-st-amber: #f59f00; /* IN_STOCK_DEFECT, IN_REPAIR, IN_PROGRESS */
|
|
110
|
+
--color-st-purple: #7b1fa2; /* AWAITING_QC */
|
|
111
|
+
--color-st-orange: #e65100; /* AWAITING_DISPOSITION */
|
|
112
|
+
--color-st-indigo: #3949ab; /* PLANT_REPAIR, OPEN orders */
|
|
113
|
+
--color-st-violet: #7e57c2; /* ON_HOLD */
|
|
114
|
+
--color-st-red: #c62828; /* SCRAPPED, RETURNED */
|
|
115
|
+
--color-st-red-dark: #8e1919; /* LOST */
|
|
116
|
+
--color-st-stone: #8d6e63; /* REPLACED */
|
|
117
|
+
--color-st-cyan: #0097a7; /* repair event group on the history ribbon */
|
|
118
|
+
|
|
119
|
+
/* --- Type -------------------------------------------------------------- */
|
|
120
|
+
--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
121
|
+
--font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", Consolas,
|
|
122
|
+
monospace;
|
|
123
|
+
|
|
124
|
+
/* --- One radius. 6px. Not a zoo. (rounded-full stays for dots) --------- */
|
|
125
|
+
--radius-sm: 6px;
|
|
126
|
+
--radius-md: 6px;
|
|
127
|
+
--radius-lg: 6px;
|
|
128
|
+
--radius-xl: 6px;
|
|
129
|
+
|
|
130
|
+
/* --- Two shadows: rest and raise --------------------------------------- */
|
|
131
|
+
--shadow-rest: 0 1px 2px rgb(26 35 50 / 0.07);
|
|
132
|
+
--shadow-raise: 0 10px 28px rgb(26 35 50 / 0.16);
|
|
133
|
+
|
|
134
|
+
/* --- Motion: hover/focus 120ms, modals/toasts 200ms --------------------- */
|
|
135
|
+
--default-transition-duration: 120ms;
|
|
136
|
+
--duration-fast: 120ms;
|
|
137
|
+
--duration-slow: 200ms;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Spacing: stock Tailwind 4px base already yields the 4/8/12/16/24/32 scale
|
|
141
|
+
(p-1/2/3/4/6/8) — keep it, use only those steps. */
|