@updraft-solutions/mcrit-sdk 0.1.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 (79) hide show
  1. package/README.md +240 -0
  2. package/dist/chunk-2DRKSYW4.cjs +99 -0
  3. package/dist/chunk-2M32EOYI.js +145 -0
  4. package/dist/chunk-AN55SAGU.js +1189 -0
  5. package/dist/chunk-D5DUVW34.js +176 -0
  6. package/dist/chunk-HQSF3WGF.cjs +14 -0
  7. package/dist/chunk-JGIAOBZI.js +99 -0
  8. package/dist/chunk-JRVRIKNM.cjs +168 -0
  9. package/dist/chunk-LVTDNDWE.cjs +124 -0
  10. package/dist/chunk-LXNCAKJZ.js +0 -0
  11. package/dist/chunk-MF7G76QS.cjs +176 -0
  12. package/dist/chunk-MI4YBENQ.cjs +145 -0
  13. package/dist/chunk-MOOGM3DW.cjs +1 -0
  14. package/dist/chunk-NQV2D3FJ.js +14 -0
  15. package/dist/chunk-TJXWL42D.js +124 -0
  16. package/dist/chunk-VYT5KQWF.js +168 -0
  17. package/dist/chunk-XKZQ5W7E.cjs +1189 -0
  18. package/dist/courses/index.cjs +7 -0
  19. package/dist/courses/index.d.cts +58 -0
  20. package/dist/courses/index.d.ts +58 -0
  21. package/dist/courses/index.js +7 -0
  22. package/dist/crew/index.cjs +10 -0
  23. package/dist/crew/index.d.cts +102 -0
  24. package/dist/crew/index.d.ts +102 -0
  25. package/dist/crew/index.js +10 -0
  26. package/dist/crew/vue.cjs +34 -0
  27. package/dist/crew/vue.d.cts +31 -0
  28. package/dist/crew/vue.d.ts +31 -0
  29. package/dist/crew/vue.js +34 -0
  30. package/dist/format/index.cjs +44 -0
  31. package/dist/format/index.d.cts +69 -0
  32. package/dist/format/index.d.ts +69 -0
  33. package/dist/format/index.js +44 -0
  34. package/dist/index.cjs +261 -0
  35. package/dist/index.d.cts +11 -0
  36. package/dist/index.d.ts +11 -0
  37. package/dist/index.js +261 -0
  38. package/dist/media/index.cjs +30 -0
  39. package/dist/media/index.d.cts +89 -0
  40. package/dist/media/index.d.ts +89 -0
  41. package/dist/media/index.js +30 -0
  42. package/dist/models-CY3MmvCg.d.cts +33801 -0
  43. package/dist/models-CY3MmvCg.d.ts +33801 -0
  44. package/dist/money/index.cjs +18 -0
  45. package/dist/money/index.d.cts +66 -0
  46. package/dist/money/index.d.ts +66 -0
  47. package/dist/money/index.js +18 -0
  48. package/dist/pagination/index.cjs +6 -0
  49. package/dist/pagination/index.d.cts +16 -0
  50. package/dist/pagination/index.d.ts +16 -0
  51. package/dist/pagination/index.js +6 -0
  52. package/dist/schemas/index.cjs +158 -0
  53. package/dist/schemas/index.d.cts +1019 -0
  54. package/dist/schemas/index.d.ts +1019 -0
  55. package/dist/schemas/index.js +158 -0
  56. package/dist/types/index.cjs +1 -0
  57. package/dist/types/index.d.cts +143 -0
  58. package/dist/types/index.d.ts +143 -0
  59. package/dist/types/index.js +1 -0
  60. package/package.json +171 -0
  61. package/src/courses/index.ts +202 -0
  62. package/src/crew/index.ts +255 -0
  63. package/src/crew/vue.ts +70 -0
  64. package/src/format/index.ts +265 -0
  65. package/src/index.ts +76 -0
  66. package/src/media/index.ts +224 -0
  67. package/src/money/index.ts +168 -0
  68. package/src/pagination/index.ts +27 -0
  69. package/src/schemas/common.ts +73 -0
  70. package/src/schemas/faq.ts +94 -0
  71. package/src/schemas/fee.ts +37 -0
  72. package/src/schemas/index.ts +6 -0
  73. package/src/schemas/models.ts +996 -0
  74. package/src/schemas/report.ts +306 -0
  75. package/src/schemas/safety-report.ts +163 -0
  76. package/src/types/api.ts +37 -0
  77. package/src/types/compliance.ts +44 -0
  78. package/src/types/index.ts +3 -0
  79. package/src/types/models.ts +124 -0
