placementt-core 1.400.1164 → 1.400.1165

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.
@@ -0,0 +1,9 @@
1
+ import { ExternalEvent } from "./typeDefinitions";
2
+ export type EventTimeSlot = {
3
+ id: string;
4
+ date: string;
5
+ start: string;
6
+ end: string;
7
+ };
8
+ export declare const getEventTimeSlots: (event: Pick<ExternalEvent, "timeSlots" | "startDate" | "endDate" | "startTime" | "endTime">) => EventTimeSlot[];
9
+ export declare const getSlotPlacesLeft: (event: Pick<ExternalEvent, "timeSlots" | "slotBookings">, slotId: string) => number;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getSlotPlacesLeft = exports.getEventTimeSlots = void 0;
4
+ const toMinutes = (time) => {
5
+ const [h, m] = time.split(":").map((v) => parseInt(v));
6
+ return h * 60 + (m || 0);
7
+ };
8
+ const toTime = (minutes) => `${String(Math.floor(minutes / 60)).padStart(2, "0")}:${String(minutes % 60).padStart(2, "0")}`;
9
+ // Every slot for the event, one run of slotMinutes-long slots per day between
10
+ // startDate and endDate (inclusive). A final slot that would overrun endTime is dropped.
11
+ const getEventTimeSlots = (event) => {
12
+ const { timeSlots, startDate, startTime, endTime } = event;
13
+ if (!timeSlots?.slotMinutes || !startDate || !startTime || !endTime)
14
+ return [];
15
+ const dayStart = toMinutes(startTime);
16
+ const dayEnd = toMinutes(endTime);
17
+ const first = new Date(`${startDate.slice(0, 10)}T00:00:00Z`);
18
+ const last = new Date(`${(event.endDate || startDate).slice(0, 10)}T00:00:00Z`);
19
+ if (isNaN(first.getTime()) || isNaN(last.getTime()))
20
+ return [];
21
+ const slots = [];
22
+ for (const day = first; day <= last; day.setUTCDate(day.getUTCDate() + 1)) {
23
+ const date = day.toISOString().slice(0, 10);
24
+ for (let t = dayStart; t + timeSlots.slotMinutes <= dayEnd; t += timeSlots.slotMinutes) {
25
+ slots.push({ id: `${date}T${toTime(t)}`, date, start: toTime(t), end: toTime(t + timeSlots.slotMinutes) });
26
+ }
27
+ }
28
+ return slots;
29
+ };
30
+ exports.getEventTimeSlots = getEventTimeSlots;
31
+ const getSlotPlacesLeft = (event, slotId) => Math.max(0, (event.timeSlots?.capacity || 0) - (event.slotBookings?.[slotId] || 0));
32
+ exports.getSlotPlacesLeft = getSlotPlacesLeft;
33
+ //# sourceMappingURL=eventTimeSlots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventTimeSlots.js","sourceRoot":"","sources":["../src/eventTimeSlots.ts"],"names":[],"mappings":";;;AASA,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,EAAE;IAC/B,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAEpI,8EAA8E;AAC9E,yFAAyF;AAClF,MAAM,iBAAiB,GAAG,CAAC,KAAmF,EAAmB,EAAE;IACtI,MAAM,EAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAC,GAAG,KAAK,CAAC;IACzD,IAAI,CAAC,SAAS,EAAE,WAAW,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAE/E,MAAM,QAAQ,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC;IAChF,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAE/D,MAAM,KAAK,GAAoB,EAAE,CAAC;IAClC,KAAK,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,KAAK,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,GAAG,SAAS,CAAC,WAAW,IAAI,MAAM,EAAE,CAAC,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC;YACrF,KAAK,CAAC,IAAI,CAAC,EAAC,EAAE,EAAE,GAAG,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC,EAAC,CAAC,CAAC;QAC7G,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AAlBW,QAAA,iBAAiB,qBAkB5B;AAEK,MAAM,iBAAiB,GAAG,CAAC,KAAsD,EAAE,MAAc,EAAE,EAAE,CACxG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAD3E,QAAA,iBAAiB,qBAC0D"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const eventTimeSlots_1 = require("./eventTimeSlots");
4
+ describe("getEventTimeSlots", () => {
5
+ const timeSlots = { slotMinutes: 60, capacity: 30, capacityUnit: "students", maxSlotsPerSchool: 1 };
6
+ it("generates hourly slots for each day in the range", () => {
7
+ const slots = (0, eventTimeSlots_1.getEventTimeSlots)({ timeSlots, startDate: "2026-11-02", endDate: "2026-11-04", startTime: "09:00", endTime: "12:00" });
8
+ expect(slots).toHaveLength(9);
9
+ expect(slots[0]).toEqual({ id: "2026-11-02T09:00", date: "2026-11-02", start: "09:00", end: "10:00" });
10
+ expect(slots[8].id).toBe("2026-11-04T11:00");
11
+ });
12
+ it("drops a final slot that would overrun the end time", () => {
13
+ const slots = (0, eventTimeSlots_1.getEventTimeSlots)({ timeSlots: { ...timeSlots, slotMinutes: 45 }, startDate: "2026-11-02", startTime: "09:00", endTime: "10:30" });
14
+ expect(slots.map((s) => s.id)).toEqual(["2026-11-02T09:00", "2026-11-02T09:45"]);
15
+ });
16
+ it("returns nothing without slot settings or times", () => {
17
+ expect((0, eventTimeSlots_1.getEventTimeSlots)({ startDate: "2026-11-02", startTime: "09:00", endTime: "12:00" })).toEqual([]);
18
+ expect((0, eventTimeSlots_1.getEventTimeSlots)({ timeSlots, startDate: "2026-11-02" })).toEqual([]);
19
+ });
20
+ });
21
+ describe("getSlotPlacesLeft", () => {
22
+ it("subtracts bookings from capacity and never goes negative", () => {
23
+ const timeSlots = { slotMinutes: 60, capacity: 30, capacityUnit: "students", maxSlotsPerSchool: 1 };
24
+ expect((0, eventTimeSlots_1.getSlotPlacesLeft)({ timeSlots, slotBookings: { a: 12 } }, "a")).toBe(18);
25
+ expect((0, eventTimeSlots_1.getSlotPlacesLeft)({ timeSlots, slotBookings: { a: 40 } }, "a")).toBe(0);
26
+ expect((0, eventTimeSlots_1.getSlotPlacesLeft)({ timeSlots }, "b")).toBe(30);
27
+ });
28
+ });
29
+ //# sourceMappingURL=eventTimeSlots.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventTimeSlots.test.js","sourceRoot":"","sources":["../src/eventTimeSlots.test.ts"],"names":[],"mappings":";;AAAA,qDAAsE;AAEtE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,MAAM,SAAS,GAAG,EAAC,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,UAAmB,EAAE,iBAAiB,EAAE,CAAC,EAAC,CAAC;IAE3G,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QACxD,MAAM,KAAK,GAAG,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAC,CAAC,CAAC;QACnI,MAAM,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAC,CAAC,CAAC;QACrG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC1D,MAAM,KAAK,GAAG,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAE,EAAC,GAAG,SAAS,EAAE,WAAW,EAAE,EAAE,EAAC,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAC,CAAC,CAAC;QAC7I,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACvG,MAAM,CAAC,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAChE,MAAM,SAAS,GAAG,EAAC,WAAW,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,YAAY,EAAE,UAAmB,EAAE,iBAAiB,EAAE,CAAC,EAAC,CAAC;QAC3G,MAAM,CAAC,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAE,YAAY,EAAE,EAAC,CAAC,EAAE,EAAE,EAAC,EAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5E,MAAM,CAAC,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAE,YAAY,EAAE,EAAC,CAAC,EAAE,EAAE,EAAC,EAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAA,kCAAiB,EAAC,EAAC,SAAS,EAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
package/lib/index.d.ts CHANGED
@@ -72,3 +72,4 @@ export * from "./reduxHooks";
72
72
  export * from "./callables";
