@updraft-solutions/mcrit-sdk 0.4.0 → 0.5.0

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.
Files changed (42) hide show
  1. package/dist/chunk-AKLFMP7G.cjs +2525 -0
  2. package/dist/chunk-YCIXOVEC.js +2525 -0
  3. package/dist/index.cjs +193 -3
  4. package/dist/index.d.cts +3 -3
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.js +196 -6
  7. package/dist/schemas/index.cjs +192 -2
  8. package/dist/schemas/index.d.cts +3123 -94
  9. package/dist/schemas/index.d.ts +3123 -94
  10. package/dist/schemas/index.js +195 -5
  11. package/dist/{models-Bc5BKYuI.d.cts → training-Der1DPU-.d.cts} +17563 -1056
  12. package/dist/{models-Bc5BKYuI.d.ts → training-Der1DPU-.d.ts} +17563 -1056
  13. package/dist/types/index.cjs +1 -1
  14. package/dist/types/index.d.cts +275 -6
  15. package/dist/types/index.d.ts +275 -6
  16. package/dist/types/index.js +1 -1
  17. package/package.json +1 -1
  18. package/src/index.ts +39 -0
  19. package/src/schemas/common.ts +33 -0
  20. package/src/schemas/course-milestone.ts +78 -0
  21. package/src/schemas/equipment.ts +27 -0
  22. package/src/schemas/form-template.ts +413 -0
  23. package/src/schemas/index.ts +7 -1
  24. package/src/schemas/location-map.ts +175 -0
  25. package/src/schemas/models.ts +38 -20
  26. package/src/schemas/newsletter.ts +362 -0
  27. package/src/schemas/todo.ts +46 -0
  28. package/src/schemas/training.ts +500 -0
  29. package/src/types/ai.ts +85 -0
  30. package/src/types/aircraft-block.ts +23 -0
  31. package/src/types/api.ts +3 -1
  32. package/src/types/background-task.ts +28 -0
  33. package/src/types/compliance.ts +27 -3
  34. package/src/types/index.ts +5 -0
  35. package/src/types/landing-fee.ts +92 -0
  36. package/src/types/models.ts +43 -3
  37. package/src/types/roles.ts +26 -0
  38. package/dist/chunk-RC2BBT6H.cjs +0 -1384
  39. package/dist/chunk-WQLDSZNN.js +0 -1384
  40. package/src/schemas/fee.ts +0 -37
  41. /package/dist/{chunk-LXNCAKJZ.js → chunk-2WJHTRIE.js} +0 -0
  42. /package/dist/{chunk-MOOGM3DW.cjs → chunk-DCEOET63.cjs} +0 -0
