fix: show the out-of-office calendar choice the way the organisation's lock applies it

This commit is contained in:
Linus Rath
2026-11-01 06:42:37 +01:00
parent cbae82b2f0
commit e7c8df01fd
2 changed files with 20 additions and 5 deletions
@@ -26,7 +26,7 @@ const client = {
function lock(value: boolean | null) {
const policy = usePolicyStore.getState().policy;
usePolicyStore.setState({ policy: { ...policy, restrictions: value === null ? {} : { vacationBlockCalendar: { locked: true, value } } } });
usePolicyStore.setState({ policy: { ...policy, defaults: {}, restrictions: value === null ? {} : { vacationBlockCalendar: { locked: true, value } } } });
}
describe('VacationCalendarBlock', () => {
@@ -61,6 +61,18 @@ describe('VacationCalendarBlock', () => {
expect(box.closest('[inert]')).not.toBeNull();
});
it('shows what the sync does for a lock without a value, and nothing when hidden', async () => {
const policy = usePolicyStore.getState().policy;
usePolicyStore.setState({ policy: { ...policy, defaults: { vacationBlockCalendar: false }, restrictions: { vacationBlockCalendar: { locked: true } } } });
const view = render(<VacationCalendarBlock hasDates />);
expect(((await screen.findByTestId('vacation-block-calendar')) as HTMLInputElement).checked).toBe(false);
view.unmount();
usePolicyStore.setState({ policy: { ...policy, defaults: {}, restrictions: { vacationBlockCalendar: { hidden: true } } } });
const hidden = render(<VacationCalendarBlock hasDates />);
await waitFor(() => expect(client.getAllCalendars).toHaveBeenCalled());
expect(hidden.container.querySelector('[data-testid="vacation-block-calendar"]')).toBeNull();
});
it('says a managed account\'s calendar can\'t be blocked without write access', async () => {
client.getAllCalendars.mockResolvedValueOnce([{ id: 'boss:b', isDefault: true, isShared: true, accountId: 'boss', myRights: { mayWriteAll: false, mayWriteOwn: false } }] as never);
render(<VacationCalendarBlock hasDates accountId="boss" />);
@@ -8,7 +8,8 @@ import { useAuthStore } from '@/stores/auth-store';
import { useSettingsStore } from '@/stores/settings-store';
import { useVacationStore } from '@/stores/vacation-store';
import { useRestriction } from '@/hooks/use-restriction';
import { blockCalendar, ownFilterScriptStops, vacationBlockChanged } from '@/lib/vacation-calendar';
import { blockCalendar, ownFilterScriptStops, resolveBlockSetting, vacationBlockChanged } from '@/lib/vacation-calendar';
import { usePolicyStore } from '@/stores/policy-store';
/**
* "Block my calendar" under the out-of-office dates (lib/vacation-calendar.ts):
@@ -22,7 +23,9 @@ export function VacationCalendarBlock({ hasDates, accountId }: { hasDates: boole
const setting = useSettingsStore((s) => s.vacationBlockCalendar);
const updateSetting = useSettingsStore((s) => s.updateSetting);
const restriction = useRestriction('vacationBlockCalendar');
const checked = restriction.locked && typeof restriction.value === 'boolean' ? restriction.value : setting !== false;
const policyDefault = usePolicyStore((s) => s.policy.defaults?.vacationBlockCalendar);
// The same resolution as the sync (lib/vacation-calendar.ts).
const { value: checked, locked, hidden } = resolveBlockSetting(restriction, policyDefault, setting);
const [access, setAccess] = useState<{ key: string; ok: boolean } | null>(null);
const id = useId();
const key = accountId ?? '';
@@ -40,7 +43,7 @@ export function VacationCalendarBlock({ hasDates, accountId }: { hasDates: boole
return () => { cancelled = true; };
}, [client, supported, hasDates, accountId, key]);
if (!client || !supported || !hasDates) return null;
if (!client || !supported || !hasDates || hidden) return null;
if (access?.key === key && !access.ok) {
return (
<p className="pb-2 text-xs text-muted-foreground" data-testid="vacation-block-no-access">
@@ -56,7 +59,7 @@ export function VacationCalendarBlock({ hasDates, accountId }: { hasDates: boole
};
return (
<SettingItem label={t('label')} description={t('description')} locked={restriction.locked}>
<SettingItem label={t('label')} description={t('description')} locked={locked}>
<input
id={id}
type="checkbox"