73
73
  export * from "./leavers";
74
74
  export { default as genericEmailDomains } from "./genericEmailDomains";
75
+ export * from "./eventTimeSlots";
package/lib/index.js CHANGED
@@ -93,4 +93,5 @@ __exportStar(require("./callables"), exports);
93
93
  __exportStar(require("./leavers"), exports);
94
94
  var genericEmailDomains_1 = require("./genericEmailDomains");
95
95
  Object.defineProperty(exports, "genericEmailDomains", { enumerable: true, get: function () { return __importDefault(genericEmailDomains_1).default; } });
96
+ __exportStar(require("./eventTimeSlots"), exports);
96
97
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,iDAA+B;AAC/B,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B;AAC9B,4CAA0B;AAC1B,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,8CAA4B;AAC5B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,wDAAsC;AACtC,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,4CAA0B;AAC1B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,4CAA0B;AAC1B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AACA,sDAAoC;AACpC,gEAA8C;AAC9C,0DAAwC;AACxC,2DAAyC;AACzC,kDAAgC;AAChC,uDAAqC;AACrC,4DAA0C;AAC1C,2DAAyC;AAEzC,oDAAkC;AAClC,8CAA4B;AAC5B,kDAAgC;AAChC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,qDAAmC;AACnC,qDAAmC;AACnC,sDAAoC;AACpC,kDAAgC;AAChC,mDAAiC;AACjC,+CAA6B;AAC7B,oDAAkC;AAClC,iDAA+B;AAC/B,qDAAmC;AACnC,uDAAqC;AACrC,gDAA8B;AAC9B,4CAA0B;AAC1B,wCAAsB;AACtB,kDAAgC;AAChC,6CAA2B;AAC3B,8CAA4B;AAC5B,oDAAkC;AAClC,4CAA0B;AAC1B,8CAA4B;AAC5B,2CAAyB;AACzB,wDAAsC;AACtC,kDAAgC;AAChC,uDAAqC;AACrC,oDAAkC;AAClC,kDAAgC;AAChC,0DAAwC;AACxC,iDAA+B;AAC/B,2DAAyC;AACzC,uDAAqC;AACrC,uDAAqC;AACrC,iDAA+B;AAC/B,8CAA4B;AAC5B,4CAA0B;AAC1B,qDAAmC;AACnC,0CAAwB;AACxB,kDAAgC;AAChC,yCAAuB;AACvB,4DAA0C;AAC1C,kDAAgC;AAChC,iDAA+B;AAC/B,2CAAyB;AACzB,4DAA0C;AAC1C,oEAAiD;AACjD,yEAAuD;AACvD,kEAAgD;AAChD,kEAAgD;AAChD,oEAAkD;AAClD,yGAAuF;AACvF,0GAAwF;AACxF,+FAA6E;AAC7E,4DAA0C;AAC1C,oEAAkD;AAClD,iEAA+C;AAC/C,gEAA8C;AAC9C,2EAAyD;AACzD,6CAA2B;AAC3B,+CAA6B;AAC7B,8CAA4B;AAC5B,4CAA0B;AAC1B,6DAAuE;AAA9D,2IAAA,OAAO,OAAuB;AACvC,mDAAiC"}
@@ -2384,6 +2384,15 @@ export type ExternalEvent = {
2384
2384
  maxExternalAttendees?: number;
2385
2385
  maxSchools?: number;
2386
2386
  maxStudents?: number;
2387
+ timeSlots?: {
2388
+ slotMinutes: number;
2389
+ capacity: number;
2390
+ capacityUnit: "students" | "schools";
2391
+ maxSlotsPerSchool: number;
2392
+ };
2393
+ slotBookings?: {
2394
+ [slotId: string]: number;
2395
+ };
2387
2396
  studentJoinCode: number;
2388
2397
  stage: "basicDetails" | "eventConfig" | "selectEmployers" | "employerInviteForms" | "sendEmployerInvites" | "setupStudentInvites" | "preEvent" | "created";
2389
2398
  draft: boolean;
@@ -3371,6 +3380,9 @@ export type ExternalEventCareersSchool = {
3371
3380
  instituteId: string;
3372
3381
  eventId: string;
3373
3382
  status: "invited" | "available" | "finalConfirmationSent" | "confirmed" | "feedbackSent" | "declined" | "visibleTo" | "creator";
3383
+ slots?: {
3384
+ [slotId: string]: number;
3385
+ };
3374
3386
  employers?: {
3375
3387
  [key: string]: {
3376
3388
  dates?: {
@@ -1 +1 @@
1
- {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AAgoIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AAjoED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AA6iEzC;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAChC,IAAwC,EACxC,MAA6C;IAE7C,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,EAAE,YAAY,IAAI,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,KAA6D;IACzF,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
1
+ {"version":3,"file":"typeDefinitions.js","sourceRoot":"","sources":["../src/typeDefinitions.ts"],"names":[],"mappings":";;;AA2oIA,sDASC;AAeD,oDAKC;AAaD,0CAEC;AA5oED;;;;;kFAKkF;AACrE,QAAA,qBAAqB,GAAG,SAAS,CAAC;AAClC,QAAA,iBAAiB,GAAG,OAAO,CAAC;AAwjEzC;yEACyE;AAC5D,QAAA,0BAA0B,GAA2C;IAC9E,gBAAgB,EAAE,2BAA2B;IAC7C,mBAAmB,EAAE,sBAAsB;IAC3C,cAAc,EAAE,iBAAiB;IACjC,kBAAkB,EAAE,qBAAqB;IACzC,UAAU,EAAE,oCAAoC;IAChD,aAAa,EAAE,yBAAyB;IACxC,aAAa,EAAE,wBAAwB;IACvC,KAAK,EAAE,OAAO;CACjB,CAAC;AAEW,QAAA,0BAA0B,GAA+C;IAClF,QAAQ,EAAE,WAAW;IACrB,KAAK,EAAE,YAAY;IACnB,KAAK,EAAE,OAAO;CACjB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,SAAgB,qBAAqB,CAAC,SAAsC;IACxE,QAAQ,SAAS,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC;QAC5B,KAAK,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC;QAC7B,KAAK,WAAW,CAAC;QACjB,KAAK,OAAO,CAAC;QACb,KAAK,OAAO,CAAC;QACb,OAAO,CAAC,CAAC,OAAO,UAAU,CAAC;IAC3B,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAgB,oBAAoB,CAChC,IAAwC,EACxC,MAA6C;IAE7C,OAAO,IAAI,EAAE,WAAW,IAAI,MAAM,EAAE,YAAY,IAAI,KAAK,CAAC;AAC9D,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,eAAe,CAAC,KAA6D;IACzF,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,IAAI,KAAK,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;AAC/E,CAAC"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.1164",
5
+ "version": "1.400.1165",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -0,0 +1,31 @@
1
+ import {getEventTimeSlots, getSlotPlacesLeft} from "./eventTimeSlots";
2
+
3
+ describe("getEventTimeSlots", () => {
4
+ const timeSlots = {slotMinutes: 60, capacity: 30, capacityUnit: "students" as const, maxSlotsPerSchool: 1};
5
+
6
+ it("generates hourly slots for each day in the range", () => {
7
+ const slots = getEventTimeSlots({timeSlots, startDate: "2026-11-02", endDate: "2026-11-04", startTime: "09:00", endTime: "12:00"});
8
+ expect(slots).toHaveLength(9);
9
+ expect(slots[0]).toEqual({id: "2026-11-02T09:00", date: "2026-11-02", start: "09:00", end: "10:00"});
10
+ expect(slots[8].id).toBe("2026-11-04T11:00");
11
+ });
12
+
13
+ it("drops a final slot that would overrun the end time", () => {
14
+ const slots = getEventTimeSlots({timeSlots: {...timeSlots, slotMinutes: 45}, startDate: "2026-11-02", startTime: "09:00", endTime: "10:30"});
15
+ expect(slots.map((s) => s.id)).toEqual(["2026-11-02T09:00", "2026-11-02T09:45"]);
16
+ });
17
+
18
+ it("returns nothing without slot settings or times", () => {
19
+ expect(getEventTimeSlots({startDate: "2026-11-02", startTime: "09:00", endTime: "12:00"})).toEqual([]);
20
+ expect(getEventTimeSlots({timeSlots, startDate: "2026-11-02"})).toEqual([]);
21
+ });
22
+ });
23
+
24
+ describe("getSlotPlacesLeft", () => {
25
+ it("subtracts bookings from capacity and never goes negative", () => {
26
+ const timeSlots = {slotMinutes: 60, capacity: 30, capacityUnit: "students" as const, maxSlotsPerSchool: 1};
27
+ expect(getSlotPlacesLeft({timeSlots, slotBookings: {a: 12}}, "a")).toBe(18);
28
+ expect(getSlotPlacesLeft({timeSlots, slotBookings: {a: 40}}, "a")).toBe(0);
29
+ expect(getSlotPlacesLeft({timeSlots}, "b")).toBe(30);
30
+ });
31
+ });
@@ -0,0 +1,40 @@
1
+ import {ExternalEvent} from "./typeDefinitions";
2
+
3
+ export type EventTimeSlot = {
4
+ id: string, // "YYYY-MM-DDTHH:mm", stable as long as the event dates and times don't change
5
+ date: string, // "YYYY-MM-DD"
6
+ start: string, // "HH:mm"
7
+ end: string, // "HH:mm"
8
+ };
9
+
10
+ const toMinutes = (time: string) => {
11
+ const [h, m] = time.split(":").map((v) => parseInt(v));
12
+ return h * 60 + (m || 0);
13
+ };
14
+
15
+ const toTime = (minutes: number) => `${String(Math.floor(minutes / 60)).padStart(2, "0")}:${String(minutes % 60).padStart(2, "0")}`;
16
+
17
+ // Every slot for the event, one run of slotMinutes-long slots per day between
18
+ // startDate and endDate (inclusive). A final slot that would overrun endTime is dropped.
19
+ export const getEventTimeSlots = (event: Pick<ExternalEvent, "timeSlots"|"startDate"|"endDate"|"startTime"|"endTime">): EventTimeSlot[] => {
20
+ const {timeSlots, startDate, startTime, endTime} = event;
21
+ if (!timeSlots?.slotMinutes || !startDate || !startTime || !endTime) return [];
22
+
23
+ const dayStart = toMinutes(startTime);
24
+ const dayEnd = toMinutes(endTime);
25
+ const first = new Date(`${startDate.slice(0, 10)}T00:00:00Z`);
26
+ const last = new Date(`${(event.endDate || startDate).slice(0, 10)}T00:00:00Z`);
27
+ if (isNaN(first.getTime()) || isNaN(last.getTime())) return [];
28
+
29
+ const slots: EventTimeSlot[] = [];
30
+ for (const day = first; day <= last; day.setUTCDate(day.getUTCDate() + 1)) {
31
+ const date = day.toISOString().slice(0, 10);
32
+ for (let t = dayStart; t + timeSlots.slotMinutes <= dayEnd; t += timeSlots.slotMinutes) {
33
+ slots.push({id: `${date}T${toTime(t)}`, date, start: toTime(t), end: toTime(t + timeSlots.slotMinutes)});
34
+ }
35
+ }
36
+ return slots;
37
+ };
38
+
39
+ export const getSlotPlacesLeft = (event: Pick<ExternalEvent, "timeSlots"|"slotBookings">, slotId: string) =>
40
+ Math.max(0, (event.timeSlots?.capacity || 0) - (event.slotBookings?.[slotId] || 0));
package/src/index.ts CHANGED
@@ -74,3 +74,4 @@ export * from "./reduxHooks";
74
74
  export * from "./callables";
75
75
  export * from "./leavers";
76
76
  export { default as genericEmailDomains } from "./genericEmailDomains";
77
+ export * from "./eventTimeSlots";
@@ -2393,6 +2393,16 @@ export type ExternalEvent = {
2393
2393
  maxExternalAttendees?: number, // max number of employers
2394
2394
  maxSchools?: number, // max number of employers
2395
2395
  maxStudents?: number, // max number of employers
2396
+ // Timed slots: schools book fixed-length slots generated from startDate..endDate
2397
+ // and startTime..endTime (see getEventTimeSlots). Booking and freeing places
2398
+ // only happens through the events-bookEventSlots / events-removeEventSlotBooking callables.
2399
+ timeSlots?: {
2400
+ slotMinutes: number,
2401
+ capacity: number, // places per slot
2402
+ capacityUnit: "students"|"schools",
2403
+ maxSlotsPerSchool: number,
2404
+ },
2405
+ slotBookings?: {[slotId: string]: number}, // places used per slot
2396
2406
  studentJoinCode: number,
2397
2407
  stage: "basicDetails"|"eventConfig"|"selectEmployers"|"employerInviteForms"|"sendEmployerInvites"|"setupStudentInvites"|"preEvent"|"created",
2398
2408
  draft: boolean,
@@ -3357,6 +3367,7 @@ export type ExternalEventCareersSchool = {
3357
3367
  instituteId: string,
3358
3368
  eventId: string,
3359
3369
  status: "invited"|"available"|"finalConfirmationSent"|"confirmed"|"feedbackSent"|"declined"|"visibleTo"|"creator",
3370
+ slots?: {[slotId: string]: number}, // Timed slot bookings, with students per slot (1 when the event counts schools)
3360
3371
  employers?: {[key: string]: {
3361
3372
  dates?: {
3362
3373
  startDate: string,