feat: configurable weekend days in Vacay — closes #97

Users can now select which days are weekends (default: Sat+Sun).
Useful for countries like Bangladesh (Fri+Sat) or others with
different work weeks. Settings appear under "Block weekends" toggle.
This commit is contained in:
Maurice
2026-03-29 19:45:30 +02:00
parent b28b483b90
commit 2171203a4c
5 changed files with 47 additions and 7 deletions

View File

@@ -26,6 +26,7 @@ export default function VacayCalendar() {
}, [entries])
const blockWeekends = plan?.block_weekends !== false
const weekendDays: number[] = plan?.weekend_days ? String(plan.weekend_days).split(',').map(Number) : [0, 6]
const companyHolidaysEnabled = plan?.company_holidays_enabled !== false
const handleCellClick = useCallback(async (dateStr) => {
@@ -35,7 +36,7 @@ export default function VacayCalendar() {
return
}
if (holidays[dateStr]) return
if (blockWeekends && isWeekend(dateStr)) return
if (blockWeekends && isWeekend(dateStr, weekendDays)) return
if (companyHolidaysEnabled && companyHolidaySet.has(dateStr)) return
await toggleEntry(dateStr, selectedUserId || undefined)
}, [companyMode, toggleEntry, toggleCompanyHoliday, holidays, companyHolidaySet, blockWeekends, companyHolidaysEnabled, selectedUserId])
@@ -57,6 +58,7 @@ export default function VacayCalendar() {
onCellClick={handleCellClick}
companyMode={companyMode}
blockWeekends={blockWeekends}
weekendDays={weekendDays}
/>
))}
</div>

View File

@@ -29,11 +29,12 @@ interface VacayMonthCardProps {
onCellClick: (date: string) => void
companyMode: boolean
blockWeekends: boolean
weekendDays?: number[]
}
export default function VacayMonthCard({
year, month, holidays, companyHolidaySet, companyHolidaysEnabled = true, entryMap,
onCellClick, companyMode, blockWeekends
onCellClick, companyMode, blockWeekends, weekendDays = [0, 6]
}: VacayMonthCardProps) {
const { language } = useTranslation()
const weekdays = language === 'de' ? WEEKDAYS_DE : language === 'es' ? WEEKDAYS_ES : language === 'ar' ? WEEKDAYS_AR : WEEKDAYS_EN
@@ -76,7 +77,8 @@ export default function VacayMonthCard({
if (day === null) return <div key={di} style={{ height: 28 }} />
const dateStr = `${year}-${pad(month + 1)}-${pad(day)}`
const weekend = di >= 5
const dayOfWeek = new Date(year, month, day).getDay()
const weekend = weekendDays.includes(dayOfWeek)
const holiday = holidays[dateStr]
const isCompany = companyHolidaysEnabled && companyHolidaySet.has(dateStr)
const dayEntries = entryMap[dateStr] || []

View File

@@ -49,6 +49,42 @@ export default function VacaySettings({ onClose }: VacaySettingsProps) {
onChange={() => toggle('block_weekends')}
/>
{/* Weekend days selector */}
{plan.block_weekends !== false && (
<div style={{ paddingLeft: 36 }}>
<p className="text-xs font-medium mb-2" style={{ color: 'var(--text-muted)' }}>{t('vacay.weekendDays')}</p>
<div className="flex flex-wrap gap-1.5">
{[
{ day: 1, label: t('vacay.mon') },
{ day: 2, label: t('vacay.tue') },
{ day: 3, label: t('vacay.wed') },
{ day: 4, label: t('vacay.thu') },
{ day: 5, label: t('vacay.fri') },
{ day: 6, label: t('vacay.sat') },
{ day: 0, label: t('vacay.sun') },
].map(({ day, label }) => {
const current: number[] = plan.weekend_days ? String(plan.weekend_days).split(',').map(Number) : [0, 6]
const active = current.includes(day)
return (
<button key={day} onClick={() => {
const next = active ? current.filter(d => d !== day) : [...current, day]
updatePlan({ weekend_days: next.join(',') })
}}
style={{
padding: '4px 10px', borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: 'pointer',
fontFamily: 'inherit', border: '1px solid', transition: 'all 0.12s',
background: active ? 'var(--text-primary)' : 'var(--bg-card)',
borderColor: active ? 'var(--text-primary)' : 'var(--border-primary)',
color: active ? 'var(--bg-primary)' : 'var(--text-muted)',
}}>
{label}
</button>
)
})}
</div>
</div>
)}
{/* Carry-over */}
<SettingToggle
icon={ArrowRightLeft}

View File

@@ -103,10 +103,9 @@ export function getHolidays(year: number, bundesland: string = 'NW'): Record<str
return holidays
}
export function isWeekend(dateStr: string): boolean {
export function isWeekend(dateStr: string, weekendDays: number[] = [0, 6]): boolean {
const d = new Date(dateStr + 'T00:00:00')
const day = d.getDay()
return day === 0 || day === 6
return weekendDays.includes(d.getDay())
}
export function getWeekday(dateStr: string): string {

View File

@@ -196,7 +196,7 @@ router.get('/plan', (req: Request, res: Response) => {
router.put('/plan', async (req: Request, res: Response) => {
const authReq = req as AuthRequest;
const planId = getActivePlanId(authReq.user.id);
const { block_weekends, holidays_enabled, holidays_region, company_holidays_enabled, carry_over_enabled } = req.body;
const { block_weekends, holidays_enabled, holidays_region, company_holidays_enabled, carry_over_enabled, weekend_days } = req.body;
const updates: string[] = [];
const params: (string | number)[] = [];
@@ -205,6 +205,7 @@ router.put('/plan', async (req: Request, res: Response) => {
if (holidays_region !== undefined) { updates.push('holidays_region = ?'); params.push(holidays_region); }
if (company_holidays_enabled !== undefined) { updates.push('company_holidays_enabled = ?'); params.push(company_holidays_enabled ? 1 : 0); }
if (carry_over_enabled !== undefined) { updates.push('carry_over_enabled = ?'); params.push(carry_over_enabled ? 1 : 0); }
if (weekend_days !== undefined) { updates.push('weekend_days = ?'); params.push(String(weekend_days)); }
if (updates.length > 0) {
params.push(planId);