@@ -0,0 +1,500 @@
1
+ import { z } from "zod";
2
+
3
+ // ── Grading scales ──
4
+
5
+ // No i18n in scale data — a plain German `key` + `description`, verbatim
6
+ // from the source paper form (e.g. J3). The UI shows the key (chip/button),
7
+ // the description as a tooltip. App-level i18n (our own UI labels) is
8
+ // unaffected — only DATA i18n (per-point `{de,en,no}` labels) was removed.
9
+ export const gradingScalePointSchema = z.object({
10
+ key: z.string(),
11
+ description: z.string(),
12
+ });
13
+ export type GradingScalePoint = z.infer<typeof gradingScalePointSchema>;
14
+
15
+ export const gradingScaleSchema = z.object({
16
+ id: z.number(),
17
+ name: z.string(),
18
+ points: z.array(gradingScalePointSchema),
19
+ });
20
+ export type GradingScale = z.infer<typeof gradingScaleSchema>;
21
+
22
+ export const GradingScaleFormSchema = z.object({
23
+ name: z.string().min(1).max(255),
24
+ points: z.array(gradingScalePointSchema).min(2),
25
+ });
26
+ export type GradingScaleForm = z.infer<typeof GradingScaleFormSchema>;
27
+
28
+ // ── Training elements (catalog) ──
29
+
30
+ export const TrainingElementKindEnum = z.enum([
31
+ "flight_exercise",
32
+ "theory",
33
+ "briefing",
34
+ "check",
35
+ ]);
36
+ export type TrainingElementKind = z.infer<typeof TrainingElementKindEnum>;
37
+
38
+ export const trainingElementSchema = z.object({
39
+ id: z.number(),
40
+ code: z.string(),
41
+ name: z.string(),
42
+ description: z.string().nullable().optional(),
43
+ kind: TrainingElementKindEnum,
44
+ grading_scale_id: z.number().nullable().optional(),
45
+ grading_scale: gradingScaleSchema.nullable(),
46
+ is_active: z.boolean(),
47
+ // Where-used count — how many of the company's syllabi carry this
48
+ // element (`TrainingElementController::index`'s `attached_syllabi_count`
49
+ // withCount alias). Absent on any payload that doesn't count it.
50
+ attached_syllabi_count: z.number().optional(),
51
+ // Provenance stamp (which template element this row was seeded/adopted
52
+ // from) — not yet exposed by TrainingElementResource; optional/forward-
53
+ // compatible.
54
+ template_element_id: z.number().nullable().optional(),
55
+ });
56
+ export type TrainingElement = z.infer<typeof trainingElementSchema>;
57
+
58
+ export const TrainingElementFormSchema = z.object({
59
+ code: z.string().min(1).max(32),
60
+ name: z.string().min(1).max(255),
61
+ description: z.string().nullable().optional(),
62
+ kind: TrainingElementKindEnum,
63
+ grading_scale_id: z.number().nullable().optional(),
64
+ is_active: z.boolean().optional(),
65
+ });
66
+ export type TrainingElementForm = z.infer<typeof TrainingElementFormSchema>;
67
+
68
+ // ── Syllabus (first-class, company-owned — see
69
+ // docs/superpowers/specs/2026-08-03-syllabus-model-design.md) ──
70
+
71
+ // A syllabus's kind (Amendment 3 — progress checks): `regular` drives the
72
+ // Übersicht / per-flight Ausbildungsnachweis as before; `progress_check`
73
+ // marks a check sheet (A41–A44 / D4 style) that is attached to courses via
74
+ // `course_check_syllabi` and filled on demand per booking. `.default` keeps
75
+ // pre-amendment fixtures/payloads parseable while the inferred output type
76
+ // stays required.
77
+ export const SyllabusKindEnum = z.enum(["regular", "progress_check"]);
78
+ export type SyllabusKind = z.infer<typeof SyllabusKindEnum>;
79
+
80
+ export const syllabusElementSchema = z.object({
81
+ id: z.number(),
82
+ syllabus_id: z.number(),
83
+ // Embedded so a row still renders once its element is deactivated;
84
+ // `.nullable().optional()` mirrors `trainingRecordEntrySchema`'s own
85
+ // embedded-element convention — the backend omits the key entirely on any
86
+ // response that didn't eager-load the relation.
87
+ training_element: trainingElementSchema.nullable().optional(),
88
+ // The sheet's section header (was `phase` pre-amendment). `null` = no
89
+ // group declared.
90
+ group: z.string().nullable().optional(),
91
+ sort_order: z.number(),
92
+ is_required_each_flight: z.boolean(),
93
+ });
94
+ export type SyllabusElement = z.infer<typeof syllabusElementSchema>;
95
+
96
+ export const AttachSyllabusElementFormSchema = z.object({
97
+ training_element_id: z.number(),
98
+ group: z.string().nullable().optional(),
99
+ sort_order: z.number().int().min(0).optional(),
100
+ is_required_each_flight: z.boolean().optional(),
101
+ });
102
+ export type AttachSyllabusElementForm = z.infer<
103
+ typeof AttachSyllabusElementFormSchema
104
+ >;
105
+
106
+ export const UpdateSyllabusElementFormSchema = z.object({
107
+ group: z.string().nullable().optional(),
108
+ sort_order: z.number().int().min(0).optional(),
109
+ is_required_each_flight: z.boolean().optional(),
110
+ });
111
+ export type UpdateSyllabusElementForm = z.infer<
112
+ typeof UpdateSyllabusElementFormSchema
113
+ >;
114
+
115
+ // One row of the `POST syllabi/{syllabus}/elements/reorder` payload — the
116
+ // full flat order AND the (frontend-computed, authoritative) `group` per
117
+ // row in one pass. `group` is the corrected value after group-adoption-on-
118
+ // drag (see `app/utils/syllabusGroupAdoption.ts`), not just the row's prior
119
+ // value — a drag that moves a row into another group's block changes its
120
+ // group, not only its position.
121
+ export const ReorderSyllabusElementItemSchema = z.object({
122
+ id: z.number(),
123
+ group: z.string().nullable(),
124
+ });
125
+ export type ReorderSyllabusElementItem = z.infer<
126
+ typeof ReorderSyllabusElementItemSchema
127
+ >;
128
+
129
+ // One lean entry per distinct course referencing a syllabus via either
130
+ // pointer or a `course_check_syllabi` attachment
131
+ // (`SyllabusResource::buildUsedByCourses` — deduped, a course using both
132
+ // pointers gets a single `as: "both"` entry, never two rows; `"check"` marks
133
+ // a check-sheet attachment, which kind enforcement keeps disjoint from the
134
+ // pointer tags — `"both"` stays pointer-only).
135
+ export const usedByCourseSchema = z.object({
136
+ id: z.number(),
137
+ name: z.string(),
138
+ as: z.enum(["syllabus", "booking_form", "both", "check"]),
139
+ });
140
+ export type UsedByCourse = z.infer<typeof usedByCourseSchema>;
141
+
142
+ export const syllabusSchema = z.object({
143
+ id: z.number(),
144
+ company_id: z.number(),
145
+ name: z.string(),
146
+ description: z.string().nullable().optional(),
147
+ // `regular` | `progress_check` — a CONTENT field like
148
+ // `has_flight_evaluation`: changing it on a published, in-use revision
149
+ // auto-spawns a draft.
150
+ kind: SyllabusKindEnum.default("regular"),
151
+ // Gates whether the per-flight record renders/validates section 2
152
+ // (Gesamtbewertung + the three "Anmerkungen des Fluglehrers" prompts).
153
+ has_flight_evaluation: z.boolean(),
154
+ // Gates whether `ready-to-sign` issues a student countersignature
155
+ // alongside the instructor's — per-syllabus (Amendment 2, replaces the
156
+ // former company-wide toggle).
157
+ requires_student_signature: z.boolean(),
158
+ // ── Versioning (Amendment 2) — always present, flat (no server-side
159
+ // lineage grouping; the frontend groups client-side from these fields). ──
160
+ version: z.number(),
161
+ root_syllabus_id: z.number().nullable(),
162
+ is_draft: z.boolean(),
163
+ // Derived server-side from the same eager-loaded where-used collections as
164
+ // the two `courses_as_*_count` fields below — same availability (`index`
165
+ // AND `show`, absent on `store`/`update`).
166
+ is_in_use: z.boolean().optional(),
167
+ // Transient, request-scoped: present (non-null) only on the response of a
168
+ // mutation that just auto-spawned THIS row as the new draft — never a
169
+ // persisted property. The frontend's signal to toast-explain and
170
+ // `router.replace` onto this syllabus's own page.
171
+ spawned_from_id: z.number().nullable().optional(),
172
+ // Only present when the relation was loaded (`show`/`store`/`update` —
173
+ // never on the `index` list).
174
+ elements: z.array(syllabusElementSchema).optional(),
175
+ // `withCount('elements')` — present on `index` AND `show`; absent on
176
+ // `store`/`update` responses (use `elements.length` there instead, since
177
+ // those always eager-load the full `elements` relation anyway).
178
+ elements_count: z.number().optional(),
179
+ // Where-used — present on `index` AND `show` (both eager-load both
180
+ // `coursesAs*` relations); absent on `store`/`update`.
181
+ courses_as_syllabus_count: z.number().optional(),
182
+ courses_as_booking_form_syllabus_count: z.number().optional(),
183
+ // One deduped entry per distinct course referencing this syllabus via
184
+ // either pointer (`as: "both"` when a course uses it for both). Same
185
+ // availability as the two counts above.
186
+ used_by_courses: z.array(usedByCourseSchema).optional(),
187
+ // Heavier metric, `show` only.
188
+ materialized_enrollment_count: z.number().optional(),
189
+ // Server-evaluated per-instance policy decisions (`HasGatesOnRequest`) —
190
+ // present when the fetch sends `permissions[syllabus]=*`. Loose record on
191
+ // purpose, same rationale as `companySchema.can`.
192
+ can: z.record(z.string(), z.boolean().nullable()).optional(),
193
+ created_at: z.string().optional(),
194
+ updated_at: z.string().optional(),
195
+ });
196
+ export type Syllabus = z.infer<typeof syllabusSchema>;
197
+
198
+ export const StoreSyllabusFormSchema = z.object({
199
+ name: z.string().min(1).max(255),
200
+ description: z.string().nullable().optional(),
201
+ kind: SyllabusKindEnum.optional(),
202
+ has_flight_evaluation: z.boolean().optional(),
203
+ requires_student_signature: z.boolean().optional(),
204
+ });
205
+ export type StoreSyllabusForm = z.infer<typeof StoreSyllabusFormSchema>;
206
+
207
+ export const UpdateSyllabusFormSchema = z.object({
208
+ name: z.string().min(1).max(255).optional(),
209
+ description: z.string().nullable().optional(),
210
+ kind: SyllabusKindEnum.optional(),
211
+ has_flight_evaluation: z.boolean().optional(),
212
+ requires_student_signature: z.boolean().optional(),
213
+ });
214
+ export type UpdateSyllabusForm = z.infer<typeof UpdateSyllabusFormSchema>;
215
+
216
+ // The lean `{id, name, version, kind}` shape `CourseResource` /
217
+ // `BaseCourseResource` serialize for each attached check syllabus
218
+ // (`check_syllabi`, present on course show/update responses).
219
+ export const courseCheckSyllabusSchema = z.object({
220
+ id: z.number(),
221
+ name: z.string(),
222
+ version: z.number(),
223
+ kind: SyllabusKindEnum.default("regular"),
224
+ });
225
+ export type CourseCheckSyllabus = z.infer<typeof courseCheckSyllabusSchema>;
226
+
227
+ // A syllabus-element mutation endpoint's uniform response (attach/update/
228
+ // detach/reorder all return this shape — never a bare 204 — precisely so a
229
+ // spawn can be detected and the frontend can redirect in one round trip).
230
+ export const syllabusElementMutationResultSchema = z.object({
231
+ syllabus: syllabusSchema,
232
+ element: syllabusElementSchema.nullable(),
233
+ spawned_from_id: z.number().nullable(),
234
+ });
235
+ export type SyllabusElementMutationResult = z.infer<
236
+ typeof syllabusElementMutationResultSchema
237
+ >;
238
+
239
+ // `POST syllabi/{syllabus}/rollout` response. `pointer: "check"` rows are
240
+ // `course_check_syllabi` re-attachments (Amendment 3).
241
+ export const syllabusRolloutResultItemSchema = z.object({
242
+ course_id: z.number(),
243
+ course_name: z.string(),
244
+ pointer: z.enum(["syllabus", "booking_form", "check"]),
245
+ });
246
+ // Attachments left on the older revision because the rolled-out revision's
247
+ // kind doesn't fit that attachment type (`RolloutSyllabusRevision`'s kind
248
+ // guard: pointers need regular, the check pivot needs progress_check) — the
249
+ // rollout surface must report these, never silently drop them.
250
+ export const syllabusRolloutSkippedItemSchema =
251
+ syllabusRolloutResultItemSchema.extend({
252
+ reason: z.enum(["kind_mismatch"]),
253
+ });
254
+ export const syllabusRolloutResultSchema = z.object({
255
+ results: z.array(syllabusRolloutResultItemSchema),
256
+ skipped: z.array(syllabusRolloutSkippedItemSchema),
257
+ });
258
+ export type SyllabusRolloutResultItem = z.infer<
259
+ typeof syllabusRolloutResultItemSchema
260
+ >;
261
+ export type SyllabusRolloutSkippedItem = z.infer<
262
+ typeof syllabusRolloutSkippedItemSchema
263
+ >;
264
+ export type SyllabusRolloutResult = z.infer<typeof syllabusRolloutResultSchema>;
265
+
266
+ // ── Enrollment training Übersicht ──
267
+
268
+ export const EnrollmentTrainingStatusEnum = z.enum([
269
+ "not_started",
270
+ "in_training",
271
+ "completed",
272
+ "removed_from_syllabus",
273
+ ]);
274
+ export type EnrollmentTrainingStatus = z.infer<
275
+ typeof EnrollmentTrainingStatusEnum
276
+ >;
277
+
278
+ export const gradeHistoryItemSchema = z.object({
279
+ training_record_id: z.number(),
280
+ booking_id: z.number(),
281
+ date: z.string().nullable().optional(),
282
+ grade_key: z.string().nullable().optional(),
283
+ comment: z.string().nullable().optional(),
284
+ instructor_name: z.string().nullable().optional(),
285
+ });
286
+ export type GradeHistoryItem = z.infer<typeof gradeHistoryItemSchema>;
287
+
288
+ export const enrollmentTrainingElementSchema = z.object({
289
+ id: z.number(),
290
+ course_enrollment_id: z.number(),
291
+ status: EnrollmentTrainingStatusEnum,
292
+ // Denormalized from the course's (Übersicht-driving) syllabus row at
293
+ // materialization time — was `phase` pre-amendment.
294
+ group_label: z.string().nullable().optional(),
295
+ sort_order: z.number(),
296
+ signed_off_at: z.string().nullable(),
297
+ signed_off_by: z
298
+ .object({
299
+ id: z.number(),
300
+ name: z.string().optional(),
301
+ })
302
+ .nullable(),
303
+ notes: z.string().nullable().optional(),
304
+ training_element: trainingElementSchema,
305
+ grade_history: z.array(gradeHistoryItemSchema),
306
+ latest_grade_key: z.string().nullable().optional(),
307
+ });
308
+ export type EnrollmentTrainingElement = z.infer<
309
+ typeof enrollmentTrainingElementSchema
310
+ >;
311
+
312
+ export const SignOffTrainingElementFormSchema = z.object({
313
+ notes: z.string().nullable().optional(),
314
+ signed_off_by_membership_id: z.number().nullable().optional(),
315
+ });
316
+ export type SignOffTrainingElementForm = z.infer<
317
+ typeof SignOffTrainingElementFormSchema
318
+ >;
319
+
320
+ // ── Training records (per-flight) ──
321
+
322
+ export const TrainingRecordStatusEnum = z.enum([
323
+ "draft",
324
+ "awaiting_signatures",
325
+ "signed",
326
+ ]);
327
+ export type TrainingRecordStatus = z.infer<typeof TrainingRecordStatusEnum>;
328
+
329
+ // The pass/fail "Ergebnis" of a progress check — only records stamped with a
330
+ // check-kind sheet may carry it (backend 422s otherwise), and it is required
331
+ // before ready-to-sign on those records.
332
+ export const TrainingRecordResultEnum = z.enum(["passed", "failed"]);
333
+ export type TrainingRecordResult = z.infer<typeof TrainingRecordResultEnum>;
334
+
335
+ export const trainingRecordEntrySchema = z.object({
336
+ id: z.number(),
337
+ training_element_id: z.number(),
338
+ // Embedded so a signed record (immutable evidence) still renders fully
339
+ // even if the element is later detached from the syllabus —
340
+ // callers must prefer this over resolving `training_element_id` against
341
+ // the (mutable, current) syllabus. `.nullable().optional()`: the backend
342
+ // omits the key entirely (via `whenLoaded`) on any response that didn't
343
+ // eager-load the relation, and could resolve it to `null` in a genuinely
344
+ // orphaned edge case — both degrade to "treat as unresolved", not a
345
+ // parse failure.
346
+ training_element: trainingElementSchema.nullable().optional(),
347
+ grade_key: z.string().nullable().optional(),
348
+ comment: z.string().nullable().optional(),
349
+ });
350
+ export type TrainingRecordEntry = z.infer<typeof trainingRecordEntrySchema>;
351
+
352
+ export const trainingRecordEntryInputSchema = z.object({
353
+ training_element_id: z.number(),
354
+ grade_key: z.string().nullable().optional(),
355
+ comment: z.string().nullable().optional(),
356
+ });
357
+ export type TrainingRecordEntryInput = z.infer<
358
+ typeof trainingRecordEntryInputSchema
359
+ >;
360
+
361
+ // Mirrors the generic `SignatureResource` (App\Http\Resources\Form) — the
362
+ // same polymorphic `Signature` model backs both form submissions and
363
+ // training records. Kept self-contained here (not imported from
364
+ // @schemas/FormTemplateSchema) to match this file's own alias root and the
365
+ // courseMilestone.ts convention of no cross-schema imports.
366
+ export const trainingSignatureSchema = z.object({
367
+ type: z.literal("signature").optional(),
368
+ id: z.number(),
369
+ signable_type: z.string().optional(),
370
+ signable_id: z.number().optional(),
371
+ field_key: z.string(),
372
+ status: z.enum(["pending", "signed", "revoked"]),
373
+ required_signer_membership_id: z.number().nullable(),
374
+ required_signer_name: z.string().nullable().optional(),
375
+ required_signer_permission: z.string().nullable().optional(),
376
+ signed_by_user_id: z.number().nullable(),
377
+ signed_by_membership_id: z.number().nullable(),
378
+ signed_by_name: z.string().nullable().optional(),
379
+ is_current_user_designated: z.boolean().optional().default(false),
380
+ signed_at: z.string().nullable(),
381
+ image: z.string().nullable(),
382
+ data_hash: z.string().nullable(),
383
+ schema_version: z.number().nullable(),
384
+ schema_hash: z.string().nullable(),
385
+ consent_text_hash: z.string().nullable(),
386
+ auth_method: z.string().nullable(),
387
+ auth_verified_at: z.string().nullable(),
388
+ ip_address: z.string().nullable(),
389
+ user_agent: z.string().nullable(),
390
+ geo_country: z.string().nullable(),
391
+ signing_event_id: z.string().nullable(),
392
+ created_at: z.string().optional(),
393
+ updated_at: z.string().optional(),
394
+ });
395
+ export type TrainingSignature = z.infer<typeof trainingSignatureSchema>;
396
+
397
+ export const trainingRecordSchema = z.object({
398
+ id: z.number(),
399
+ booking_id: z.number(),
400
+ course_enrollment_id: z.number(),
401
+ instructor_membership_id: z.number().nullable().optional(),
402
+ status: TrainingRecordStatusEnum,
403
+ // Whether section 2 (Gesamtbewertung + Anmerkungen) applies at all — read
404
+ // live off the course's booking-form syllabus by
405
+ // `TrainingRecordResource`, the single source of truth. `false` (never
406
+ // absent) when there's no booking-form syllabus at all.
407
+ has_flight_evaluation: z.boolean().default(false),
408
+ // The exact booking-form syllabus revision stamped at CREATE time (never
409
+ // on a later update) — the audit-chain provenance ("Rev. n this session
410
+ // was graded against"). `syllabus` is `null` when the course had no
411
+ // booking-form syllabus assigned at creation time.
412
+ syllabus_id: z.number().nullable().optional(),
413
+ // `kind` is the frontend's signal to render the check-only "Ergebnis"
414
+ // section for this record.
415
+ syllabus: z
416
+ .object({
417
+ id: z.number(),
418
+ name: z.string(),
419
+ version: z.number(),
420
+ kind: SyllabusKindEnum.default("regular"),
421
+ })
422
+ .nullable()
423
+ .optional(),
424
+ // Non-null only on check-kind records (see TrainingRecordResultEnum).
425
+ result: TrainingRecordResultEnum.nullable().optional(),
426
+ // The J3-mirroring fields — the Gesamtbewertung (a point key from the
427
+ // booking-form syllabus's element grading scales) plus the three
428
+ // "Anmerkungen des Fluglehrers" comment sections from the paper form.
429
+ overall_grade_key: z.string().nullable().optional(),
430
+ what_went_well: z.string().nullable().optional(),
431
+ needs_improvement: z.string().nullable().optional(),
432
+ repeat_next_flight: z.string().nullable().optional(),
433
+ entries: z.array(trainingRecordEntrySchema),
434
+ signatures: z.array(trainingSignatureSchema),
435
+ created_at: z.string().optional(),
436
+ updated_at: z.string().optional(),
437
+ });
438
+ export type TrainingRecord = z.infer<typeof trainingRecordSchema>;
439
+
440
+ export const UpsertTrainingRecordFormSchema = z.object({
441
+ // CREATE only — which sheet this record fills (the course's booking-form
442
+ // syllabus when omitted, or one of its attached check syllabi). The choice
443
+ // IS the stamp; PATCH prohibits this key entirely (the stamp never moves).
444
+ syllabus_id: z.number().nullable().optional(),
445
+ // Check-kind sheets only — 422 on any other sheet.
446
+ result: TrainingRecordResultEnum.nullable().optional(),
447
+ overall_grade_key: z.string().nullable().optional(),
448
+ what_went_well: z.string().nullable().optional(),
449
+ needs_improvement: z.string().nullable().optional(),
450
+ repeat_next_flight: z.string().nullable().optional(),
451
+ instructor_membership_id: z.number().nullable().optional(),
452
+ entries: z.array(trainingRecordEntryInputSchema),
453
+ });
454
+ export type UpsertTrainingRecordForm = z.infer<
455
+ typeof UpsertTrainingRecordFormSchema
456
+ >;
457
+
458
+ // ── Training template packs (syllabus templates) ──
459
+
460
+ export const trainingTemplatePackSchema = z.object({
461
+ key: z.string(),
462
+ name: z.string(),
463
+ description: z.string().nullable().optional(),
464
+ // Carried onto the syllabus the pack creates (regular vs. progress check).
465
+ kind: SyllabusKindEnum.default("regular"),
466
+ element_count: z.number().optional(),
467
+ });
468
+ export type TrainingTemplatePack = z.infer<typeof trainingTemplatePackSchema>;
469
+
470
+ export const CreateSyllabusFromTemplateFormSchema = z.object({
471
+ template_key: z.string().min(1),
472
+ });
473
+ export type CreateSyllabusFromTemplateForm = z.infer<
474
+ typeof CreateSyllabusFromTemplateFormSchema
475
+ >;
476
+
477
+ // Mirrors `CreateSyllabusFromTemplate::run()`'s return shape (m-crit-api),
478
+ // as wrapped by `SyllabusController::fromTemplate`.
479
+ export const createSyllabusFromTemplateResultSchema = z.object({
480
+ catalog: z.object({
481
+ created: z.number(),
482
+ existing: z.number(),
483
+ adopted: z.number(),
484
+ mismatched: z.array(
485
+ z.object({
486
+ code: z.string(),
487
+ company_name: z.string(),
488
+ template_name: z.string(),
489
+ }),
490
+ ),
491
+ }),
492
+ elements: z.object({
493
+ created: z.number(),
494
+ existing: z.number(),
495
+ }),
496
+ syllabus: syllabusSchema,
497
+ });
498
+ export type CreateSyllabusFromTemplateResult = z.infer<
499
+ typeof createSyllabusFromTemplateResultSchema
500
+ >;
@@ -0,0 +1,85 @@
1
+ // AI assistant suggestion shapes. Suggestions are reviewable drafts — the
2
+ // backend stores them as AiSuggestion rows; applying one only fills the local
3
+ // edit form (or materializes through the subject model's normal endpoint).
4
+
5
+ export type AiSuggestionStatus =
6
+ | "pending"
7
+ | "ready"
8
+ | "applied"
9
+ | "dismissed"
10
+ | "failed";
11
+
12
+ export interface AiSuggestion<TPayload = unknown> {
13
+ type: "ai_suggestion";
14
+ id: number;
15
+ subject_type: string;
16
+ subject_id: number;
17
+ company_id: number | null;
18
+ suggestion_type: string;
19
+ locale: string;
20
+ payload: TPayload;
21
+ meta?: unknown;
22
+ status: AiSuggestionStatus;
23
+ requested_by: number | null;
24
+ applied_at: string | null;
25
+ dismissed_at: string | null;
26
+ created_at: string;
27
+ }
28
+
29
+ // Course description assistant.
30
+
31
+ export interface AiSuggestedMilestone {
32
+ name: string;
33
+ description?: string | null;
34
+ kind: "progress" | "prerequisite" | "solo" | "cross_country";
35
+ is_prerequisite: boolean;
36
+ sort_order: number;
37
+ }
38
+
39
+ export interface CourseDescriptionSuggestionPayload {
40
+ title: string;
41
+ subtitle: string;
42
+ summary: string;
43
+ html: string;
44
+ milestones: AiSuggestedMilestone[];
45
+ }
46
+
47
+ // MIA logbook import (booking-scoped flight-log suggestions).
48
+
49
+ export interface LogbookFlightLogPayload {
50
+ etdZ: string | null;
51
+ etaZ: string | null;
52
+ departure_airport_id: number | null;
53
+ destination_airport_id: number | null;
54
+ departure_airport_icao: string | null;
55
+ destination_airport_icao: string | null;
56
+ full_stop_landings: number;
57
+ touch_and_gos: number;
58
+ low_approaches: number;
59
+ crew_assignments: { membership_id: number; role: string }[];
60
+ membership_id: number | null;
61
+ }
62
+
63
+ export interface LogbookSuggestionMeta {
64
+ flags?: string[];
65
+ match?: { confidence: string; reasoning: string };
66
+ extraction?: { valid: boolean | null; discrepancy_minutes: number | null };
67
+ crew_text?: string | null;
68
+ }
69
+
70
+ export type LogbookSuggestion = AiSuggestion<LogbookFlightLogPayload> & {
71
+ meta?: LogbookSuggestionMeta;
72
+ };
73
+
74
+ export interface DiscardedRow {
75
+ reason: string;
76
+ flight: Record<string, unknown>;
77
+ }
78
+
79
+ export interface LogbookStageResult {
80
+ booking_id: number;
81
+ suggestion_ids: number[];
82
+ staged: number;
83
+ routed: Record<string, number> | number[];
84
+ discarded: DiscardedRow[];
85
+ }
@@ -0,0 +1,23 @@
1
+ export type AircraftBlockReason =
2
+ | "general"
3
+ | "ferry"
4
+ | "weather"
5
+ | "private"
6
+ | "aog"
7
+ | "other";
8
+
9
+ export interface AircraftBlock {
10
+ type: "aircraftBlock";
11
+ id: number;
12
+ airplane_id: number;
13
+ company_id: number;
14
+ starts_at: string; // UTC ISO
15
+ ends_at: string; // UTC ISO
16
+ reason: AircraftBlockReason;
17
+ note: string | null;
18
+ is_maintenance: boolean;
19
+ maintenance_event_id: number | null;
20
+ created_at: string;
21
+ updated_at: string;
22
+ can?: Record<string, boolean | null>;
23
+ }
package/src/types/api.ts CHANGED
@@ -8,7 +8,9 @@ export interface DataWrappedResponse<T, M = undefined> {
8
8
  meta?: M;
9
9
  }