@@ -0,0 +1,168 @@
1
+ import Dinero from "dinero.js";
2
+ import type { Dinero as DineroType } from "dinero.js";
3
+ import { filter, head, sortBy } from "lodash-es";
4
+
5
+ Dinero.globalLocale = "de-DE";
6
+
7
+ export type NetGross = "net" | "gross";
8
+
9
+ export interface PhpMoneyObject {
10
+ amount?: number;
11
+ currency?: string;
12
+ formatted?: string;
13
+ }
14
+
15
+ /**
16
+ * Permissive input shape for {@link parsePrice}. Mirrors the API
17
+ * `Price` resource but every field is optional so callers can pass
18
+ * partially-loaded data without casting.
19
+ */
20
+ export interface PriceLike {
21
+ id?: number;
22
+ net_total?: PhpMoneyObject;
23
+ gross_total?: PhpMoneyObject;
24
+ tax_percentage?: number;
25
+ valid_from?: string;
26
+ input_value?: number;
27
+ net_gross?: NetGross;
28
+ }
29
+
30
+ /**
31
+ * Permissive input shape for {@link parseFee}. Mirrors the API `Fee`
32
+ * resource — accepts both the marketing-site shape and the SPA shape.
33
+ */
34
+ export interface FeeLike {
35
+ id?: number;
36
+ price?: PriceLike | null;
37
+ prices?: PriceLike[] | null;
38
+ title?: string;
39
+ ident?: string;
40
+ [key: string]: unknown;
41
+ }
42
+
43
+ export interface ParsedPrice extends Omit<PriceLike, "net_total" | "gross_total"> {
44
+ net_total: DineroType;
45
+ gross_total: DineroType;
46
+ }
47
+
48
+ export interface ParsedFee {
49
+ currentPrice(netGross?: NetGross | null): DineroType | ParsedPrice | null;
50
+ upcomingPrice(netGross?: NetGross | null): DineroType | ParsedPrice | null;
51
+ }
52
+
53
+ interface MoneyShape {
54
+ amount?: number;
55
+ value?: { amount?: number };
56
+ }
57
+
58
+ type MoneyInput = number | string | MoneyShape | PhpMoneyObject | null | undefined;
59
+
60
+ export function money(amount: MoneyInput): DineroType {
61
+ try {
62
+ let parsed: unknown = amount;
63
+
64
+ if (parsed && typeof parsed === "object") {
65
+ const obj = parsed as MoneyShape;
66
+ if (obj.value && typeof obj.value === "object" && typeof obj.value.amount !== "undefined") {
67
+ parsed = obj.value.amount;
68
+ } else if (typeof obj.amount !== "undefined") {
69
+ parsed = obj.amount;
70
+ }
71
+ }
72
+
73
+ const int = parseInt(String(parsed ?? "0"), 10);
74
+ if (Number.isNaN(int)) {
75
+ throw new Error("Invalid amount");
76
+ }
77
+
78
+ return Dinero({ amount: int, currency: "EUR" });
79
+ } catch (e) {
80
+ // eslint-disable-next-line no-console
81
+ console.error("[mcrit-sdk/money] failed to parse amount:", amount, e);
82
+ return Dinero({ amount: 0, currency: "EUR" });
83
+ }
84
+ }
85
+
86
+ export function moneyFormatted(dinero: DineroType | null | undefined): string {
87
+ return dinero ? dinero.toFormat() : "";
88
+ }
89
+
90
+ export function parsePrice(price: PriceLike | null | undefined): ParsedPrice | null {
91
+ if (!price) {
92
+ return null;
93
+ }
94
+ return {
95
+ ...price,
96
+ net_total: money(price.net_total?.amount ?? 0),
97
+ gross_total: money(price.gross_total?.amount ?? 0),
98
+ };
99
+ }
100
+
101
+ function priceFor(parsed: ParsedPrice, netGross: NetGross | null): DineroType | ParsedPrice {
102
+ if (netGross === "net") {
103
+ return parsed.net_total;
104
+ }
105
+ if (netGross === "gross") {
106
+ return parsed.gross_total;
107
+ }
108
+ return parsed;
109
+ }
110
+
111
+ export function parseFee(fee: FeeLike | null | undefined): ParsedFee | null {
112
+ if (!fee) {
113
+ return null;
114
+ }
115
+ return {
116
+ currentPrice(netGross: NetGross | null = null) {
117
+ const parsed = parsePrice(fee.price ?? null);
118
+ return parsed ? priceFor(parsed, netGross) : null;
119
+ },
120
+
121
+ upcomingPrice(netGross: NetGross | null = null) {
122
+ const now = Date.now();
123
+ const upcoming = head(
124
+ sortBy(
125
+ filter(fee.prices ?? [], (p) => {
126
+ const t = p.valid_from ? Date.parse(p.valid_from) : NaN;
127
+ return Number.isFinite(t) && t > now;
128
+ }),
129
+ (p) => Date.parse(p.valid_from ?? ""),
130
+ ),
131
+ );
132
+ if (!upcoming) {
133
+ return null;
134
+ }
135
+ const parsed = parsePrice(upcoming);
136
+ return parsed ? priceFor(parsed, netGross) : null;
137
+ },
138
+ };
139
+ }
140
+
141
+ export function displayFee(
142
+ fee: FeeLike | null | undefined,
143
+ netGross: NetGross = "gross",
144
+ fallback = "Keine Gebühr hinterlegt",
145
+ ): string {
146
+ const current = parseFee(fee)?.currentPrice(netGross);
147
+ if (current && "toFormat" in current) {
148
+ return current.toFormat();
149
+ }
150
+ return fallback;
151
+ }
152
+
153
+ /**
154
+ * Strict-numeric alias of {@link money} for callers that already have
155
+ * a clean integer cents amount. Equivalent to `Dinero({amount, currency: "EUR"})`.
156
+ */
157
+ export function fromCents(amount: number): DineroType {
158
+ return Dinero({ amount, currency: "EUR" });
159
+ }
160
+
161
+ /**
162
+ * Formats an integer cents amount as a German Euro string
163
+ * (`"€ 1.234,56"`). Tolerates null/undefined/string input.
164
+ */
165
+ export function formatToEuroString(amount: number | string | null | undefined): string {
166
+ const cents = parseInt(String(amount ?? "0"), 10);
167
+ return `€ ${fromCents(Number.isNaN(cents) ? 0 : cents).setLocale("de-DE").toFormat("0,0.00")}`;
168
+ }
@@ -0,0 +1,27 @@
1
+ export interface PaginationOptions {
2
+ page: number;
3
+ itemsPerPage: number;
4
+ }
5
+
6
+ export interface PaginationMeta {
7
+ start: number;
8
+ end: number;
9
+ total: number;
10
+ }
11
+
12
+ /**
13
+ * Compute the `start`/`end`/`total` window for a paginated UI footer.
14
+ * Returns `start: 0` when `total` is zero so empty states render cleanly.
15
+ */
16
+ export function paginationMeta(
17
+ options: PaginationOptions,
18
+ total: number,
19
+ ): PaginationMeta {
20
+ const start = (options.page - 1) * options.itemsPerPage + 1;
21
+ const end = Math.min(options.page * options.itemsPerPage, total);
22
+ return {
23
+ start: total === 0 ? 0 : start,
24
+ end,
25
+ total,
26
+ };
27
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from "zod";
2
+
3
+ // Shared helpers
4
+ export const zNullableBool = z.boolean().nullable().optional();
5
+ export const zNetGross = z.enum(["net", "gross"]).optional();
6
+ export const zContactDTO = z.object({
7
+ name: z.string().optional(),
8
+ email: z.string().email().optional(),
9
+ phone: z.string().optional(),
10
+ });
11
+ export const zPhpMoneyObject = z.object({
12
+ amount: z.number(),
13
+ currency: z.string(),
14
+ formatted: z.string(),
15
+ });
16
+
17
+ // Contact attribute schema (from Models/common.ts)
18
+ export const contactAttributeSchema = z.object({
19
+ name: z.string().optional(),
20
+ email: z.string().optional(),
21
+ phone: z.string().optional(),
22
+ company: z.string().optional(),
23
+ fax: z.string().optional(),
24
+ });
25
+
26
+ // Base model
27
+ export const BaseModel = z.object({
28
+ type: z.string(),
29
+ id: z.number(),
30
+ created_at: z.string(),
31
+ updated_at: z.string(),
32
+ });
33
+
34
+ // Media
35
+ export const mediaSchema = z.object({
36
+ type: z.literal("media"),
37
+ id: z.number(),
38
+ description: z.string().optional(),
39
+ collection_name: z.string().optional(),
40
+ conversions: z.record(z.string().url()).optional(),
41
+ created_at: z.string().optional(),
42
+ updated_at: z.string().optional(),
43
+ url: z.string().optional(),
44
+ mime_type: z.string().optional(),
45
+ file_name: z.string().optional(),
46
+ size: z.number().optional(),
47
+ });
48
+
49
+ export const mediaCollection = z
50
+ .object({
51
+ avatar: mediaSchema.nullable().optional(),
52
+ header: mediaSchema.nullable().optional(),
53
+ images: z.array(mediaSchema).nullable().optional(),
54
+ documents: z.array(mediaSchema).nullable().optional(),
55
+ })
56
+ .nullable()
57
+ .optional();
58
+
59
+ // User & Company
60
+ export const userSchema = z.object({
61
+ type: z.literal("user"),
62
+ id: z.number(),
63
+ name: z.string(),
64
+ email: z.string().optional(),
65
+ mobile: z.string().optional(),
66
+ });
67
+
68
+ export const companySchema = z.object({
69
+ type: z.literal("company"),
70
+ id: z.number(),
71
+ name: z.string(),
72
+ media: mediaCollection,
73
+ });
@@ -0,0 +1,94 @@
1
+ import { z } from "zod";
2
+
3
+ // FAQ Status enum - matches backend constants
4
+ export enum FaqStatus {
5
+ PENDING = "pending",
6
+ APPROVED = "approved",
7
+ REJECTED = "rejected",
8
+ }
9
+
10
+ // User schema for submitter/reviewer
11
+ const UserBasicSchema = z
12
+ .object({
13
+ id: z.number(),
14
+ name: z.string(),
15
+ email: z.string().email(),
16
+ })
17
+ .nullable();
18
+
19
+ // FAQ schema - matches backend FaqResource with user submission support
20
+ export const FaqSchema = z.object({
21
+ type: z.literal("faq"),
22
+ id: z.number(),
23
+ company_id: z.number().nullable(),
24
+ faqable_type: z.string().nullable(),
25
+ faqable_id: z.number().nullable(),
26
+ category: z.string(),
27
+ subcategory: z.string().nullable(),
28
+ question: z.string(),
29
+ answer: z.string().nullable(),
30
+ order: z.number(),
31
+ is_active: z.boolean(),
32
+ status: z.enum(["pending", "approved", "rejected"]),
33
+ submitted_by: z.number().nullable(),
34
+ reviewed_by: z.number().nullable(),
35
+ reviewed_at: z.string().nullable(),
36
+ created_at: z.string(),
37
+ updated_at: z.string(),
38
+ submitter: UserBasicSchema.optional(),
39
+ reviewer: UserBasicSchema.optional(),
40
+ is_pending: z.boolean().optional(),
41
+ is_user_submitted: z.boolean().optional(),
42
+ can: z
43
+ .object({
44
+ view: z.boolean().optional(),
45
+ update: z.boolean().optional(),
46
+ delete: z.boolean().optional(),
47
+ review: z.boolean().optional(),
48
+ submit: z.boolean().optional(),
49
+ })
50
+ .optional(),
51
+ });
52
+
53
+ // Type exports
54
+ export type Faq = z.infer<typeof FaqSchema>;
55
+
56
+ // Input types for API calls
57
+ export interface CreateFaqInput {
58
+ category: string;
59
+ subcategory?: string | null;
60
+ question: string;
61
+ answer: string;
62
+ order?: number;
63
+ is_active?: boolean;
64
+ }
65
+
66
+ export interface UpdateFaqInput {
67
+ category?: string;
68
+ subcategory?: string | null;
69
+ question?: string;
70
+ answer?: string;
71
+ order?: number;
72
+ is_active?: boolean;
73
+ }
74
+
75
+ // User submission schema - matches SubmitFaqQuestionRequest
76
+ export const SubmitFaqQuestionSchema = z.object({
77
+ category: z.string().min(1, "Category is required"),
78
+ subcategory: z.string().nullable().optional(),
79
+ question: z.string().min(10, "Question must be at least 10 characters"),
80
+ });
81
+
82
+ export type SubmitFaqQuestionInput = z.infer<typeof SubmitFaqQuestionSchema>;
83
+
84
+ // Review schema - matches ReviewFaqRequest
85
+ export const ReviewFaqSchema = z.object({
86
+ status: z.enum(["pending", "approved", "rejected"]),
87
+ answer: z.string().optional(),
88
+ category: z.string().optional(),
89
+ subcategory: z.string().nullable().optional(),
90
+ order: z.number().optional(),
91
+ is_active: z.boolean().optional(),
92
+ });
93
+
94
+ export type ReviewFaqInput = z.infer<typeof ReviewFaqSchema>;
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+
3
+ export const FeeSchema = z.object({
4
+ id: z.number(),
5
+ type: z.literal("fee").optional(),
6
+ company_id: z.number(),
7
+ provider: z.string().nullable().optional(),
8
+ category: z.string(),
9
+ subcategory: z.string().nullable().optional(),
10
+ title: z.string(),
11
+ internal_title: z.string().nullable().optional(),
12
+ display_title: z.string().optional(),
13
+ relation: z
14
+ .array(z.enum(["booking", "flightlog", "invoice", "instructor"]))
15
+ .nullable()
16
+ .optional(),
17
+ payment_per: z
18
+ .enum(["perSession", "perFlight", "perBooking", "perHour"])
19
+ .nullable()
20
+ .optional(),
21
+ price: z.any().optional(),
22
+ prices: z.array(z.any()).optional(),
23
+ can: z
24
+ .object({
25
+ update: z.boolean().optional(),
26
+ delete: z.boolean().optional(),
27
+ })
28
+ .optional(),
29
+ });
30
+
31
+ export const FeeCategoryOptionsSchema = z.object({
32
+ categories: z.array(z.string()),
33
+ subcategories: z.record(z.string(), z.array(z.string())),
34
+ });
35
+
36
+ export type Fee = z.infer<typeof FeeSchema>;
37
+ export type FeeCategoryOptions = z.infer<typeof FeeCategoryOptionsSchema>;
@@ -0,0 +1,6 @@
1
+ export * from "./common";
2
+ export * from "./models";
3
+ export * from "./faq";
4
+ export * from "./fee";
5
+ export * from "./safety-report";
6
+ export * from "./report";