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.
- package/lib/eventTimeSlots.d.ts +9 -0
- package/lib/eventTimeSlots.js +33 -0
- package/lib/eventTimeSlots.js.map +1 -0
- package/lib/eventTimeSlots.test.d.ts +1 -0
- package/lib/eventTimeSlots.test.js +29 -0
- package/lib/eventTimeSlots.test.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/typeDefinitions.d.ts +12 -0
- package/lib/typeDefinitions.js.map +1 -1
- package/package.json +1 -1
- package/src/eventTimeSlots.test.ts +31 -0
- package/src/eventTimeSlots.ts +40 -0
- package/src/index.ts +1 -0
- package/src/typeDefinitions.ts +11 -0
|
@@ -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
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"}
|
package/lib/typeDefinitions.d.ts
CHANGED
|
@@ -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":";;;
|
|
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
|
@@ -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
package/src/typeDefinitions.ts
CHANGED
|
@@ -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,
|