10
10
 
11
- export interface PaginatedResponse<T = any> {
11
+ export type ApiResponse<T> = DataWrappedResponse<T>;
12
+
13
+ export interface PaginatedResponse<T = unknown> {
12
14
  data: T[];
13
15
  meta: {
14
16
  current_page: number;
@@ -0,0 +1,28 @@
1
+ // Generic background-task tracking. A long-running server action returns a
2
+ // BackgroundTask immediately (202); the client then tracks it to completion
3
+ // via the Echo `background_task` signal (push) with a polling fallback.
4
+
5
+ export type BackgroundTaskStatus =
6
+ | "queued"
7
+ | "processing"
8
+ | "completed"
9
+ | "failed"
10
+ | "canceled";
11
+
12
+ export interface BackgroundTask<TResult = unknown> {
13
+ type: "background_task";
14
+ /** Store key — mirrors `uuid` (the API has no client-facing integer id). */
15
+ id: string;
16
+ uuid: string;
17
+ task_type: string;
18
+ status: BackgroundTaskStatus;
19
+ progress: number | null;
20
+ progress_label: string | null;
21
+ company_id: number | null;
22
+ user_id: number | null;
23
+ result: TResult | null;
24
+ error: string | null;
25
+ started_at: string | null;
26
+ finished_at: string | null;
27
+ created_at: string | null;
28
+ }