Files

1084 lines
80 KiB
HTML
Raw Permalink Normal View History

2026-09-24 14:35:05 +03:00
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title data-title-key="meguri_on_call_together_f7894c"></title>
<link rel="stylesheet" href="/assets/app.css">
<script defer src="/assets/i18n.js"></script>
<script defer src="/assets/cytoscape.js"></script>
<script defer src="/assets/generation-map.js"></script>
<script defer src="/assets/generation.js"></script>
<script defer src="/assets/app.js">
</script>
<script defer src="/assets/alpine.js">
</script>
</head>
<body x-data="meguri" :class="{'demo-mode':demo}">
<div class="demo-banner" x-ref="demoBanner" x-cloak x-show="demo" role="status" :aria-label="t('demo_data_retention_e272aa')"><div><strong><span x-text="t('demo_temporary_data_8112dd')"></span></strong><span x-text="demoLifecycle?t('teams_workspaces_332acb')+ttlText(demoLifecycle.retention.workspaces)+t('requests_0e1805')+ttlText(demoLifecycle.retention.requests)+t('visitor_accounts_f4ac15')+ttlText(demoLifecycle.retention.accounts):''"></span><span><span x-text="t('restart_resets_demo_data_examples_refresh_automatica_0082d1')"></span></span></div><p class="demo-protection" x-show="demoLifecycle?.isolated"><strong><span x-text="t('protected_database_isolated_demo_active_2e4ba8')"></span></strong> <span x-text="t('the_configured_database_was_not_verified_as_demo_and_c1a54e')"></span></p></div>
<datalist id="timezones">
<template x-for="zone in zones">
<option :value="zone">
</option>
</template>
</datalist>
<div class="loading" x-show="!ready"><span x-text="t('loading_meguri_856b9a')"></span></div>
<div class="toast" role="status" x-cloak x-show="toast" x-text="l(toast)">
</div>
<div class="error" role="alert" x-cloak x-show="error">
<span x-text="l(error)">
</span>
<button @click="error=''" :aria-label="t('dismiss_error_347aaa')"><span x-text="t('symbol_67fba2')"></span></button>
</div>
<main class="login" x-cloak x-show="ready && !user">
<div class="login-story">
<a class="brand" href="/"><span x-text="t('symbol_4d82f1')"></span> <span><span x-text="t('meguri_d160e8')"></span></span>
<small class="app-version" x-show="version" x-text="t('app_version_label',{version})"></small>
2026-09-24 14:35:05 +03:00
</a>
<div>
<span class="eyebrow"><span x-text="t('on_call_in_good_hands_f1cf2d')"></span></span>
<h1><span x-text="t('a_clear_rhythm_c58fae')"></span><br><span x-text="t('a_shared_responsibility_2c5e48')"></span></h1>
<p><span x-text="t('one_calendar_for_your_team_thoughtful_rotations_simp_52aff3')"></span></p>
</div>
<span class="quiet"><span x-text="t('plan_here_stay_in_sync_everywhere_70e528')"></span></span>
</div>
<div class="login-form">
2026-09-24 14:35:05 +03:00
<span class="eyebrow"><span x-text="t('your_team_s_home_for_on_call_66a895')"></span></span>
<h2 x-text="t('welcome_back_b80783')"></h2>
2026-09-24 14:35:05 +03:00
<p class="muted"><span x-text="t('make_space_for_a_smoother_handover_44adaf')"></span></p>
<div class="login-error" role="alert" x-cloak x-show="loginError"><span x-text="loginError ? t('access_login_error_'+loginError.code) : ''"></span><small x-show="loginError?.ref" x-text="loginError?.ref ? t('access_support_reference',{ref:loginError.ref}) : ''"></small></div>
<a class="button primary sso-button" href="/api/oidc/login" x-cloak x-show="auth.oidc"><span x-text="t('access_sso_button')"></span></a>
<div class="login-divider" x-cloak x-show="auth.oidc && auth.password"><span x-text="t('access_or_divider')"></span></div>
<form class="password-login" @submit.prevent="signIn" x-cloak x-show="auth.password">
2026-09-24 14:35:05 +03:00
<label><span x-text="t('username_84c290')"></span><input autocomplete="username" x-model="loginForm.username" required maxlength="64">
</label>
<label><span x-text="t('password_8be3c9')"></span><input type="password" autocomplete="current-password" x-model="loginForm.password" required>
2026-09-24 14:35:05 +03:00
</label>
<button class="primary" :disabled="busy" x-text="t('sign_in_ada2e9')">
2026-09-24 14:35:05 +03:00
</button>
</form>
<p class="info-note" x-cloak x-show="!auth.password && !auth.oidc"><span x-text="t('access_no_sign_in_method')"></span></p>
<div class="demo-note" x-cloak x-show="demo && demoLifecycle?.accounts?.length"><strong><span x-text="t('access_demo_accounts')"></span></strong>
<ul class="demo-accounts">
<template x-for="account in demoLifecycle?.accounts || []" :key="account.username">
<li><button type="button" class="text demo-account" :data-demo-account="account.username" :aria-label="t('access_demo_use_account',{username:account.username})" @click="useDemoAccount(account)"><code x-text="account.username"></code><span x-text="t('access_role_'+account.role)"></span></button></li>
</template>
</ul>
<span class="demo-password"><span x-text="t('access_demo_password')"></span> <code data-demo-password x-text="demoLifecycle?.password"></code> <button type="button" class="text" @click="copyDemoPassword()"><span x-text="t('access_demo_copy_password')"></span></button></span>
<small x-text="t('access_demo_restart_hint')"></small></div>
</div>
2026-09-24 14:35:05 +03:00
</main>
<div class="shell" :inert="!!modal" x-cloak x-show="ready && user">
<aside class="sidebar">
<a class="brand" href="/"><span x-text="t('symbol_4d82f1')"></span> <span><span x-text="t('meguri_d160e8')"></span></span>
<small class="app-version" x-show="version" x-text="t('app_version_label',{version})"></small>
2026-09-24 14:35:05 +03:00
</a>
<span class="eyebrow workspace-label"><span x-text="t('workspace_703988')"></span></span>
<select class="workspace-select" :aria-label="t('team_218887')" x-model="selected" :disabled="busy" @change="changeWorkspace()">
<option value="" x-show="!directoryState.workspaces.length" x-text="t('choose_a_team_1853ee')"></option>
<template x-for="t in directoryState.workspaces" :key="t.id">
<option :value="t.id" :selected="selected===(t.id)" x-text="t.name">
</option>
</template>
</select>
<nav :aria-label="t('main_navigation_efd197')">
<template x-for="item in nav.filter(n=>['calendar','requests','activity'].includes(n.id))" :key="item.id">
<a :href="menuHref(item.id)" :aria-current="view===item.id?'page':null" :class="{active:view===item.id}" @click="menuClick($event,item.id)">
<span aria-hidden="true" class="nav-icon" x-text="item.icon">
</span>
<span x-text="item.name">
</span>
<span class="counter" x-show="item.id==='requests' && pending.length" x-text="pending.length">
</span>
</a>
</template>
<button class="setup-toggle" :aria-label="t('setup_cdd7bb')" :class="{active:setupView}" @click="setupOpen=!setupOpen" :aria-expanded="setupOpen"><span class="nav-icon"><span x-text="t('symbol_be22a3')"></span></span><span><span x-text="t('setup_cdd7bb')"></span></span><span class="grow"></span><span x-text="setupOpen?'−':'+'"></span></button>
<div class="setup-nav" x-show="setupOpen"><template x-for="item in nav.filter(n=>['configuration','settings'].includes(n.id))" :key="item.id"><a :href="menuHref(item.id)" :aria-current="view===item.id?'page':null" :class="{active:view===item.id}" @click="menuClick($event,item.id)" x-text="item.name"></a></template></div>
<a class="instance-link" x-cloak x-show="user?.instance_admin" :href="menuHref('instance')" :aria-current="view==='instance'?'page':null" :class="{active:view==='instance'}" @click="menuClick($event,'instance')"><span aria-hidden="true" class="nav-icon" x-text="nav.find(n=>n.id==='instance').icon"></span><span x-text="nav.find(n=>n.id==='instance').name"></span></a>
2026-09-24 14:35:05 +03:00
</nav>
<div class="sidebar-bottom">
<button class="account-button" @click="open('account')">
<span class="avatar teal" x-text="user?.display_name?.slice(0,1)">
</span>
<span>
<strong x-text="user?.display_name">
</strong>
<small x-text="'@'+user?.username">
</small>
</span>
<span><span x-text="t('symbol_be22a3')"></span></span>
</button>
<button class="text signout" @click="signOut"><span x-text="t('sign_out_dc1649')"></span></button>
</div>
</aside>
<main class="main">
<header class="topbar">
<select class="mobile-workspace-select" :aria-label="t('choose_team_9c6970')" x-model="selected" :disabled="busy" @change="changeWorkspace()">
<template x-for="t in directoryState.workspaces" :key="t.id">
<option :value="t.id" :selected="selected===(t.id)" x-text="t.name">
</option>
</template>
</select>
<span>
<strong x-text="doc?.name || t('getting_started_76e7a3')">
</strong>
</span>
<div class="top-actions">
<span class="display-zone" x-text="t('your_time_31bdbf')+displayZone"></span>
</div>
</header>
<div class="content">
<template x-if="view==='instance' && user?.instance_admin">
<section class="instance-page" id="instance">
<div class="page-heading"><div><div class="eyebrow" x-text="t('access_instance_eyebrow')"></div><h1 x-text="t('access_instance_title')"></h1></div></div>
<div class="warning-banner" role="alert" x-show="admin?.config.bootstrap_usernames.length"><span x-text="t('access_bootstrap_warning')"></span></div>
<template x-if="admin">
<div>
<article class="panel rule-card spaced" id="instance-config">
<span class="eyebrow"><span x-text="t('access_config_title')"></span></span>
<dl class="config-summary">
<div><dt x-text="t('access_config_password_login')"></dt><dd x-text="admin.config.password_login?t('access_enabled'):t('access_disabled')"></dd></div>
<div><dt x-text="t('access_config_oidc')"></dt><dd x-text="admin.config.oidc_issuer || t('access_not_configured')"></dd></div>
<div><dt x-text="t('access_config_admin_groups')"></dt><dd x-text="admin.config.instance_admin_groups.join(', ') || t('access_none')"></dd></div>
<div><dt x-text="t('access_config_bootstrap')"></dt><dd x-text="admin.config.bootstrap_usernames.join(', ') || t('access_none')"></dd></div>
</dl>
<p class="muted"><span x-text="t('access_config_note')"></span></p>
</article>
<div class="section-line"><h2><span x-text="t('access_workspaces_title')"></span></h2></div>
<div class="panel compact" id="instance-workspaces">
<p class="muted padded" x-show="!admin.workspaces.length"><span x-text="t('access_workspaces_empty')"></span></p>
<template x-for="w in admin.workspaces" :key="w.id">
<div class="list-row admin-workspace" :class="{disabled:w.disabled_at}" :data-workspace-key="w.key">
<div class="grow">
<strong x-text="w.name"></strong>
<small x-text="t('access_workspace_key',{key:w.key || '—'})+' · '+t('access_workspace_members',{count:w.member_count})+(w.sync_state?' · '+t('access_workspace_publication',{state:w.sync_state}):'')"></small>
<small x-show="w.in_config"><span x-text="t('access_groups_admin')+': '+(w.admin_groups.join(', ') || t('access_none'))"></span></small>
<small x-show="w.in_config"><span x-text="t('access_groups_member')+': '+(w.member_groups.join(', ') || t('access_none'))"></span></small>
<small class="disabled-note" x-show="w.disabled_at" x-text="t('access_workspace_disabled')+' · '+t('access_workspace_disabled_since',{date:formatTime(w.disabled_at)})"></small>
</div>
<span class="badge" x-show="w.in_config" x-text="t('access_workspace_in_config')"></span>
<button class="danger-button" x-show="w.disabled_at" @click="open('admin-delete',{id:w.id,name:w.name,expected:w.confirm,confirm:''})"><span x-text="t('access_delete_workspace')"></span></button>
</div>
</template>
</div>
<div class="section-line"><h2><span x-text="t('access_events_title')"></span></h2></div>
<article class="panel rule-card spaced" id="instance-events">
<p class="muted"><span x-text="t('access_events_note')"></span></p>
<form class="event-search" @submit.prevent="run(()=>loadEvents())">
<input type="search" x-model="eventSearch" maxlength="200" :aria-label="t('access_events_search_label')" :placeholder="t('access_events_search_placeholder')">
<button class="primary" :disabled="busy"><span x-text="t('access_events_search')"></span></button>
<button type="button" :disabled="busy" @click="run(()=>loadEvents())"><span x-text="t('access_events_refresh')"></span></button>
</form>
<p class="muted padded" data-events-empty x-show="!events.length && !eventsLoading"><span x-text="t('access_events_empty')"></span></p>
<div class="event-list">
<template x-for="e in events" :key="e.id">
<div class="event-row" :class="{failed:isFailedLogin(e)}" :data-event-action="e.action">
<div class="event-head"><code class="event-action" :class="{failed:isFailedLogin(e)}" x-text="e.action"></code><span class="event-actor" x-text="e.actor || t('access_event_system')"></span><small class="event-time" x-text="formatTime(e.created_at)"></small></div>
<dl class="event-details">
<template x-for="d in eventDetails(e)" :key="d.key">
<div><dt x-text="eventLabel(d.key)"></dt><dd><template x-if="d.chips"><span class="chips"><template x-for="chip in d.chips" :key="chip"><code class="chip" x-text="chip"></code></template><small class="muted" x-show="d.empty" x-text="t('access_user_no_groups')"></small></span></template><template x-if="!d.chips"><span x-text="d.value"></span></template></dd></div>
</template>
</dl>
</div>
</template>
</div>
<p class="info-note" data-events-more x-show="eventsMore"><span x-text="t('access_events_more')"></span></p>
</article>
<div class="section-line"><h2><span x-text="t('access_users_title')"></span></h2>
<button class="primary" @click="open('admin-user')"><span x-text="t('access_add_user')"></span></button>
</div>
<div class="panel compact" id="instance-users">
<p class="muted padded" x-show="!admin.users.length"><span x-text="t('access_users_empty')"></span></p>
<template x-for="u in admin.users" :key="u.id">
<div class="list-row admin-user" :data-username="u.username">
<span class="avatar" x-text="u.display_name.slice(0,1)"></span>
<div class="grow">
<strong x-text="u.display_name"></strong> <small class="inline" x-show="u.id===user.id"><span x-text="t('you_8af56d')"></span></small>
<small x-text="'@'+u.username+(u.email?' · '+u.email:'')"></small>
<small x-text="t('access_user_groups')+': '+(u.groups.join(', ') || t('access_user_no_groups'))"></small>
<small class="muted" x-show="u.sso"><span x-text="t('access_groups_sso_readonly')"></span></small>
<small class="muted" x-show="u.bootstrap"><span x-text="t('access_groups_bootstrap_readonly')"></span></small>
</div>
<span class="badge" x-show="u.instance_admin" x-text="t('access_role_instance_admin')"></span>
<span class="badge" x-show="u.sso" x-text="t('access_badge_sso')"></span>
<span class="badge" x-show="u.bootstrap" x-text="t('access_badge_bootstrap')"></span>
<span class="badge" x-show="!u.sso && !u.bootstrap" x-text="t('access_badge_password')"></span>
<button x-show="canEditUserGroups(u)" @click="open('admin-groups',{id:u.id,username:u.username,groups:[...u.groups]})"><span x-text="t('access_edit_groups')"></span></button>
<button x-show="!u.sso && !u.bootstrap" @click="open('admin-password',{id:u.id,username:u.username,password:''})"><span x-text="t('access_reset_password')"></span></button>
</div>
</template>
</div>
</div>
</template>
2026-09-24 14:35:05 +03:00
</section>
</template>
<template x-if="view!=='instance' && !workspace">
<section class="empty welcome">
<span class="empty-symbol"><span x-text="t('symbol_4d82f1')"></span></span>
<h1><span x-text="t('access_no_workspace_title')"></span></h1>
<p x-text="user?.instance_admin?t('access_no_workspace_admin_text'):t('access_no_workspace_text')"></p>
<a class="button primary" x-show="user?.instance_admin" :href="menuHref('instance')" @click="menuClick($event,'instance')"><span x-text="t('access_open_instance_page')"></span></a>
</section>
</template>
<template x-if="view!=='instance' && workspace">
2026-09-24 14:35:05 +03:00
<div>
<div class="page-heading">
<div>
<div class="eyebrow" x-text="setupView?t('team_setup_504738'):t('your_team_303d2a')"></div>
<h1 x-text="title">
</h1>
</div>
<div class="actions" x-show="view==='calendar'"><button @click="open('absence')" x-show="can('request_overrides')"><span x-text="t('plan_time_off_33c0fc')"></span></button><button class="primary" x-show="can('request_overrides') || can('force_overrides')" @click="nextOwnDuty()"><span x-text="t('ask_for_cover_53c1cd')"></span></button></div>
</div>
2026-10-07 15:30:31 +01:00
<div class="publication-review" data-publication-review>
<div class="publication-review-heading"><strong x-text="t('setup_review_changes')"></strong><div class="actions publication-actions"><button type="button" @click="openTransfer()" :disabled="busy || generationSaving" x-show="can('edit_schedules')" x-text="t('transfer_title')"></button><button @click="reloadSharedGeneration()" :disabled="busy || generationSaving" x-show="can('edit_schedules')" x-text="t('reload_saved_state_1b1cb9')"></button><button @click="applyGeneration()" :disabled="busy || !generationCanApply()" x-show="can('edit_schedules')" x-text="t('apply_checked_changes_efa2a4')"></button><button class="publication-sync" :class="{'sync-needed':publicationDirty()}" x-show="can('manage_integration')" @click="retry" :disabled="busy || generationSaving || !!generationSaveError || syncSubmitting || publicationPending() || detail?.sync?.publication_busy"><span x-text="t(syncSubmitting?'publication_submitting':publicationPending()?'publication_pending_button':detail?.sync?.publication_busy?'publication_running_button':'sync_now_2b7d93')"></span></button></div></div>
<div class="publication-draft-note"><span x-text="generationSaveError || ((generationSaving || generationUnsaved())?t('shared_draft_saving'):publicationDirty()?t('publication_waiting_click'):t('publication_no_changes'))"></span></div>
<p class="muted publication-workflow" x-text="t('publication_workflow_hint')"></p>
<p class="publication-draft-note" x-show="detail?.sync?.publication_busy" x-text="t('publication_lease_wait',{date:detail?.sync?.lease_until?formatTime(detail.sync.lease_until):''})"></p>
<p class="publication-draft-note" x-show="detail?.sync?.state==='publishing' &amp;&amp; !detail?.sync?.publication_busy" x-text="t('publication_lease_expired')"></p>
<p class="publication-draft-note" role="status" x-show="publicationPending()" x-text="t('publication_pending_hint')"></p>
<!-- GENERATION_PREVIEW -->
2026-09-24 14:35:05 +03:00
</div>
<section class="coverage-problems panel" x-show="detail?.coverage?.total">
<h2 x-text="t('coverage_attention')"></h2>
<p class="muted" x-text="t('coverage_publication_wait')"></p>
<div class="coverage-issue-list"><template x-for="(gap,index) in coverageItems" :key="index">
<div class="coverage-issue" :class="{'is-pending':gap.awaiting}">
<div class="coverage-issue-body"><span class="coverage-state" x-text="t(gap.awaiting?'coverage_waiting':'coverage_missing')"></span>
<strong x-text="person(gap.user_id)+' · '+scheduleName(gap.schedule_id)"></strong>
<small class="coverage-times"><span x-text="formatTime(gap.start)"></span><span x-text="t('symbol_213edd')"></span><span x-text="formatTime(gap.end)"></span></small>
<small x-show="gap.awaiting" x-text="t('coverage_substitute',{names:gap.substitutes.map(id=>person(id)).join(', ')})"></small>
<small x-show="gap.awaiting" x-text="t('coverage_waiting_for',{names:gap.approvers.map(id=>person(id)).join(', ')})"></small></div>
<button x-show="gap.awaiting" @click="view='requests'" x-text="t('coverage_open_requests')"></button>
<button x-show="!gap.awaiting" @click="locateCoverage(gap)" x-text="t('open_affected_day')"></button>
</div></template></div></section>
<section x-show="view==='calendar'">
<div class="daily-overview">
<div class="today-card"><span class="eyebrow"><span x-text="t('on_call_now_0ec724')"></span> <span x-text="selectedSchedule?.name"></span></span><div class="person-line"><span class="avatar" :class="color(nowAssignment?.user_id)" x-text="nowAssignment?initials(nowAssignment.user_id):'—'"></span><div><strong x-text="nowAssignment?person(nowAssignment.user_id):t('no_coverage_252d1f')"></strong><small x-text="nowAssignment?t('until_24bf03')+formatTime(nowAssignment.end):t('choose_a_schedule_below_dea8d5')"></small></div></div></div>
<div class="today-card"><span class="eyebrow"><span x-text="t('your_next_shift_next_4_weeks_0e4beb')"></span></span><strong x-text="nextDuty?scheduleName(nextDuty.schedule_id):t('no_shifts_in_this_view_e878f4')"></strong><small x-text="nextDuty?formatTime(nextDuty.start)+' → '+formatTime(nextDuty.end):t('enjoy_your_time_off_44297f')"></small></div>
</div>
<section class="attention-panel" x-show="needsMe.length" :aria-label="t('needs_your_reply_9e26e0')"><div class="section-line"><h2><span x-text="t('needs_your_reply_9e26e0')"></span> <span class="badge" x-text="needsMe.length"></span></h2><button class="text" @click="view='requests'"><span x-text="t('all_requests_63246c')"></span></button></div>
<template x-for="r in needsMe" :key="r.id"><article class="reply-row"><span class="avatar" :class="color(r.requested_by)" x-text="initials(r.requested_by)"></span><div class="grow"><strong x-text="requestTitle(r)"></strong><template x-for="leg in r.legs"><small x-text="scheduleName(leg.schedule_id)+' · '+formatTime(leg.start)+' → '+formatTime(leg.end)"></small></template><p class="action-comment" x-show="r.reason" x-text="r.reason"></p></div><div class="actions"><button @click="decide(r,'reject')" :disabled="busy"><span x-text="t('decline_b59cf9')"></span></button><button class="primary" @click="decide(r,'approve')" :disabled="busy"><span x-text="t('accept_bb54db')"></span></button></div></article></template></section>
<div class="waiting-line" x-show="myRequests.length"><span x-text="t('waiting_count',{count:myRequests.length})"></span><button class="text" @click="view='requests'"><span x-text="t('view_cf3dbd')"></span></button></div>
<div class="panel calendar-panel">
<div class="calendar-toolbar">
<div>
<h2 x-text="monthTitle">
</h2>
<span class="muted"><span x-text="t('select_a_day_to_arrange_cover_510f60')"></span></span>
</div>
<div class="toolbar-controls">
<div class="schedule-control"><button class="schedule-trigger" @click="schedulePicker=!schedulePicker" :aria-expanded="schedulePicker"><span x-text="selectedSchedule?.name || t('choose_schedule_33049c')"></span> <span x-text="t('symbol_00d74e')"></span></button><div class="schedule-picker" x-show="schedulePicker" @click.outside="schedulePicker=false"><input :aria-label="t('search_schedules_235648')" type="search" x-model="scheduleSearch" :placeholder="t('search_schedules_85f641')"><div class="schedule-results"><template x-for="s in doc.schedules.filter(s=>s.name.toLowerCase().includes(scheduleSearch.toLowerCase()))" :key="s.id"><button @click="chooseSchedule(s.id)" :class="{chosen:s.id===scheduleId}" x-text="s.name"></button></template></div></div><select class="sr-only" tabindex="-1" :aria-label="t('schedule_0a8ada')" x-model="scheduleId" @change="run(()=>loadCalendar())">
<template x-for="s in doc.schedules" :key="s.id">
<option :value="s.id" :selected="scheduleId===(s.id)" x-text="s.name">
</option>
</template>
</select></div>
<div class="segmented">
<button :aria-label="t('previous_four_weeks_37f96a')" @click="navigateDays(-28)"><span x-text="t('symbol_0dccf9')"></span></button>
<button @click="navigateDays(0)"><span x-text="t('today_24345a')"></span></button>
<button :aria-label="t('next_four_weeks_08a1b8')" @click="navigateDays(28)"><span x-text="t('symbol_60ecbc')"></span></button>
</div>
</div>
</div>
<div class="weekdays">
<template x-for="day in calendar.days.slice(0,7)">
<span x-text="weekday(day)">
</span>
</template>
</div>
<div class="calendar-grid">
<template x-for="day in calendar.days" :key="day.date">
<button class="day" :data-duty-date="day.date" :class="{today:day.date===today(),weekend:[0,6].includes(new Date(day.date+'T12:00Z').getUTCDay())}" @click="clickDay(day)" :title="dayInterval(day)" :aria-label="dayInterval(day)+' '+onDay(day).map(a=>person(a.user_id)).join(', ')">
<span class="day-date" x-text="dayNumber(day)">
</span>
<small class="day-clock" x-text="localClock(day.start)">
</small>
<span class="holiday" x-show="holidaysOn(day)" x-text="holidaysOn(day)" :title="holidaysOn(day)">
</span>
<template x-for="a in onDay(day)" :key="a.schedule_id+a.start">
<span class="assignment" :class="color(a.user_id)" :title="assignmentInterval(a,day)">
<span class="mini-dot">
</span>
<span x-text="a.unavailable?t('uncovered_77617b')+shortName(a.user_id)+t('off_e362e3'):shortName(a.user_id)">
</span>
<span x-show="a.reason==='Approved replacement'"><span x-text="t('symbol_0380cf')"></span></span>
<span x-show="a.unavailable" :title="t('no_available_replacement_publication_is_blocked_144e92')"><span x-text="t('symbol_0ab831')"></span></span>
</span>
</template>
</button>
</template>
</div>
<div class="calendar-footer">
<span><span x-text="t('times_shown_in_1f96a9')"></span> <strong x-text="displayZone">
</strong>
</span>
<span><span x-text="t('approved_change_coverage_needs_attention_fc97bf')"></span></span>
</div>
</div>
<div class="section-line">
<h2><span x-text="t('your_team_80b301')"></span></h2>
<button class="text" @click="view='settings'"><span x-text="t('manage_team_68a698')"></span></button>
</div>
<div class="people-row">
<template x-for="m in members" :key="m.user?.id">
<span class="person-chip">
<span class="avatar small" :class="color(m.user?.id)" x-text="initials(m.user?.id)">
</span>
<span x-text="m.user.display_name">
</span>
<small x-show="m.user?.id===user?.id"><span x-text="t('you_8af56d')"></span></small>
</span>
</template>
</div>
</section>
<section x-show="view==='requests'">
<div class="section-line">
<p class="muted"><span x-text="t('replacements_take_effect_after_everyone_involved_con_a63d3a')"></span></p>
<button class="primary" @click="nextOwnDuty()" x-show="can('request_overrides') || can('force_overrides')"><span x-text="t('new_request_8a3714')"></span></button>
</div>
<div class="empty panel" x-show="!inbox.length">
<h2><span x-text="t('nothing_waiting_on_a_handover_1a3b46')"></span></h2>
<p><span x-text="t('your_team_s_replacements_and_swaps_will_appear_here_ee9501')"></span></p>
</div>
<template x-for="r in inbox" :key="r.id">
<article class="panel request-card">
<div class="section-line">
<strong x-text="requestTitle(r)">
</strong>
<span class="badge" :class="r.status" x-text="label(r.status)">
</span>
</div>
<p class="action-comment" x-show="r.reason" x-text="r.reason">
</p>
<template x-for="leg in r.legs">
<p class="leg">
<strong x-text="scheduleName(leg.schedule_id)">
</strong>
<span x-text="person(leg.from_user)+' → '+person(leg.to_user)">
</span>
<small x-text="formatTime(leg.start)+' — '+formatTime(leg.end)">
</small>
</p>
</template>
<small class="muted" x-text="t('created_05e8fd')+formatTime(r.created_at)">
</small>
<p class="muted" x-show="r.status==='pending'" x-text="t('waiting_for_f91441')+r.required_approvers.filter(id=>!r.approvals.includes(id)).map(id=>person(id)).join(', ')">
</p>
<p class="muted" x-show="r.forced_by" x-text="t('applied_without_confirmation_f694d2')+(r.force_reason || '')">
</p>
<div class="actions" x-show="r.status==='pending'">
<template x-if="r.required_approvers.includes(user?.id) && !r.approvals.includes(user?.id)">
<span>
<button class="primary" @click="decide(r,'approve')" :disabled="busy"><span x-text="t('accept_bb54db')"></span></button> <button @click="decide(r,'reject')" :disabled="busy"><span x-text="t('decline_b59cf9')"></span></button>
</span>
</template>
<button x-show="r.requested_by===user?.id" @click="decide(r,'cancel')" :disabled="busy"><span x-text="t('cancel_request_84837a')"></span></button>
<button x-show="can('force_overrides')" @click="open('force',{id:r.id,reason:''})"><span x-text="t('apply_without_confirmation_f0350d')"></span></button>
</div>
</article>
</template>
</section>
<!-- GENERATION_PAGE -->
<!-- RESOURCE_INVENTORY -->
<section x-show="view==='activity'">
<p class="muted" x-show="!can('view_audit')"><span x-text="t('your_role_does_not_include_access_to_audit_history_bb8e51')"></span></p>
<div class="panel compact" x-show="can('view_audit')">
<template x-for="event in detail.audit || []" :key="event.id">
<details class="audit-row">
<summary>
<span class="audit-dot">
</span>
<strong x-text="label(event.action)">
</strong>
<span x-text="event.actor_name || event.actor">
</span>
<time x-text="formatTime(event.created_at)">
</time>
</summary>
<pre x-text="auditDetails(event.resolved_details || event.details)"></pre>
<details class="audit-raw"><summary x-text="t('audit_raw_details')"></summary><pre x-text="JSON.stringify(event.details,null,2)"></pre></details>
</details>
</template>
</div>
</section>
<section x-show="view==='settings'">
<div class="two-columns">
<article class="panel rule-card">
<span class="eyebrow"><span x-text="t('team_preferences_440fbe')"></span></span>
<h2 x-text="doc.name">
</h2>
<p x-text="t('workspace_schedule_timezone_954c44')+doc.timezone">
</p>
<p x-text="t('planning_horizon_b249cc')+doc.horizon_days+t('days_e72d54')">
</p>
2026-10-07 14:22:37 +01:00
<button x-show="detail?.is_owner" @click="open('preferences')"><span x-text="t('edit_preferences_4cefcb')"></span></button>
2026-09-24 14:35:05 +03:00
</article>
<article class="panel rule-card">
<span class="eyebrow"><span x-text="t('connected_provider_f22440')"></span></span>
<h2 x-text="doc.integration.provider==='mock'?t('local_preview_8d4a3f'):t('jira_service_management_071f90')">
</h2>
<p class="muted"><span x-text="t('approved_changes_are_published_automatically_a715bf')"></span></p>
<div class="actions" x-show="can('manage_integration')">
<button @click="open('integration')"><span x-text="t('configure_792c81')"></span></button>
<button @click="preview"><span x-text="t('download_preview_30507d')"></span></button>
</div>
</article>
</div>
<div class="section-line">
<h2><span x-text="t('team_access_8fb357')"></span></h2>
</div>
<p class="info-note" x-show="detail?.instance_admin && !members.some(m=>m.user.id===user?.id)"><span x-text="t('access_instance_admin_note')"></span></p>
<div class="panel compact" id="workspace-members">
2026-09-24 14:35:05 +03:00
<template x-for="m in members" :key="m.user?.id">
<div class="list-row" :data-username="m.user.username">
2026-09-24 14:35:05 +03:00
<span class="avatar" :class="color(m.user?.id)" x-text="initials(m.user?.id)">
</span>
<div class="grow">
<strong x-text="m.user.display_name">
</strong>
<small x-text="'@'+m.user.username">
2026-09-24 14:35:05 +03:00
</small>
</div>
<span class="badge role-badge" :class="{admin:m.is_owner}" x-text="m.is_owner?t('access_role_workspace_admin'):t('access_role_employee')"></span>
2026-09-24 14:35:05 +03:00
</div>
</template>
</div>
<p class="muted"><span x-text="t('access_members_hint')"></span></p>
<article class="panel rule-card spaced" id="employee-permissions">
<span class="eyebrow"><span x-text="t('access_employee_permissions_title')"></span></span>
<p class="muted"><span x-text="t('access_employee_permissions_hint')"></span></p>
<ul class="permission-list">
<template x-for="grant in employeeGrantOptions" :key="grant.id">
<li :class="{on:(doc.member_permissions||[]).includes(grant.id)}" :data-permission="grant.id"><span aria-hidden="true" x-text="(doc.member_permissions||[]).includes(grant.id)?'✓':'·'"></span> <span x-text="grant.name"></span></li>
</template>
</ul>
<p class="info-note" x-text="(doc.member_permissions||[]).includes('force_overrides')?t('access_force_enabled'):t('access_force_disabled')"></p>
<div class="actions" x-show="can('manage_members')"><button @click="open('member-permissions')"><span x-text="t('access_edit_employee_permissions')"></span></button></div>
</article>
<article class="panel rule-card spaced" id="access-groups" x-show="detail?.access">
<span class="eyebrow"><span x-text="t('access_groups_title')"></span></span>
<dl class="config-summary">
<div><dt x-text="t('access_groups_admin')"></dt><dd x-text="(detail?.access?.admin_groups||[]).join(', ') || t('access_none')"></dd></div>
<div><dt x-text="t('access_groups_member')"></dt><dd x-text="(detail?.access?.member_groups||[]).join(', ') || t('access_none')"></dd></div>
</dl>
<p class="muted"><span x-text="t('access_groups_hint')"></span></p>
</article>
2026-09-24 14:35:05 +03:00
<div class="section-line">
<h2><span x-text="t('scheduling_directory_e8fdd4')"></span></h2>
<span class="muted"><span x-text="t('country_controls_public_holidays_defaults_to_cyprus_e828a2')"></span></span>
</div>
<div class="panel compact">
<template x-for="p in people" :key="p.id">
<div class="list-row">
<span class="avatar" :class="color(p.id)" x-text="initials(p.id)">
</span>
<div class="grow">
<strong x-text="p.display_name">
</strong>
<small x-text="(p.email || (p.username?'@'+p.username:t('no_email_configured_b309ce')))+' · '+countryName(p.country)+' · '+(p.user_id?t('workspace_user_95981a'):t('email_contact_e9e0b3'))">
</small>
</div>
2026-10-07 14:22:37 +01:00
<button x-show="detail?.is_owner" @click="open('person',p)"><span x-text="t('edit_person_d98516')"></span></button>
2026-09-24 14:35:05 +03:00
</div>
</template>
</div>
<article class="panel rule-card spaced">
<span class="eyebrow"><span x-text="t('workspace_time_a3e57f')"></span></span>
<h2><span x-text="t('working_interval_schedule_timezone_fc825f')"></span></h2>
<p x-text="workingSummary()">
</p>
<p class="muted"><span x-text="t('shared_by_every_team_rotations_use_this_timezone_wit_e06e20')"></span></p>
<button x-show="detail?.is_owner" @click="open('working')"><span x-text="t('edit_working_interval_290690')"></span></button>
</article>
<article class="panel rule-card spaced" x-show="detail?.is_owner">
<span class="eyebrow"><span x-text="t('workspace_policy_4437ae')"></span></span>
<h2><span x-text="t('changes_made_outside_meguri_98ff25')"></span></h2>
<p class="muted"><span x-text="t('applies_to_every_team_in_this_workspace_and_only_to__909c8c')"></span></p>
<label><span x-text="t('when_external_configuration_changes_18f056')"></span><select :value="doc?.overwrite_drift?'overwrite':'block'" @change="setDrift($event.target.value==='overwrite')">
<option value="block" x-text="t('pause_publication_and_report_a_conflict_default_5b9c4a')"></option>
<option value="overwrite" x-text="t('overwrite_external_changes_with_meguri_s_state_36db36')"></option>
</select>
</label>
</article>
</section>
</div>
</template>
</div>
<footer class="page-footer"><span x-text="t('meguri_8bdb60')"></span> <span><span x-text="t('good_rotations_start_with_a_shared_plan_2e3f86')"></span></span>
</footer>
</main>
</div>
<div class="modal-backdrop" x-cloak x-show="modal" @keydown.escape.window="picker?picker=null:(bindingEditor || rulePeopleTarget)?closeRuleEditor():modal=''" @click.self="modal=''">
2026-10-07 15:30:31 +01:00
<section class="modal" :class="{'wide-modal':['variable','generation-rule','generation-transfer','absence'].includes(modal),'constructor-modal':modal==='generation-rule'}" role="dialog" aria-modal="true" aria-labelledby="dialog-title" @keydown.tab="trapDialog($event)" x-ref="dialog">
2026-09-24 14:35:05 +03:00
<form @submit.prevent="save()">
<div class="modal-heading">
<div>
<span class="eyebrow" x-text="modal.startsWith('admin-')?t('access_instance_eyebrow'):['request','day','force'].includes(modal)?scheduleName(form.schedule_id || scheduleId):doc?.name || 'MEGURI'"></span>
<h2 id="dialog-title" x-text="modal==='request'?(form.force_mode?t('apply_without_approval_d72926'):form.from_user===user?.id?t('who_can_cover_for_you_6e894c'):t('cover_person',{name:shortName(form.from_user)})):modal==='day'?t('choose_a_shift_330348'):({'generation-transfer':t('transfer_title'),variable:t('workspace_variable_7abc57'),'generation-rule':t('generation_rule_d34ccf'),rotation:t('rotation_906fe5'),priority:t('ordered_project_coverage_cf12c3'),coverage:t('project_coverage_536388'),request:t('request_a_change_3d465f'),force:t('apply_without_confirmation_f0350d'),absence:t('planned_absence_bfcea7'),holiday:t('public_holiday_49ec03'),'holiday-import':t('import_public_holidays_8098e3'),'remove-schedule':t('remove_schedule_ef0a8a'),person:t('scheduling_person_1273c6'),working:t('workspace_working_interval_c7d250'),'member-permissions':t('access_employee_permissions_title'),'admin-user':t('access_new_user_title'),'admin-groups':t('access_edit_groups_title'),'admin-password':t('access_reset_password_title'),'admin-delete':t('access_delete_workspace_title'),account:t('your_account_4ab291'),policy:t('escalation_policy_bbb0f7'),route:t('routing_rule_b00b22'),integration:t('connected_provider_9702c0'),preferences:t('team_preferences_7fcab5')})[modal]">
2026-09-24 14:35:05 +03:00
</h2>
</div>
<button type="button" class="icon-button" @click="modal=''" :aria-label="t('close_dialog_7b2902')"><span x-text="t('symbol_67fba2')"></span></button>
</div>
<template x-if="picker"><div class="person-picker" role="region" :aria-label="t('choose_a_person_202007')">
<div class="section-line">
<h3 x-text="pickerEditing?t('person_details_0574fe'):t('choose_a_person_202007')">
</h3>
<button type="button" @click="picker=null"><span x-text="t('back_c32ae9')"></span></button>
</div>
<div x-show="!pickerEditing">
<label><span x-text="t('search_workspace_users_or_enter_email_b3922e')"></span><input x-ref="personSearch" x-model="personQuery" type="search" :placeholder="t('name_or_person_example_com_c4e3fd')">
</label>
<div class="picker-results">
<template x-for="p in pickerResults" :key="p.id">
<button type="button" class="picker-option" @click="pickPerson(p)">
<span class="avatar small" :class="color(p.id)" x-text="initials(p.id)">
</span>
<span>
<strong x-text="p.display_name">
</strong>
<small x-text="(p.email || (p.username?'@'+p.username:t('workspace_user_95981a')))+' · '+countryName(p.country)">
</small>
</span>
<span class="badge" x-text="p.user_id?t('user_9f8a23'):t('contact_b37456')">
</span>
</button>
</template>
<p class="muted" x-show="!pickerResults.length"><span x-text="t('no_matching_person_in_this_workspace_dcec7a')"></span></p>
</div>
<div class="email-contact" x-show="personQuery.includes('@') && !people.some(p=>p.email?.toLowerCase()===personQuery.trim().toLowerCase())">
<h3><span x-text="t('add_by_email_9368f3')"></span></h3>
<p class="muted"><span x-text="t('they_do_not_need_a_meguri_account_adding_a_contact_d_15a721')"></span></p>
<label><span x-text="t('name_optional_9c9f03')"></span><input x-model="pickerName" :placeholder="t('displayed_in_the_calendar_923a0e')">
</label>
<label><span x-text="t('country_d523eb')"></span><select :aria-label="t('country_d523eb')" x-model="pickerCountry">
<template x-for="country in countries" :key="country.code">
<option :value="country.code" :selected="pickerCountry===country.code" x-text="country.name">
</option>
</template>
</select>
</label>
<button type="button" class="primary" @click="addEmailPerson()" x-text="t('add_e2b431')+personQuery.trim()">
</button>
</div>
</div>
<div x-show="pickerEditing">
2026-10-07 15:30:31 +01:00
<label><span x-text="t('name_709a23')"></span><input x-model="pickerName" :readonly="!!people.find(p=>p.id===pickerEditing)?.user_id">
2026-09-24 14:35:05 +03:00
</label>
2026-10-07 15:30:31 +01:00
<label><span x-text="t('email_for_jsm_lookup_193cfb')"></span><input type="email" x-model="pickerEmail" :readonly="!!people.find(p=>p.id===pickerEditing)?.user_id">
2026-09-24 14:35:05 +03:00
</label>
2026-10-07 15:30:31 +01:00
<label><span x-text="t('scheduling_country_393322')"></span><select :aria-label="t('scheduling_country_393322')" x-model="pickerCountry" :disabled="!!people.find(p=>p.id===pickerEditing)?.user_id">
2026-09-24 14:35:05 +03:00
<template x-for="country in countries" :key="country.code">
<option :value="country.code" :selected="pickerCountry===country.code" x-text="country.name">
</option>
</template>
</select>
</label>
<button type="button" class="primary" @click="savePersonDetails()"><span x-text="t('use_these_details_775a68')"></span></button>
2026-10-07 15:30:31 +01:00
<p class="info-note" x-show="people.find(p=>p.id===pickerEditing)?.user_id" x-text="t('account_person_authoritative')"></p>
2026-09-24 14:35:05 +03:00
</div>
</div>
</template><div x-show="!picker">
<template x-if="modal==='account'">
<div>
<label><span x-text="t('language')"></span><select x-model="form.language" :aria-label="t('language')"><option value="" x-text="t('language_auto')"></option><template x-for="code in I18n.supported"><option :value="code" :selected="form.language===code" x-text="t('language_'+code)"></option></template></select></label>
<label x-show="user?.email"><span x-text="t('account_email')"></span><input type="email" :value="user?.email" readonly></label>
2026-09-24 14:35:05 +03:00
<label><span x-text="t('display_name_c7874a')"></span><input x-model="form.display_name" required>
</label>
<label><span x-text="t('country_of_work_88453b')"></span><select :aria-label="t('country_of_work_88453b')" x-model="form.country">
<template x-for="country in countries" :key="country.code">
<option :value="country.code" :selected="form.country===country.code" x-text="country.name">
</option>
</template>
</select>
</label>
<label><span x-text="t('display_timezone_2425c3')"></span><input list="timezones" x-model="form.timezone" :placeholder="t('browser_default_e90df2')+browserZone" :aria-label="t('display_timezone_2425c3')">
</label>
<button type="button" class="text" @click="form.timezone=''"><span x-text="t('use_browser_timezone_3f54ba')"></span></button>
<p class="info-note" x-text="t('dates_and_timestamps_are_displayed_in_77ec74')+(form.timezone || browserZone)+t('this_never_changes_the_workspace_timezone_or_shifts_1de864')">
</p>
<p class="muted"><span x-text="t('country_changes_update_your_holiday_calendar_in_team_1e4eb8')"></span></p>
<template x-if="user?.has_password"><div>
2026-09-24 14:35:05 +03:00
<label><span x-text="t('current_password_19dff4')"></span><input type="password" autocomplete="current-password" x-model="form.current_password">
</label>
<label><span x-text="t('new_password_d850ee')"></span><input type="password" autocomplete="new-password" minlength="12" x-model="form.password">
</label>
</div></template>
<p class="muted" x-show="!user?.has_password"><span x-text="t('access_sso_password_note')"></span></p>
2026-09-24 14:35:05 +03:00
</div>
</template>
<template x-if="modal==='person'">
<div>
2026-10-07 15:30:31 +01:00
<label><span x-text="t('name_709a23')"></span><input x-model="form.display_name" required :readonly="!!form.user_id">
2026-09-24 14:35:05 +03:00
</label>
2026-10-07 15:30:31 +01:00
<label><span x-text="t('email_for_jsm_lookup_193cfb')"></span><input type="email" x-model="form.email" required :readonly="!!form.user_id">
2026-09-24 14:35:05 +03:00
</label>
2026-10-07 15:30:31 +01:00
<label><span x-text="t('scheduling_country_393322')"></span><select :aria-label="t('scheduling_country_393322')" x-model="form.country" :disabled="!!form.user_id">
2026-09-24 14:35:05 +03:00
<template x-for="country in countries" :key="country.code">
<option :value="country.code" :selected="form.country===country.code" x-text="country.name">
</option>
</template>
</select>
</label>
2026-10-07 15:30:31 +01:00
<p class="info-note" x-show="form.user_id" x-text="t('account_person_authoritative')"></p>
2026-09-24 14:35:05 +03:00
<p class="info-note"><span x-text="t('an_email_contact_can_be_scheduled_without_logging_in_34d74f')"></span></p>
</div>
</template>
<template x-if="modal==='working'">
<div>
<label><span x-text="t('workspace_iana_timezone_bea0e0')"></span><input list="timezones" x-model="form.timezone" required>
</label>
<div class="days-select">
<template x-for="(day,index) in [t('mon_24b2a0'),t('tue_529541'),t('wed_23408b'),t('thu_3593cc'),t('fri_bbd6e3'),t('sat_6b782d'),t('sun_48c98c')]">
<label class="check">
<input type="checkbox" :value="index+1" x-model="form.weekdays">
<span x-text="day">
</span>
</label>
</template>
</div>
<div class="two-columns">
<label><span x-text="t('working_day_starts_5418a6')"></span><input type="time" x-model="form.start" required>
</label>
<label><span x-text="t('working_day_ends_52d5f6')"></span><input type="time" x-model="form.end" required>
</label>
</div>
<p class="info-note"><span x-text="t('this_interval_is_shared_across_the_workspace_the_tim_07e765')"></span></p>
</div>
</template>
<template x-if="['rotation','priority'].includes(modal)">
<div>
<label><span x-text="t('schedule_name_fb63dc')"></span><input x-model="form.name" required>
</label>
<label><span x-text="t('handover_time_c9e315')"></span><input :aria-label="t('handover_time_c9e315')" type="time" x-model="form.handover" required>
</label>
<small x-text="t('schedule_time_in_911b55')+doc.timezone+t('each_shift_ends_when_the_next_begins_14ab16')">
</small>
<template x-if="['rotation','priority'].includes(modal)">
<div>
<template x-if="modal==='rotation'"><div class="two-columns">
<label><span x-text="t('anchor_date_e77bd6')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.anchor"><input type="text" :aria-label="t('anchor_date_e77bd6')" x-ref="textDate" :value="text" @input="edit($event.target)" :placeholder="t('dd_mm_yyyy_d7e5fe')" inputmode="numeric" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked()" tabindex="-1" aria-hidden="true"></span>
</label>
<label><span x-text="t('days_per_shift_83ec07')"></span><input type="number" min="1" max="366" x-model="form.shift_days" required>
</label>
</div></template>
<label><span x-text="modal==='priority'?t('responsible_people_in_priority_order_7205a5'):t('participants_in_rotation_order_4edee0')"></span></label>
<template x-for="(id,index) in form.participants" :key="id">
<div class="list-row">
<div class="grow">
<strong x-text="person(id)">
</strong>
<small x-text="countryName(personById(id)?.country)">
2026-09-24 14:35:05 +03:00
</small>
</div>
2026-10-07 14:22:37 +01:00
<button type="button" x-show="detail?.is_owner" @click="editPerson(id)" :aria-label="t('edit_participant_details_73e491')"><span x-text="t('details_dc3dec')"></span></button>
2026-09-24 14:35:05 +03:00
<button type="button" @click="moveParticipant(index,-1)" :aria-label="t('move_earlier_07dd6f')"><span x-text="t('symbol_9555ff')"></span></button>
<button type="button" @click="moveParticipant(index,1)" :aria-label="t('move_later_bb4c84')"><span x-text="t('symbol_fc4866')"></span></button>
<button type="button" @click="form.participants.splice(index,1)" :aria-label="t('remove_participant_131344')"><span x-text="t('symbol_67fba2')"></span></button>
</div>
</template>
<button type="button" class="person-select wide" @click="choosePerson('participants')"><span x-text="t('add_participant_name_or_email_5d89ac')"></span></button>
<p class="muted" x-show="modal==='rotation'"><span x-text="t('the_first_participant_starts_on_the_anchor_date_at_t_d87af4')"></span></p>
</div>
</template>
<template x-if="modal==='priority'"><div><label><span x-text="t('fallback_schedule_7b7a21')"></span><select x-model="form.fallback" required>
<option value="" x-text="t('choose_a_schedule_0636f3')"></option>
<template x-for="s in doc.schedules.filter(s=>s.id!==form.id)">
<option :value="s.id" :selected="form.fallback===(s.id)" x-text="s.name">
</option>
</template>
</select>
</label></div></template>
</div>
</template>
<template x-if="modal==='day'"><div><p x-show="!form.assignments.length"><span x-text="t('no_assignment_for_this_day_95a9f7')"></span></p><template x-for="a in form.assignments"><button type="button" class="shift-option" @click="openDuty(form.day,a)"><strong x-text="person(a.user_id)"></strong><small x-text="assignmentInterval(a,form.day)"></small><span><span x-text="t('choose_755968')"></span></span></button></template></div></template>
<template x-if="modal==='request'"><div class="cover-sheet">
<div class="shift-ticket"><div class="person-line"><span class="avatar" :class="color(form.from_user)" x-text="initials(form.from_user)"></span><div><strong x-text="person(form.from_user)"></strong><small x-text="scheduleName(form.schedule_id)"></small></div><span class="badge" x-show="Date.parse(form.end)<Date.now()"><span x-text="t('past_shift_cd54e4')"></span></span></div><div class="shift-times" x-show="!form.custom_range && !form.swap"><div><small><span x-text="t('from_3f6605')"></span></small><strong x-text="Date.parse(form.start)<Date.now() && Date.parse(form.end)>Date.now()?t('now_e3b820'):formatTime(form.start)"></strong></div><span><span x-text="t('symbol_213edd')"></span></span><div><small><span x-text="t('until_96bdb6')"></span></small><strong x-text="formatTime(form.end)"></strong></div></div></div>
<template x-if="Date.parse(form.end)>Date.now() && (can('request_overrides') || can('force_overrides'))"><div>
<p class="action-comment" x-show="matchingRequest?.reason && !form.custom_range && !form.swap" x-text="matchingRequest?.reason"></p><div class="cover-self" x-show="form.from_user!==user?.id && !form.force_mode"><span class="avatar" :class="color(user?.id)" x-text="initials(user?.id)"></span><strong><span x-text="t('you_ll_cover_this_shift_afcf1e')"></span></strong><span class="badge" x-show="matchingRequest && !form.custom_range && !form.swap"><span x-text="t('requested_by_your_colleague_1320db')"></span></span></div>
<div x-show="form.from_user===user?.id || form.force_mode"><label><span x-text="t('who_s_covering_926c44')"></span><input type="search" :aria-label="t('search_colleagues_9c47ad')" x-model="coverQuery" :placeholder="t('search_by_name_or_email_eedfe2')" autocomplete="off"></label><div class="cover-options" role="group" :aria-label="t('covering_colleague_10b47f')"><template x-for="p in coverPeople" :key="p.id"><button type="button" :class="{chosen:form.to_user===p.id}" @click="form.to_user=p.id" :aria-pressed="form.to_user===p.id" :aria-label="p.display_name"><span class="avatar small" :class="color(p.id)" x-text="initials(p.id)"></span><span x-text="p.id===user?.id?t('me_94563e'):p.display_name"></span><span class="selection-tick" x-show="form.to_user===p.id"><span x-text="t('symbol_698a87')"></span></span></button></template><p class="muted" x-show="!coverPeople.length"><span x-text="t('no_matching_teammates_dac71f')"></span></p></div></div>
<div class="cover-range"><small x-text="t('cover_timezone',{zone:doc.timezone})"></small><div class="cover-boundary"><label><span x-text="t('cover_start_date')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.start_day"><input type="text" :aria-label="t('cover_start_date')" x-ref="textDate" :value="text" @input="edit($event.target);coverRangeChanged()" :placeholder="t('dd_mm_yyyy_d7e5fe')" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked();coverRangeChanged()" tabindex="-1" aria-hidden="true"></span></label><label><span x-text="t('cover_start_time')"></span><input type="time" :aria-label="t('cover_start_time')" x-model="form.start_time" @input="coverRangeChanged()" required></label></div><div class="cover-boundary"><label><span x-text="t('cover_end_date')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.end_day"><input type="text" :aria-label="t('cover_end_date')" x-ref="textDate" :value="text" @input="edit($event.target);coverRangeChanged()" :placeholder="t('dd_mm_yyyy_d7e5fe')" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked();coverRangeChanged()" tabindex="-1" aria-hidden="true"></span></label><label><span x-text="t('cover_end_time')"></span><input type="time" :aria-label="t('cover_end_time')" x-model="form.end_time" @input="coverRangeChanged()" required></label></div></div><details class="range-options"><summary x-text="t('cover_swap_optional')"></summary><label class="check"><input type="checkbox" x-model="form.swap"><span x-text="t('i_ll_take_a_shift_in_return_b47888')"></span></label><template x-if="form.swap"><div><div class="cover-boundary"><label><span x-text="t('cover_return_date')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.return_start"><input type="text" :aria-label="t('cover_return_date')" x-ref="textDate" :value="text" @input="edit($event.target);coverRangeChanged()" :placeholder="t('dd_mm_yyyy_d7e5fe')" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked();coverRangeChanged()" tabindex="-1" aria-hidden="true"></span></label><label><span x-text="t('cover_return_time')"></span><input type="time" :aria-label="t('cover_return_time')" x-model="form.return_start_time" @input="coverRangeChanged()" required></label></div><div class="cover-boundary"><label><span x-text="t('cover_return_end_date')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.return_end"><input type="text" :aria-label="t('cover_return_end_date')" x-ref="textDate" :value="text" @input="edit($event.target);coverRangeChanged()" :placeholder="t('dd_mm_yyyy_d7e5fe')" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked();coverRangeChanged()" tabindex="-1" aria-hidden="true"></span></label><label><span x-text="t('cover_return_end_time')"></span><input type="time" :aria-label="t('cover_return_end_time')" x-model="form.return_end_time" @input="coverRangeChanged()" required></label></div></div></template></details>
<label x-show="!matchingRequest || form.force_mode || form.custom_range || form.swap"><span x-text="t('comment_optional_f28415')"></span><input :aria-label="t('comment_optional_f28415')" x-model="form.reason" maxlength="1000" :placeholder="t('e_g_ops_123_vacation_befe39')"></label>
<p class="validation-error" role="status" x-show="requestError" x-text="l(requestError)"></p><p class="muted" x-show="requestLoading"><span x-text="t('checking_availability_24f241')"></span></p>
2026-10-07 15:30:31 +01:00
<div class="request-ready" x-show="requestResult && !requestLoading"><template x-for="leg in requestResult?.legs || []"><small x-text="person(leg.from_user)+' → '+person(leg.to_user)+' · '+formatTime(leg.start)+' → '+formatTime(leg.end)"></small></template><span x-text="(form.force_mode||requestResult?.applies_immediately)?t('applies_immediately_no_approval_needed_842779'):matchingRequest?t('your_acceptance_completes_this_request_3713f7'):(requestResult?.required_approvers||[]).map(id=>shortName(id)).join(', ')+t('will_confirm_in_meguri_a4a9b6')"></span></div>
2026-09-24 14:35:05 +03:00
</div></template>
</div></template>
<template x-if="modal==='force'">
<div>
<p class="info-note"><span x-text="t('applies_immediately_your_action_and_optional_comment_50c159')"></span></p>
<label><span x-text="t('comment_optional_f28415')"></span><textarea x-model="form.reason" maxlength="1000" :placeholder="t('e_g_ops_123_vacation_befe39')">
</textarea>
</label>
</div>
</template>
<template x-if="modal==='absence'">
<div>
<label><span x-text="t('employee_079711')"></span><button type="button" class="person-select" @click="choosePerson('user_id')">
<span x-text="form.user_id?person(form.user_id):t('choose_a_person_or_enter_email_bcface')">
</span>
<span><span x-text="t('symbol_00d74e')"></span></span>
</button>
</label>
<div class="two-columns">
<label><span x-text="t('first_absent_duty_day_b0b47f')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.start"><input type="text" :aria-label="t('first_absent_duty_day_b0b47f')" x-ref="textDate" :value="text" @input="edit($event.target)" :placeholder="t('dd_mm_yyyy_d7e5fe')" inputmode="numeric" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked()" tabindex="-1" aria-hidden="true"></span>
</label>
<label><span x-text="t('last_absent_duty_day_inclusive_6f2bf4')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.end"><input type="text" :aria-label="t('last_absent_duty_day_inclusive_6f2bf4')" x-ref="textDate" :value="text" @input="edit($event.target)" :placeholder="t('dd_mm_yyyy_d7e5fe')" inputmode="numeric" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked()" tabindex="-1" aria-hidden="true"></span>
</label>
</div>
<p class="info-note"><span x-text="t('dates_are_inclusive_duty_days_in_the_workspace_timez_b377b5')"></span></p>
<div class="section-line"><h3><span x-text="t('replacement_ranges_a8d04e')"></span></h3><button type="button" @click="addAbsenceRange()"><span x-text="t('replacement_range_f216ec')"></span></button></div>
<p class="muted" x-show="!form.ranges.length"><span x-text="t('no_explicit_substitutes_yet_the_preview_below_shows__07dfef')"></span></p>
<template x-for="(range,index) in form.ranges" :key="range.id"><div class="absence-range">
<label><span x-text="t('schedule_0a8ada')"></span><select x-model="range.schedule_id"><option value="" x-text="t('absence_all_schedules')"></option><template x-for="schedule in doc.schedules" :key="schedule.id"><option :value="schedule.id" x-text="schedule.name"></option></template></select></label><small class="absence-zone" x-text="t('absence_interval_zone',{zone:doc.timezone})"></small><div class="two-columns">
<label><span x-text="t('range_starts_3632e7')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="range.start"><input type="text" :aria-label="t('range_starts_3632e7')" x-ref="textDate" :value="text" @input="edit($event.target)" :placeholder="t('dd_mm_yyyy_d7e5fe')" inputmode="numeric" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked()" tabindex="-1" aria-hidden="true"></span><input type="time" step="1" x-model="range.start_time" :aria-label="t('start_time')" required></label>
<label><span x-text="t('absence_end_exclusive')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="range.end"><input type="text" :aria-label="t('absence_end_exclusive')" x-ref="textDate" :value="text" @input="edit($event.target)" :placeholder="t('dd_mm_yyyy_d7e5fe')" inputmode="numeric" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked()" tabindex="-1" aria-hidden="true"></span><input type="time" step="1" x-model="range.end_time" :aria-label="t('end_time')" required></label>
</div>
<label><span x-text="t('replacement_c385e3')"></span><button type="button" class="person-select" @click="choosePerson('absenceReplacement',index)"><span x-text="range.replacement?person(range.replacement):t('choose_a_person_or_enter_email_bcface')"></span><span><span x-text="t('symbol_00d74e')"></span></span></button></label>
<label x-show="!range.schedule_id"><span x-text="t('project_coverage_536388')"></span><select :value="range.replace_projects?'replace':'automatic'" @change="range.replace_projects=$event.target.value==='replace'"><option value="automatic" x-text="t('promote_secondary_automatically_3bee50')"></option><option value="replace" x-text="t('use_this_replacement_in_affected_projects_too_edf224')"></option></select></label>
<button type="button" class="text danger" @click="form.ranges.splice(index,1)"><span x-text="t('remove_range_55c658')"></span></button>
</div></template>
<label class="check" x-show="can('force_overrides')"><input type="checkbox" x-model="form.force"><span x-text="t('apply_replacements_without_confirmation_7c4a2e')"></span></label>
<label x-show="form.ranges.length || form.force"><span x-text="t('comment_optional_f28415')"></span><textarea x-model="form.reason" maxlength="1000" :placeholder="t('vacation_coverage_9d7295')"></textarea></label>
<div class="absence-preview" aria-live="polite">
<h3><span x-text="t('schedule_impact_0ae487')"></span></h3>
<p class="muted" x-show="absenceLoading"><span x-text="t('checking_affected_schedules_109ed9')"></span></p>
<p class="validation-error" x-show="absenceError" x-text="l(absenceError)"></p>
<template x-if="absenceResult"><div>
<p class="preview-count" x-text="absenceResult.plan.schedules+t('affected_schedules_c82c00')+absenceResult.plan.groups.length+t('replacement_requests_c81010')+absenceResult.plan.uncovered+t('uncovered_intervals_b5430f')"></p>
<template x-for="error in absenceResult.plan.errors"><p class="validation-error" x-text="l(error)"></p></template>
<template x-for="warning in absenceResult.plan.warnings"><p class="validation-warning" x-text="l(warning)"></p></template>
<p class="muted" x-show="!absenceResult.plan.rows.length"><span x-text="t('no_scheduled_shifts_change_in_this_interval_98915a')"></span></p>
<div class="impact-scroll" x-show="absenceResult.plan.rows.length"><table class="impact-table"><thead><tr><th><span x-text="t('schedule_interval_3cf85c')"></span></th><th><span x-text="t('coverage_80e135')"></span></th><th><span x-text="t('action_97c89a')"></span></th></tr></thead><tbody>
2026-10-07 15:30:31 +01:00
<template x-for="row in (showAllAbsence?absenceResult.plan.rows:absenceResult.plan.rows.slice(0,8))" :key="row.schedule_id+row.start"><tr><td class="impact-interval"><strong x-text="row.schedule_name"></strong><small><span x-text="formatTime(row.start)"></span><span x-text="t('symbol_213edd')"></span><span x-text="formatTime(row.end)"></span></small></td><td><span x-text="person(row.before)"></span> <span x-text="t('symbol_213edd')"></span> <strong x-text="row.after?person(row.after):t('uncovered_88710a')"></strong></td><td><button type="button" class="text" x-show="row.status==='uncovered'" @click="coverAbsenceRow(row)" x-text="t('choose_a_substitute_be8f67')"></button><span x-show="row.status!=='uncovered'" x-text="row.status==='replacement'?(absenceRowPending(row)?t('needs_confirmation_722b99'):t('apply_now_a356cc')):t('automatic_secondary_fallback_7197c5')"></span></td></tr></template>
2026-09-24 14:35:05 +03:00
</tbody></table></div>
<button class="text" type="button" x-show="absenceResult.plan.rows.length>8" @click="showAllAbsence=!showAllAbsence" x-text="showAllAbsence?t('show_fewer_intervals_6d52e6'):t('show_all_382ebd')+absenceResult.plan.rows.length+t('intervals_d03402')"></button>
<p class="info-note" x-show="absenceResult.plan.groups.length && !form.force"><span x-text="t('absence_takes_effect_when_saved_each_replacement_con_a55d09')"></span></p>
<p class="validation-warning" x-show="absenceResult.plan.uncovered"><span x-text="t('some_shifts_have_no_available_replacement_they_are_s_c20d79')"></span></p>
</div></template>
</div>
</div>
</template>
<template x-if="modal==='holiday'">
<div>
<label><span x-text="t('country_d523eb')"></span><select :aria-label="t('country_d523eb')" x-model="form.country">
<template x-for="country in countries" :key="country.code">
<option :value="country.code" :selected="form.country===country.code" x-text="country.name">
</option>
</template>
</select>
</label>
<label><span x-text="t('date_in_country_calendar_fbc5e1')"></span><span class="date-field" x-data="dateField" x-modelable="value" x-model="form.date"><input type="text" :aria-label="t('date_in_country_calendar_fbc5e1')" x-ref="textDate" :value="text" @input="edit($event.target)" :placeholder="t('dd_mm_yyyy_d7e5fe')" inputmode="numeric" maxlength="10" required><button type="button" :aria-label="t('open_calendar_41547e')" @click="$refs.calendar.showPicker()"><span x-text="t('symbol_21887f')"></span></button><input class="native-date" type="date" x-ref="calendar" x-model="value" @input="picked()" tabindex="-1" aria-hidden="true"></span>
</label>
<label><span x-text="t('holiday_name_ceba88')"></span><input x-model="form.name" required maxlength="120">
</label>
</div>
</template>
<template x-if="modal==='holiday-import'">
<div>
<p class="muted"><span x-text="t('paste_a_csv_with_113f64')"></span> <code><span x-text="t('country_date_name_a98540')"></span></code> <span x-text="t('headers_use_country_codes_from_the_same_directory_as_abe68d')"></span> <strong><span x-text="t('cy_c44de8')"></span></strong> <span x-text="t('cyprus_and_834749')"></span> <strong><span x-text="t('am_80d305')"></span></strong> <span x-text="t('armenia_existing_dates_will_never_be_overwritten_5250bb')"></span></p>
<details>
<summary><span x-text="t('country_codes_csv_example_b6cb8a')"></span></summary>
<div class="two-columns">
<label><span x-text="t('find_a_country_bec93b')"></span><select @change="form.csvCountry=$event.target.value">
<option value="" x-text="t('choose_a_country_7843c8')"></option>
<template x-for="c in countries">
<option :value="c.code" x-text="c.name+' · '+c.code">
</option>
</template>
</select>
</label>
<p class="muted" x-text="form.csvCountry || t('cy_cyprus_am_armenia_8c0034')">
</p>
</div>
<pre><span x-text="t('country_date_name_cy_2026_10_01_independence_day_am__be8930')"></span></pre>
</details>
<label><span x-text="t('holiday_csv_222225')"></span><textarea class="csv-input" :aria-label="t('holiday_csv_222225')" x-model="csvText" @input="csvChanged()" :placeholder="t('country_date_name_cy_2026_12_25_christmas_day_b4543f')" spellcheck="false">
</textarea>
</label>
<div aria-live="polite">
<p class="muted" x-show="csvLoading"><span x-text="t('comparing_with_saved_holidays_ad992c')"></span></p>
<p class="danger-text" x-show="csvError" x-text="l(csvError)">
</p>
<template x-if="csvResult">
<div>
<div class="import-summary">
<strong x-text="t('holiday_count',{count:csvResult.preview.additions.length})">
</strong>
<span x-text="csvResult.preview.existing+t('already_saved_2ba31a')">
</span>
<span x-text="csvResult.preview.duplicates+t('repeated_rows_60187e')">
</span>
</div>
<p class="muted"><span x-text="t('nothing_is_saved_until_you_choose_import_996b73')"></span></p>
<template x-for="message in csvResult.preview.errors">
<p class="validation-error" x-text="l(message)">
</p>
</template>
<template x-for="message in csvResult.preview.conflicts">
<p class="validation-warning" x-text="l(message)">
</p>
</template>
<div class="import-table" x-show="csvResult.preview.additions.length">
<div class="import-table-head">
<span><span x-text="t('country_d523eb')"></span></span>
<span><span x-text="t('date_eb9a4b')"></span></span>
<span><span x-text="t('will_be_added_5399ac')"></span></span>
</div>
<template x-for="h in csvResult.preview.additions" :key="h.country+h.date">
<div>
<span x-text="countryName(h.country)">
</span>
<time x-text="formatDate(h.date)">
</time>
<span x-text="h.name">
</span>
</div>
</template>
</div>
</div>
</template>
</div>
</div>
</template>
<template x-if="modal==='member-permissions'">
2026-09-24 14:35:05 +03:00
<div>
<p class="muted"><span x-text="t('access_employee_permissions_hint')"></span></p>
<template x-for="grant in employeeGrantOptions" :key="grant.id">
2026-09-24 14:35:05 +03:00
<label class="check">
<input type="checkbox" :value="grant.id" x-model="form.permissions" :disabled="grant.id==='view'" :data-permission="grant.id">
<span x-text="grant.name"></span>
2026-09-24 14:35:05 +03:00
</label>
</template>
<p class="info-note" x-text="t('access_force_hint')"></p>
</div>
</template>
<template x-if="modal==='admin-user'">
<div>
<label><span x-text="t('username_84c290')"></span><input x-model="form.username" autocomplete="off" required minlength="3" maxlength="64">
</label>
<label><span x-text="t('display_name_c7874a')"></span><input x-model="form.display_name" required>
</label>
<label><span x-text="t('access_email_optional')"></span><input type="email" x-model="form.email" autocomplete="off">
</label>
<label><span x-text="t('password_8be3c9')"></span><input type="password" x-model="form.password" autocomplete="new-password" required minlength="12" maxlength="256">
</label>
<p class="muted"><span x-text="t('access_password_hint')"></span></p>
<fieldset class="group-picker"><legend x-text="t('access_user_groups')"></legend>
<p class="muted" x-show="!admin?.known_groups.length"><span x-text="t('access_no_known_groups')"></span></p>
<template x-for="group in admin?.known_groups || []" :key="group">
<label class="check"><input type="checkbox" :value="group" x-model="form.groups"><span x-text="group"></span></label>
</template>
</fieldset>
</div>
</template>
<template x-if="modal==='admin-groups'">
<div>
<p class="muted"><span x-text="t('access_groups_picker_hint')"></span></p>
<fieldset class="group-picker"><legend x-text="'@'+form.username"></legend>
<p class="muted" x-show="!admin?.known_groups.length"><span x-text="t('access_no_known_groups')"></span></p>
<template x-for="group in admin?.known_groups || []" :key="group">
<label class="check"><input type="checkbox" :value="group" x-model="form.groups"><span x-text="group"></span></label>
</template>
</fieldset>
</div>
</template>
<template x-if="modal==='admin-password'">
<div>
<p class="muted" x-text="'@'+form.username"></p>
<label><span x-text="t('new_password_d850ee')"></span><input type="password" x-model="form.password" autocomplete="new-password" required minlength="12" maxlength="256">
</label>
<p class="muted"><span x-text="t('access_password_hint')"></span></p>
</div>
</template>
<template x-if="modal==='admin-delete'">
<div>
<p class="info-note danger-note"><span x-text="t('access_delete_warning')"></span></p>
<p><strong x-text="form.name"></strong></p>
<label><span x-text="t('access_delete_confirm_label',{confirm:form.expected})"></span><input x-model="form.confirm" autocomplete="off" required>
</label>
2026-09-24 14:35:05 +03:00
</div>
</template>
<template x-if="modal==='preferences'">
<div>
<label><span x-text="t('team_name_9b11ed')"></span><input x-model="form.name" required>
</label>
<label><span x-text="t('planning_horizon_days_fc8284')"></span><input type="number" min="7" max="366" x-model="form.horizon_days" required>
</label>
<p class="info-note" x-text="t('schedule_timezone_003979')+doc.timezone+t('change_it_in_workspace_working_interval_settings_eac_1779f8')">
</p>
</div>
</template>
<template x-if="modal==='remove-schedule' && removal"><div class="removal-preview">
<p><span x-text="t('remove_e96390')"></span> <strong x-text="removal.plan.schedule.name"></strong> <span x-text="t('from_the_current_configuration_past_assignments_and__e2a3f7')"></span></p>
<p class="validation-error" x-show="removal.plan.active_replacements.length"><span x-text="t('this_schedule_has_current_or_future_approved_replace_6bfa3a')"></span></p>
<div class="dependency-list" x-show="removal.plan.escalation_steps.length"><h3><span x-text="t('used_by_escalation_steps_3e2542')"></span></h3><template x-for="step in removal.plan.escalation_steps" :key="step.policy_id+':'+step.step_number"><div class="list-row"><strong class="grow" x-text="step.policy_name"></strong><span class="badge" x-text="t('step_37d11c')+step.step_number+' · '+step.delay_minutes+t('min_f4e8a8')"></span></div></template></div>
<div class="dependency-list" x-show="removal.plan.coverage_fallbacks.length"><h3><span x-text="t('used_as_a_coverage_fallback_8955da')"></span></h3><template x-for="s in removal.plan.coverage_fallbacks" :key="s.id"><p x-text="s.name"></p></template></div>
<label><span x-text="t('existing_references_748fbb')"></span><select :aria-label="t('existing_references_748fbb')" x-model="form.redirect_to"><option value="" x-text="t('remove_the_affected_escalation_steps_8586b1')"></option><template x-for="s in doc.schedules.filter(s=>s.id!==form.id)" :key="s.id"><option :value="s.id" :selected="form.redirect_to===s.id" x-text="t('use_8023ca')+s.name+t('instead_cd9ec2')"></option></template></select></label>
<p class="info-note" x-show="form.redirect_to"><span x-text="t('all_escalation_steps_and_coverage_fallbacks_that_ref_8ca984')"></span></p>
<p class="validation-warning" x-show="!form.redirect_to && removal.plan.coverage_fallbacks.length"><span x-text="t('choose_another_schedule_for_the_coverage_fallbacks_b_61dbff')"></span></p>
<div class="validation-warning" x-show="!form.redirect_to && removal.plan.policies_emptied.length"><span x-text="t('these_policies_would_have_no_steps_left_choose_a_rep_2ce922')"></span><template x-for="p in removal.plan.policies_emptied" :key="p.id"><p x-text="p.name"></p></template></div>
<p class="info-note" x-show="!form.redirect_to && !removal.plan.escalation_steps.length && !removal.plan.coverage_fallbacks.length"><span x-text="t('no_escalation_or_fallback_references_need_changing_b2018c')"></span></p>
<p class="validation-warning" x-show="removal.plan.pending_requests.length" x-text="removal.plan.pending_requests.length+t('pending_replacement_requests_will_be_cancelled_in_fu_a1f178')"></p>
<p class="muted"><span x-text="t('the_schedule_and_these_dependency_changes_are_saved__42e786')"></span></p>
</div></template>
<template x-if="modal==='policy'">
<div>
<label><span x-text="t('policy_name_101bf6')"></span><input x-model="form.name" required>
</label>
<template x-for="(step,index) in form.steps">
<div class="step-editor">
<div class="flow-arrow" x-show="index"><span x-text="t('then_if_the_condition_still_holds_0fa74f')"></span></div>
<div class="two-columns">
<label><span x-text="t('after_minutes_d33505')"></span><input type="number" min="0" max="1440" x-model="step.delay_minutes" required>
</label>
<label><span x-text="t('condition_2f4979')"></span><select x-model="step.condition">
<option value="if-not-acked" x-text="t('if_not_acknowledged_5fc93a')"></option>
<option value="if-not-closed" x-text="t('if_not_closed_4369ad')"></option>
</select>
</label>
</div>
<label><span x-text="t('notify_6c03ee')"></span><select x-model="step.target">
<optgroup label="Schedules">
<template x-for="s in doc.schedules">
<option :value="'schedule:'+s.id" :selected="step.target===('schedule:'+s.id)" x-text="s.name">
</option>
</template>
</optgroup>
<optgroup label="Employees">
<template x-for="p in people">
<option :value="'user:'+p.id" :selected="step.target===('user:'+p.id)" x-text="p.display_name">
</option>
</template>
</optgroup>
<option value="team_all" x-text="t('everyone_on_the_team_235b55')"></option>
<option value="team_random" x-text="t('a_random_team_member_8f35f3')"></option>
</select>
</label>
<button type="button" class="text danger" @click="form.steps.splice(index,1)"><span x-text="t('remove_step_088d48')"></span></button>
</div>
</template>
<button type="button" @click="form.steps.push({delay_minutes:30,condition:'if-not-acked',target:'team_all'})"><span x-text="t('escalation_step_72b706')"></span></button>
<div class="two-columns spaced">
<label><span x-text="t('repeat_count_912e08')"></span><input type="number" min="0" max="20" x-model="form.repeat.count">
</label>
<label><span x-text="t('wait_between_repeats_0_disables_2e1378')"></span><input type="number" min="0" max="1440" x-model="form.repeat.wait_minutes">
</label>
</div>
<label class="check">
<input type="checkbox" x-model="form.repeat.reset_recipient_states"><span x-text="t('reset_recipient_states_on_repeat_8152c6')"></span></label>
<label class="check">
<input type="checkbox" x-model="form.repeat.close_alert_after_all"><span x-text="t('close_alert_after_all_repeats_2bec22')"></span></label>
</div>
</template>
<template x-if="modal==='route'">
<div>
<div class="two-columns">
<label><span x-text="t('rule_name_9e84d7')"></span><input x-model="form.name" required>
</label>
<label><span x-text="t('position_cf1c85')"></span><select :aria-label="t('position_cf1c85')" x-model.number="form.position" required>
<template x-for="position in (doc.routes.length+(doc.routes.some(r=>r.id===form.id)?0:1))"><option :value="position" :selected="form.position===position" x-text="position===1?t('1_first_6d7898'):position"></option></template>
</select>
</label>
</div>
<label><span x-text="t('escalation_policy_bbb0f7')"></span><select x-model="form.escalation_id">
<option value="" x-text="t('do_not_notify_1a59a6')"></option>
<template x-for="p in doc.escalations">
<option :value="p.id" :selected="form.escalation_id===(p.id)" x-text="p.name">
</option>
</template>
</select>
</label>
<div class="logic-editor">
<div class="logic-header">
<span class="logic-key"><span x-text="t('if_7be63e')"></span></span>
<strong><span x-text="t('all_conditions_match_e5187c')"></span></strong>
<span class="badge" x-show="!form.conditions.length"><span x-text="t('any_alert_4496c3')"></span></span>
</div>
<template x-for="(c,index) in form.conditions">
<div class="condition-editor">
<span class="logic-and" x-text="index?t('and_c2f934'):t('when_e3eafa')">
</span>
<select :aria-label="t('field_c326a4')" x-model="c.field">
<option value="tags" x-text="t('tags_848eed')"></option>
<option value="priority" x-text="t('priority_886cbf')"></option>
<option value="message" x-text="t('message_68f414')"></option>
<option value="description" x-text="t('description_55f8eb')"></option>
</select>
2026-10-07 15:30:31 +01:00
<select :aria-label="t('operation_430d32')" x-model="c.operation"><option value="greater-than" x-text="t('routing_greater_than')"></option><option value="less-than" x-text="t('routing_less_than')"></option>
2026-09-24 14:35:05 +03:00
<option value="contains" x-text="t('contains_33e15d')"></option>
<option value="equals" x-text="t('equals_09b6a6')"></option>
<option value="starts-with" x-text="t('starts_with_2a61f5')"></option>
</select>
<input :aria-label="t('value_8dce17')" x-model="c.value" required>
<label class="check">
<input type="checkbox" x-model="c.negate"><span x-text="t('not_ca1dd3')"></span></label>
<button type="button" @click="form.conditions.splice(index,1)" :aria-label="t('remove_condition_858fb1')"><span x-text="t('symbol_67fba2')"></span></button>
</div>
</template>
<button type="button" class="text" @click="form.conditions.push({field:'tags',operation:'contains',value:'',negate:false})"><span x-text="t('and_condition_63ce48')"></span></button>
</div>
<label class="check spaced">
<input type="checkbox" x-model="form.working"><span x-text="t('only_during_working_hours_fa1eb6')"></span></label>
<p class="info-note" x-show="form.working">
<strong x-text="workingSummary()">
</strong>
<br><span x-text="t('uses_the_shared_workspace_interval_change_it_in_team_01fcc4')"></span></p>
</div>
</template>
<template x-if="modal==='integration'">
<div>
<label><span x-text="t('provider_7ceee3')"></span><select x-model="form.provider">
<option value="mock" x-text="t('local_preview_no_external_writes_0c302e')"></option>
<option value="jsm" :disabled="demo" x-text="t('jira_service_management_071f90')"></option>
</select>
</label>
<div x-show="form.provider==='jsm'">
<label><span x-text="t('atlassian_cloud_id_829908')"></span><input x-model="form.cloud_id" :required="form.provider==='jsm'">
</label>
<label><span x-text="t('jsm_team_id_70f05e')"></span><input x-model="form.team_id" :required="form.provider==='jsm'">
</label>
<label class="check">
<input type="checkbox" x-model="form.advanced_escalations"><span x-text="t('premium_enterprise_advanced_escalations_available_483732')"></span></label>
<label><span x-text="t('atlassian_accounts_a9aeb6')"></span></label>
<p class="muted"><span x-text="t('emails_resolve_automatically_to_existing_active_atla_e073f6')"></span></p>
<template x-for="p in people">
<label>
<span x-text="p.display_name+' · '+(p.email || t('no_email_configured_b309ce'))">
</span>
<input x-model="form.users[p.id]" :placeholder="t('account_id_optional_when_email_is_available_d135c3')">
</label>
</template>
<p class="info-note"><span x-text="t('ask_your_deployment_administrator_to_configure_crede_782891')"></span></p>
<code class="secret-key" x-text="secretPrefix()+'_EMAIL / _TOKEN'">
</code>
</div>
</div>
</template>
<!-- GENERATION_MODALS -->
<div class="modal-footer cover-footer" x-show="modal==='request' && Date.parse(form.end)>Date.now() && (can('request_overrides') || can('force_overrides'))"><div class="form-validation" x-data="{validationScope:'form'}"><!-- VALIDATION_STATUS --></div>
<button type="button" @click="modal=''"><span x-text="t('cancel_77dfd2')"></span></button>
2026-10-07 15:30:31 +01:00
<button type="button" class="force-action" x-show="can('force_overrides') && !form.force_mode && !requestResult?.applies_immediately" @click="form.force_mode=true"><span x-text="t('apply_without_approval_d72926')"></span></button>
2026-09-24 14:35:05 +03:00
<button type="button" x-show="form.force_mode && can('request_overrides')" @click="form.force_mode=false;form.to_user=form.from_user===user?.id?'':user?.id"><span x-text="t('request_approval_instead_b84265')"></span></button>
2026-10-07 15:30:31 +01:00
<button type="button" class="primary" @click="sendQuickRequest()" :disabled="busy || !canSendRequest" x-show="can('request_overrides') || form.force_mode" x-text="form.force_mode || requestResult?.applies_immediately?t('apply_now_a356cc'):matchingRequest && !form.custom_range && !form.swap?t('accept_cover_d9a527'):form.from_user===user?.id?t('send_request_a89d64'):t('offer_to_cover_054c95')"></button>
2026-09-24 14:35:05 +03:00
</div>
2026-10-07 15:30:31 +01:00
<div class="modal-footer" x-show="!['request','day','generation-transfer'].includes(modal)">
2026-09-24 14:35:05 +03:00
<div class="form-validation" x-data="{validationScope:'form'}"><!-- VALIDATION_STATUS --></div>
<button type="button" x-show="modal==='account'" @click="modal='';signOut()"><span x-text="t('sign_out_dc1649')"></span></button>
<button type="button" @click="modal=''"><span x-text="t('cancel_77dfd2')"></span></button>
<button type="button" class="primary" x-show="rulePeopleTarget" @click="closeRuleEditor()" x-text="t('done_e9b450')"></button>
<button x-show="!rulePeopleTarget" class="primary" :class="{'remove-confirm':modal==='remove-schedule'}" :disabled="busy || (modal==='holiday-import' && !canImport) || (modal==='absence' && !canSaveAbsence) || (modal==='remove-schedule' && !canRemoveSchedule) || (modal==='admin-delete' && form.confirm!==form.expected)" x-text="busy?t('saving_56a228'):modal==='admin-user'?t('access_create_user'):modal==='admin-password'?t('access_reset_password'):modal==='admin-delete'?t('access_delete_permanently'):modal==='remove-schedule'?(form.redirect_to?t('remove_redirect_references_f1a241'):removal?.plan.escalation_steps.length?t('remove_schedule_affected_steps_7348af'):t('remove_schedule_ef0a8a')):modal==='holiday-import'?t('import_new_holidays_b36db6'):modal==='request'?(requestResult?.applies_immediately?t('apply_replacement_9b0845'):t('send_for_confirmation_f3df0f')):modal==='force'?t('apply_replacement_9b0845'):['variable','generation-rule'].includes(modal)?t('use_in_draft_a8f78c'):t('save_changes_179359')">
2026-09-24 14:35:05 +03:00
</button>
</div>
</div>
</form>
</section>
</div>
<!-- GENERATION_MAP -->
</body>
</html>