Skip to content

Commit f51a7fe

Browse files
committed
Merge branch 'add-weekly-rhythm-options' into private/dev
2 parents 499d57a + 7626f81 commit f51a7fe

File tree

7 files changed

+45
-6
lines changed

7 files changed

+45
-6
lines changed

app/admin/weeklys/_components/AdminWeeklyForm.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface WeeklyEventConfig {
2626
weekday: number;
2727
weeksOn: number;
2828
weeksOff: number;
29+
skipInterval: number;
2930
startDate: string;
3031
airports?: string[];
3132
startTime?: string;
@@ -48,6 +49,7 @@ interface FormData {
4849
weekday: number;
4950
weeksOn: number;
5051
weeksOff: number;
52+
skipInterval: number;
5153
startDate: string;
5254
airports: string[];
5355
airportInput: string;
@@ -96,6 +98,7 @@ export default function AdminWeeklyForm({ config, firs }: Props) {
9698
weekday: 1,
9799
weeksOn: 1,
98100
weeksOff: 0,
101+
skipInterval: 0,
99102
startDate: new Date().toISOString().split("T")[0],
100103
airports: [],
101104
airportInput: "",
@@ -127,6 +130,7 @@ export default function AdminWeeklyForm({ config, firs }: Props) {
127130
weekday: config.weekday,
128131
weeksOn: config.weeksOn,
129132
weeksOff: config.weeksOff,
133+
skipInterval: config.skipInterval ?? 0,
130134
startDate: config.startDate.split("T")[0],
131135
airports,
132136
airportInput: "",
@@ -266,6 +270,7 @@ export default function AdminWeeklyForm({ config, firs }: Props) {
266270
weekday: formData.weekday,
267271
weeksOn: formData.weeksOn,
268272
weeksOff: formData.weeksOff,
273+
skipInterval: formData.skipInterval,
269274
startDate: new Date(formData.startDate).toISOString(),
270275
airports: formData.airports.length > 0 ? formData.airports : null,
271276
startTime: formData.startTime || null,
@@ -380,7 +385,7 @@ export default function AdminWeeklyForm({ config, firs }: Props) {
380385
setActiveTab(v);
381386
if (v === "managers" && isEdit) loadManagers();
382387
}} className="w-full">
383-
<TabsList className={`grid w-full ${isEdit && formData.requiresRoster ? 'grid-cols-4' : isEdit ? 'grid-cols-3' : formData.requiresRoster ? 'grid-cols-3' : 'grid-cols-2'}`}>
388+
<TabsList className={`grid w-full ${isEdit && formData.requiresRoster ? 'grid-cols-4' : formData.requiresRoster ? 'grid-cols-3' : 'grid-cols-2'}`}>
384389
<TabsTrigger value="basic">Grunddaten</TabsTrigger>
385390
<TabsTrigger value="schedule">Zeitplan</TabsTrigger>
386391
{formData.requiresRoster && (
@@ -650,6 +655,25 @@ export default function AdminWeeklyForm({ config, firs }: Props) {
650655
</p>
651656
</div>
652657
</div>
658+
659+
<div className="space-y-2">
660+
<Label htmlFor="skipInterval">Regelmäßige Aussetzer (optional)</Label>
661+
<Input
662+
id="skipInterval"
663+
type="number"
664+
min="0"
665+
value={formData.skipInterval}
666+
onChange={(e) =>
667+
setFormData({
668+
...formData,
669+
skipInterval: parseInt(e.target.value) || 0,
670+
})
671+
}
672+
/>
673+
<p className="text-xs text-muted-foreground">
674+
0 = deaktiviert. Bei z.B. 3 wird jedes 3. Vorkommen übersprungen (nützlich für regelmäßige Pausen alle N Termine).
675+
</p>
676+
</div>
653677
</CardContent>
654678
</Card>
655679
</TabsContent>

app/api/admin/weeklys/[id]/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const weeklyEventConfigUpdateSchema = z.object({
1212
weekday: z.number().min(0).max(6).optional(),
1313
weeksOn: z.number().min(1).max(52).optional(),
1414
weeksOff: z.number().min(0).max(52).optional(),
15+
skipInterval: z.number().min(0).max(52).optional(),
1516
startDate: z.string().refine((val) => !isNaN(Date.parse(val)), {
1617
message: "Invalid date format for startDate",
1718
}).optional(),
@@ -156,6 +157,7 @@ export async function PATCH(
156157
if (parsed.data.weekday !== undefined) updateData.weekday = parsed.data.weekday;
157158
if (parsed.data.weeksOn !== undefined) updateData.weeksOn = parsed.data.weeksOn;
158159
if (parsed.data.weeksOff !== undefined) updateData.weeksOff = parsed.data.weeksOff;
160+
if (parsed.data.skipInterval !== undefined) updateData.skipInterval = parsed.data.skipInterval;
159161
if (parsed.data.startDate !== undefined) updateData.startDate = new Date(parsed.data.startDate);
160162
if (parsed.data.airports !== undefined) updateData.airports = parsed.data.airports ? JSON.stringify(parsed.data.airports) : null;
161163
if (parsed.data.startTime !== undefined) updateData.startTime = parsed.data.startTime;
@@ -174,11 +176,12 @@ export async function PATCH(
174176
data: updateData,
175177
});
176178

177-
// If pattern changed (weekday, weeksOn, weeksOff, or startDate), regenerate occurrences
179+
// If pattern changed (weekday, weeksOn, weeksOff, skipInterval, or startDate), regenerate occurrences
178180
if (
179181
parsed.data.weekday !== undefined ||
180182
parsed.data.weeksOn !== undefined ||
181183
parsed.data.weeksOff !== undefined ||
184+
parsed.data.skipInterval !== undefined ||
182185
parsed.data.startDate !== undefined
183186
) {
184187
// Delete future occurrences that don't have signups

app/api/admin/weeklys/route.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const weeklyEventConfigSchema = z.object({
1515
weekday: z.number().min(0).max(6),
1616
weeksOn: z.number().min(1).max(52),
1717
weeksOff: z.number().min(0).max(52),
18+
skipInterval: z.number().min(0).max(52).optional(),
1819
startDate: z.string().refine((val) => !isNaN(Date.parse(val)), {
1920
message: "Invalid date format for startDate",
2021
}),
@@ -191,6 +192,7 @@ export async function POST(req: NextRequest) {
191192
weekday: parsed.data.weekday,
192193
weeksOn: parsed.data.weeksOn,
193194
weeksOff: parsed.data.weeksOff,
195+
skipInterval: parsed.data.skipInterval ?? 0,
194196
startDate: new Date(parsed.data.startDate),
195197
airports: parsed.data.airports ? (JSON.stringify(parsed.data.airports) as any) : null,
196198
startTime: parsed.data.startTime || null,

lib/weeklys/generateOccurrences.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,22 @@ export async function generateOccurrences(configId: number) {
3434
// weekCounter auf 0 – cursor ist per Definition Woche 0 des Zyklus
3535
const weeksOn = config.weeksOn;
3636
const weeksOff = config.weeksOff;
37+
const skipInterval = ("skipInterval" in config ? (config as { skipInterval: number }).skipInterval : 0) ?? 0;
3738
const cycleLength = weeksOn + weeksOff; // bei weeksOff=0 → cycleLength=weeksOn
3839

3940
const occurrenceDates: Date[] = [];
4041
let weekCounter = 0;
42+
let occurrenceNumber = 0; // counts all candidate occurrences from the startDate for skip logic
4143

4244
while (cursor <= endUTC) {
4345
const positionInCycle = weekCounter % cycleLength;
4446

4547
// Nur in den ersten weeksOn Positionen des Zyklus ist ein Event
4648
if (positionInCycle < weeksOn) {
47-
if (cursor >= todayUTC) {
49+
occurrenceNumber++;
50+
// Skip every Nth occurrence when skipInterval is configured
51+
const isSkipped = skipInterval > 0 && (occurrenceNumber % skipInterval === 0);
52+
if (!isSkipped && cursor >= todayUTC) {
4853
occurrenceDates.push(new Date(cursor));
4954
}
5055
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE `WeeklyEventConfiguration` ADD COLUMN `skipInterval` INTEGER NOT NULL DEFAULT 0;

prisma/schema.prisma

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,9 +273,10 @@ model WeeklyEventConfiguration {
273273
274274
// Pattern configuration for recurring events
275275
// e.g., for "2 weeks on, 1 week off": weeksOn=2, weeksOff=1
276-
weeksOn Int @default(1) // How many consecutive weeks the event occurs
277-
weeksOff Int @default(0) // How many weeks off after the "on" period
278-
startDate DateTime // When the pattern starts
276+
weeksOn Int @default(1) // How many consecutive weeks the event occurs
277+
weeksOff Int @default(0) // How many weeks off after the "on" period
278+
skipInterval Int @default(0) // Skip every Nth occurrence (0 = disabled), e.g. 3 = every 3rd occurrence is skipped
279+
startDate DateTime // When the pattern starts
279280
280281
// Event details
281282
airports Json? // Array of ICAO codes

types/weeklyEvent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface WeeklyEventConfigurationCreate {
44
weekday: number; // 0-6 (Sunday-Saturday)
55
weeksOn: number;
66
weeksOff: number;
7+
skipInterval?: number; // 0 = disabled, N = skip every Nth occurrence
78
startDate: Date | string;
89
airports?: string[]; // Array of ICAO codes
910
startTime?: string; // HH:mm format (UTC)
@@ -22,6 +23,7 @@ export interface WeeklyEventConfigurationUpdate {
2223
weekday?: number;
2324
weeksOn?: number;
2425
weeksOff?: number;
26+
skipInterval?: number; // 0 = disabled, N = skip every Nth occurrence
2527
startDate?: Date | string;
2628
airports?: string[];
2729
startTime?: string;

0 commit comments

Comments
 (0)