@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,265 @@
1
+ import moment from "moment-timezone";
2
+
3
+ const DEFAULT_TZ = "UTC";
4
+
5
+ /**
6
+ * Note on German locale: the SDK does not eagerly load `moment/locale/de`.
7
+ * Consumers that want German formatting should import it once at their
8
+ * app entry: `import "moment/locale/de"`. Otherwise `timeUntil` and other
9
+ * locale-dependent functions will fall back to English.
10
+ */
11
+
12
+ /** Predefined moment format strings, frozen so callers can reference them safely. */
13
+ export const DATE_FORMATS = Object.freeze({
14
+ DATE: "DD.MM.YYYY",
15
+ TIME: "HH:mm",
16
+ TIME_TZ: "HH:mm z",
17
+ DATETIME: "DD.MM.YYYY HH:mm",
18
+ DATETIME_TZ: "DD.MM.YYYY HH:mm z",
19
+ YEAR: "YYYY",
20
+ DAY: "DD",
21
+ MONTH_SHORT: "MMM",
22
+ } as const);
23
+
24
+ export const TIME_FORMATS = Object.freeze({
25
+ TIME: "HH:mm",
26
+ TIME_WITH_SECONDS: "HH:mm:ss",
27
+ } as const);
28
+
29
+ export type DateFormat = (typeof DATE_FORMATS)[keyof typeof DATE_FORMATS];
30
+ export type TimeFormat = (typeof TIME_FORMATS)[keyof typeof TIME_FORMATS];
31
+
32
+ function isValidDate(date: unknown): boolean {
33
+ return moment(date as moment.MomentInput).isValid();
34
+ }
35
+
36
+ function isValidDateStrict(date: unknown): boolean {
37
+ return (
38
+ typeof date === "string" &&
39
+ /^\d{4}-\d{2}-\d{2}$/.test(date) &&
40
+ moment(date, "YYYY-MM-DD", true).isValid()
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Format a strict `YYYY-MM-DD` date string with the given moment format.
46
+ * Returns `""` for invalid input.
47
+ */
48
+ export function formatDate(
49
+ date: string | null | undefined,
50
+ format: string = DATE_FORMATS.DATE,
51
+ locale: string = "de",
52
+ ): string {
53
+ if (!isValidDateStrict(date)) {
54
+ return "";
55
+ }
56
+ return moment(date as string, "YYYY-MM-DD").locale(locale).format(format);
57
+ }
58
+
59
+ /**
60
+ * Normalize an `HH:mm[:ss]`-ish string into a chosen format. Pads
61
+ * components and clamps each to its valid range.
62
+ */
63
+ export function formatTime(
64
+ time: string | null | undefined,
65
+ format: string = TIME_FORMATS.TIME,
66
+ ): string {
67
+ if (typeof time !== "string" || !time) {
68
+ return "";
69
+ }
70
+
71
+ const parts = time
72
+ .trim()
73
+ .split(/[:.\s]/)
74
+ .map(Number)
75
+ .filter((n) => !Number.isNaN(n));
76
+
77
+ const [hh = 0, mm = 0, ss = 0] = parts;
78
+ const safeHH = Math.min(Math.max(hh, 0), 23);
79
+ const safeMM = Math.min(Math.max(mm, 0), 59);
80
+ const safeSS = Math.min(Math.max(ss, 0), 59);
81
+ const pad = (n: number) => n.toString().padStart(2, "0");
82
+
83
+ if (format === TIME_FORMATS.TIME) {
84
+ return `${pad(safeHH)}:${pad(safeMM)}`;
85
+ }
86
+ return `${pad(safeHH)}:${pad(safeMM)}:${pad(safeSS)}`;
87
+ }
88
+
89
+ /**
90
+ * Format an ISO date-time string or Date in a specific timezone+locale.
91
+ * Returns `""` for invalid input.
92
+ */
93
+ export function formatDateTime(
94
+ dateTime: string | Date | null | undefined,
95
+ timezone: string = DEFAULT_TZ,
96
+ format: string = DATE_FORMATS.DATETIME,
97
+ locale: string = "de",
98
+ ): string {
99
+ if (!dateTime || !isValidDate(dateTime)) {
100
+ return "";
101
+ }
102
+ return moment(dateTime).locale(locale).tz(timezone).format(format);
103
+ }
104
+
105
+ export function bytesToHuman(bytes: number | null | undefined): string {
106
+ if (!bytes || bytes === 0) {
107
+ return "0 Bytes";
108
+ }
109
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
110
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
111
+ return `${parseFloat((bytes / 2 ** (i * 10)).toFixed(2))} ${sizes[i]}`;
112
+ }
113
+
114
+ export function monthString(date: string | Date | null | undefined): string {
115
+ return date ? moment(date).format("MMMM YYYY") : "";
116
+ }
117
+
118
+ export function getMonthFromDate(date: string | Date | null | undefined): string {
119
+ return date ? moment(date).format("MMM") : "";
120
+ }
121
+
122
+ export function yearString(
123
+ date: string | Date | null | undefined,
124
+ timezone: string = DEFAULT_TZ,
125
+ ): string {
126
+ return date ? moment(date).tz(timezone).format("YYYY") : "";
127
+ }
128
+
129
+ export function percentage(value: number | string | null | undefined): string {
130
+ if (value === null || value === undefined) {
131
+ return "-";
132
+ }
133
+ return `${parseFloat(String(value)).toFixed(2)}%`;
134
+ }
135
+
136
+ export interface MembershipStatusBadge {
137
+ text: string;
138
+ color: string;
139
+ }
140
+
141
+ export function membershipStatus(value: string | null | undefined): MembershipStatusBadge {
142
+ switch (value?.toLowerCase()) {
143
+ case "confirmed":
144
+ return { text: "Aktiv", color: "success" };
145
+ case "denied":
146
+ return { text: "Inaktiv", color: "error" };
147
+ case "pending":
148
+ return { text: "In Bearbeitung", color: "warning" };
149
+ default:
150
+ return { text: "Unbekannter Status", color: "default" };
151
+ }
152
+ }
153
+
154
+ export function dayString(
155
+ date: string | Date | null | undefined,
156
+ timezone: string = DEFAULT_TZ,
157
+ ): string {
158
+ return date ? moment(date).tz(timezone).format("DD") : "";
159
+ }
160
+
161
+ export function dateString(
162
+ date: string | Date | null | undefined,
163
+ timezone: string = DEFAULT_TZ,
164
+ ): string {
165
+ return date ? moment(date).tz(timezone).format("DD.MM.YYYY") : "";
166
+ }
167
+
168
+ export function dateTimeString(
169
+ date: string | Date | null | undefined,
170
+ timezone: string = DEFAULT_TZ,
171
+ includeTimezone: boolean = false,
172
+ ): string | null {
173
+ if (!date) {
174
+ return null;
175
+ }
176
+ let formatted = moment(date).tz(timezone).format("DD.MM.YYYY HH:mm");
177
+ if (includeTimezone) {
178
+ formatted += ` ${moment(date).tz(timezone).format("z")}`;
179
+ }
180
+ return formatted;
181
+ }
182
+
183
+ export function timeString(
184
+ date: string | Date | null | undefined,
185
+ timezone: string = DEFAULT_TZ,
186
+ includeTimezone: boolean = false,
187
+ ): string {
188
+ if (!date) {
189
+ return "";
190
+ }
191
+ let formatted = moment(date).tz(timezone).format("HH:mm");
192
+ if (includeTimezone) {
193
+ formatted += ` ${moment(date).tz(timezone).format("z")}`;
194
+ }
195
+ return formatted;
196
+ }
197
+
198
+ export function timeUntil(
199
+ dateTime: string | Date | null | undefined,
200
+ locale: string = "de",
201
+ ): string {
202
+ if (!dateTime) {
203
+ return "";
204
+ }
205
+ return moment(dateTime).locale(locale).fromNow();
206
+ }
207
+
208
+ export function minutesFormatted(minutes: number | null | undefined): string {
209
+ if (minutes == null) {
210
+ return "";
211
+ }
212
+ const sign = minutes < 0 ? "-" : "";
213
+ const abs = Math.abs(minutes);
214
+ const hours = Math.floor(abs / 60);
215
+ const remaining = abs % 60;
216
+ return `${sign}${hours < 10 ? "0" : ""}${hours}:${`0${remaining}`.slice(-2)}`;
217
+ }
218
+
219
+ export interface CrewMember {
220
+ name?: string;
221
+ role?: string;
222
+ }
223
+
224
+ export function crewObjectToString(
225
+ crew: CrewMember[] | string | null | undefined,
226
+ { includeRoles = false }: { includeRoles?: boolean } = {},
227
+ ): string {
228
+ if (!crew) {
229
+ return "No Crew";
230
+ }
231
+ if (typeof crew === "string") {
232
+ return crew;
233
+ }
234
+ const names = crew
235
+ .map((member) => {
236
+ if (includeRoles && member.role) {
237
+ return `${member.name} (${member.role})`;
238
+ }
239
+ return member.name;
240
+ })
241
+ .filter((n): n is string => Boolean(n))
242
+ .join(", ");
243
+ return names.length === 0 ? "No Crew" : names;
244
+ }
245
+
246
+ export function timeDiffInDays(
247
+ startDateTime: string | Date | null | undefined,
248
+ endDateTime: string | Date | null | undefined,
249
+ ): string | null {
250
+ if (!startDateTime || !endDateTime) {
251
+ return null;
252
+ }
253
+ const diff = moment(endDateTime).diff(moment(startDateTime), "days");
254
+ return diff ? `+${diff}` : null;
255
+ }
256
+
257
+ export interface BookingTimespan {
258
+ etdZ: string | Date;
259
+ etaZ: string | Date;
260
+ }
261
+
262
+ export function bookingDuration(booking: BookingTimespan): string {
263
+ const minutes = moment(booking.etaZ).diff(moment(booking.etdZ), "minutes");
264
+ return `${`0${Math.floor(minutes / 60)}`.slice(-2)}:${`0${minutes % 60}`.slice(-2)}`;
265
+ }
package/src/index.ts ADDED
@@ -0,0 +1,76 @@
1
+ export * from "./schemas/index.js";
2
+ export {
3
+ // types/models.ts — re-export only type aliases (not conflicting with schema values)
4
+ type User,
5
+ type Company,
6
+ type MediaCollection,
7
+ type BaseMembership,
8
+ type Membership,
9
+ type Airplane,
10
+ type ExtendedAirplane,
11
+ type AirplanePermission,
12
+ type LandingFee,
13
+ type AircraftRating,
14
+ type UserAircraftRating,
15
+ type UserQualification,
16
+ type Airport,
17
+ type RecurringFlight,
18
+ type CourseFee,
19
+ type Price,
20
+ type MaintenanceEvent,
21
+ type MaintenanceStatus,
22
+ type MaintenanceEventEstimation,
23
+ type Avail,
24
+ type Alert,
25
+ type Booking,
26
+ type Document,
27
+ type DocumentType,
28
+ type DocumentRequirement,
29
+ type DocumentRequirementGroup,
30
+ type DocumentRequirementGroupAssignment,
31
+ type DocumentComplianceStatus,
32
+ type DocumentShare,
33
+ type AllowedDocumentType,
34
+ type FlightLog,
35
+ type ScheduledFlight,
36
+ type Landing,
37
+ type ApproachType,
38
+ type Waypoint,
39
+ type Invoice,
40
+ type Expense,
41
+ type ExpenseItem,
42
+ type SpecialFlight,
43
+ type PrePaidPackage,
44
+ type Qualification,
45
+ type Course,
46
+ type CourseEnrollment,
47
+ type Event,
48
+ type Ticket,
49
+ type Post,
50
+ type Comment,
51
+ type CreateCommentInput,
52
+ type UpdateCommentInput,
53
+ type Address,
54
+ type Runway,
55
+ type Role,
56
+ type Setting,
57
+ // types/api.ts
58
+ type DataWrappedResponse,
59
+ type PaginatedResponse,
60
+ type Resource,
61
+ type ToggleFavoriteResponse,
62
+ // types/compliance.ts
63
+ type ComplianceRequirement,
64
+ type ComplianceRequirementGroup,
65
+ type ComplianceGroup,
66
+ type ComplianceSummary,
67
+ type ComplianceData,
68
+ type ComplianceResponse,
69
+ } from "./types/index.js";
70
+
71
+ export * from "./money/index.js";
72
+ export * from "./courses/index.js";
73
+ export * from "./format/index.js";
74
+ export * from "./crew/index.js";
75
+ export * from "./media/index.js";
76
+ export * from "./pagination/index.js";
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Media conversion + classification helpers for spatie/laravel-medialibrary
3
+ * payloads. All functions are pure and locale-agnostic.
4
+ */
5
+
6
+ /** Header conversion keys produced by the backend. */
7
+ export const HeaderConversion = {
8
+ Dense: "header_dense",
9
+ Large: "header_large",
10
+ } as const;
11
+
12
+ /** Image conversion keys. */
13
+ export const ImageConversion = {
14
+ GalleryMedium: "gallery_medium",
15
+ GalleryFull: "gallery_large",
16
+ Thumb: "thumb",
17
+ } as const;
18
+
19
+ /** Document conversion keys. */
20
+ export const DocumentConversion = {
21
+ Preview: "document_preview",
22
+ } as const;
23
+
24
+ /** Thumbnail conversion keys. */
25
+ export const ThumbnailConversion = {
26
+ Square: "thumb",
27
+ } as const;
28
+
29
+ /** Avatar conversion keys. */
30
+ export const AvatarConversion = {
31
+ Square: "thumb",
32
+ } as const;
33
+
34
+ export const MediaType = {
35
+ IMAGE: "image",
36
+ DOCUMENT: "document",
37
+ VIDEO: "video",
38
+ AUDIO: "audio",
39
+ UNKNOWN: "unknown",
40
+ } as const;
41
+
42
+ export type MediaTypeValue = (typeof MediaType)[keyof typeof MediaType];
43
+
44
+ export const MimeTypes = {
45
+ IMAGES: [
46
+ "image/jpeg",
47
+ "image/jpg",
48
+ "image/png",
49
+ "image/gif",
50
+ "image/webp",
51
+ "image/svg+xml",
52
+ "image/bmp",
53
+ "image/tiff",
54
+ ],
55
+ DOCUMENTS: [
56
+ "application/pdf",
57
+ "application/msword",
58
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
59
+ "application/vnd.ms-excel",
60
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
61
+ "application/vnd.ms-powerpoint",
62
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
63
+ "text/plain",
64
+ "text/csv",
65
+ "application/rtf",
66
+ ],
67
+ VIDEOS: [
68
+ "video/mp4",
69
+ "video/webm",
70
+ "video/ogg",
71
+ "video/avi",
72
+ "video/mov",
73
+ "video/wmv",
74
+ "video/flv",
75
+ "video/x-matroska",
76
+ ],
77
+ AUDIO: ["audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/flac", "audio/mp3"],
78
+ } as const;
79
+
80
+ /** Map a MIME type to its broad category. */
81
+ export function getMediaType(mimeType: string | null | undefined): MediaTypeValue {
82
+ if (!mimeType) {
83
+ return MediaType.UNKNOWN;
84
+ }
85
+ const mt = mimeType as (typeof MimeTypes.IMAGES)[number];
86
+ if (MimeTypes.IMAGES.includes(mt as never)) return MediaType.IMAGE;
87
+ if (MimeTypes.DOCUMENTS.includes(mt as never)) return MediaType.DOCUMENT;
88
+ if (MimeTypes.VIDEOS.includes(mt as never)) return MediaType.VIDEO;
89
+ if (MimeTypes.AUDIO.includes(mt as never)) return MediaType.AUDIO;
90
+ return MediaType.UNKNOWN;
91
+ }
92
+
93
+ /** Pick a specific MDI icon name for a document MIME type. */
94
+ export function getDocumentIcon(mimeType: string | null | undefined): string {
95
+ switch (mimeType) {
96
+ case "application/pdf":
97
+ return "mdi-file-pdf-box";
98
+ case "application/msword":
99
+ case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
100
+ return "mdi-file-word";
101
+ case "application/vnd.ms-excel":
102
+ case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
103
+ return "mdi-file-excel";
104
+ case "application/vnd.ms-powerpoint":
105
+ case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
106
+ return "mdi-file-powerpoint";
107
+ case "text/plain":
108
+ return "mdi-file-document";
109
+ case "text/csv":
110
+ return "mdi-file-delimited";
111
+ default:
112
+ return "mdi-file";
113
+ }
114
+ }
115
+
116
+ /** Pick an MDI icon name for any media MIME type. */
117
+ export function getMediaIcon(mimeType: string | null | undefined): string {
118
+ const t = getMediaType(mimeType);
119
+ switch (t) {
120
+ case MediaType.IMAGE:
121
+ return "mdi-image";
122
+ case MediaType.VIDEO:
123
+ return "mdi-video";
124
+ case MediaType.AUDIO:
125
+ return "mdi-music";
126
+ case MediaType.DOCUMENT:
127
+ return getDocumentIcon(mimeType);
128
+ default:
129
+ return "mdi-file";
130
+ }
131
+ }
132
+
133
+ /** True if the media type can be previewed inline (images, video, audio). */
134
+ export function canPreview(mimeType: string | null | undefined): boolean {
135
+ const t = getMediaType(mimeType);
136
+ return t === MediaType.IMAGE || t === MediaType.VIDEO || t === MediaType.AUDIO;
137
+ }
138
+
139
+ /**
140
+ * Localized label for a media type. Pass `labels` to override (for i18n).
141
+ * Defaults to German since that's the only locale currently in use in MCRIT.
142
+ */
143
+ export function getMediaDisplayName(
144
+ mimeType: string | null | undefined,
145
+ labels: Partial<Record<MediaTypeValue, string>> = {},
146
+ ): string {
147
+ const defaults: Record<MediaTypeValue, string> = {
148
+ image: "Bild",
149
+ video: "Video",
150
+ audio: "Audio",
151
+ document: "Dokument",
152
+ unknown: "Datei",
153
+ };
154
+ const t = getMediaType(mimeType);
155
+ return labels[t] ?? defaults[t];
156
+ }
157
+
158
+ /** Permissive Media shape — accepts spatie/laravel-medialibrary payloads. */
159
+ export interface MediaLike {
160
+ url?: string;
161
+ mime_type?: string;
162
+ file_name?: string;
163
+ name?: string;
164
+ size?: number;
165
+ alt?: string;
166
+ description?: string;
167
+ title?: string;
168
+ conversions?: Record<string, string>;
169
+ }
170
+
171
+ export interface MappedMedia {
172
+ href: string | undefined;
173
+ src: string | undefined;
174
+ thumbnail: string | undefined;
175
+ documentPreview: string | null;
176
+ mediaType: MediaTypeValue;
177
+ mimeType: string | undefined;
178
+ icon: string;
179
+ previewable: boolean;
180
+ fileName: string | undefined;
181
+ size: number | undefined;
182
+ alt: string | undefined;
183
+ title: string | undefined;
184
+ original: MediaLike;
185
+ }
186
+
187
+ function mapSingle(m: MediaLike): MappedMedia {
188
+ const galleryFull = m?.conversions?.[ImageConversion.GalleryFull] ?? m?.url;
189
+ const thumb = m?.conversions?.[ImageConversion.Thumb] ?? m?.url;
190
+ const documentPreview = m?.conversions?.[DocumentConversion.Preview] ?? null;
191
+
192
+ return {
193
+ href: galleryFull,
194
+ src: galleryFull,
195
+ thumbnail: thumb,
196
+ documentPreview,
197
+ mediaType: getMediaType(m?.mime_type),
198
+ mimeType: m?.mime_type,
199
+ icon: getMediaIcon(m?.mime_type),
200
+ previewable: canPreview(m?.mime_type),
201
+ fileName: m?.file_name ?? m?.name,
202
+ size: m?.size,
203
+ alt: m?.alt ?? m?.description ?? m?.file_name,
204
+ title: m?.title ?? m?.description ?? m?.file_name,
205
+ original: m,
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Normalize a media field (single object or array) into a uniform array
211
+ * of gallery-ready entries with conversions resolved. Returns `[]` for
212
+ * `null`/`undefined`.
213
+ */
214
+ export function mapMediaConversions(
215
+ media: MediaLike | MediaLike[] | null | undefined,
216
+ ): MappedMedia[] {
217
+ if (!media) {
218
+ return [];
219
+ }
220
+ if (Array.isArray(media)) {
221
+ return media.map(mapSingle);
222
+ }
223
+ return [mapSingle(media)];
224
+ }