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 >
</ 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 >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< button class = "primary" :disabled = "busy" x-text = "t('sign_in_ada2e9')" >
2026-09-24 14:35:05 +03:00
</ button >
</ form >
2026-10-07 13:28:43 +01:00
< 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 >
</ 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 >
2026-10-07 13:28:43 +01:00
< 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" >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 15:06:12 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
< div class = "sync-bar" x-show = "setupView || ['failed','conflict'].includes(detail?.sync?.state)" :class = "{'sync-problem':['failed','conflict'].includes(detail?.sync?.state)}" >
< span class = "status-dot" :class = "detail?.sync?.state==='synced'?'green':'amber-dot'" >
</ span >
< span x-text = "detail?.sync?.state==='synced' ? t('schedule_is_in_sync_be394a') : t('publication_fe5f07')+label(detail?.sync?.state || 'pending')" >
</ span >
< span class = "muted" x-text = "doc.integration.provider==='mock'?t('local_preview_provider_aca82c'):t('jira_service_management_071f90')" >
</ span >
< span class = "sync-error" x-text = "l(detail?.sync?.last_error || '')" >
</ span >
< button class = "text" x-show = "can('manage_integration')" @ click = "retry" :disabled = "busy" >< span x-text = "t('sync_now_2b7d93')" ></ span ></ button >
</ 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 >
2026-10-07 13:28:43 +01:00
< 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" >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< small x-text = "'@'+m.user.username" >
2026-09-24 14:35:05 +03:00
</ small >
</ div >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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=''" >
< section class = "modal" :class = "{'wide-modal':['variable','generation-rule','absence'].includes(modal),'constructor-modal':modal==='generation-rule'}" role = "dialog" aria-modal = "true" aria-labelledby = "dialog-title" @ keydown . tab = "trapDialog($event)" x-ref = "dialog" >
< form @ submit . prevent = "save()" >
< div class = "modal-heading" >
< div >
2026-10-07 13:28:43 +01:00
< 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'):({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" >
< label >< span x-text = "t('name_709a23')" ></ span >< input x-model = "pickerName" >
</ label >
< label >< span x-text = "t('email_for_jsm_lookup_193cfb')" ></ span >< input type = "email" x-model = "pickerEmail" >
</ label >
< label >< span x-text = "t('scheduling_country_393322')" ></ span >< select :aria-label = "t('scheduling_country_393322')" 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 = "savePersonDetails()" >< span x-text = "t('use_these_details_775a68')" ></ span ></ button >
</ 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 >< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
</ 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 >
< label >< span x-text = "t('name_709a23')" ></ span >< input x-model = "form.display_name" required >
</ label >
< label >< span x-text = "t('email_for_jsm_lookup_193cfb')" ></ span >< input type = "email" x-model = "form.email" :required = "!form.user_id" >
</ label >
< label >< span x-text = "t('scheduling_country_393322')" ></ span >< select :aria-label = "t('scheduling_country_393322')" 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 >
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
< 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?t('applies_immediately_no_approval_needed_842779'):matchingRequest?t('your_acceptance_completes_this_request_3713f7'):(form.from_user===user?.id?shortName(form.to_user):shortName(form.from_user))+t('will_confirm_in_meguri_a4a9b6')" ></ span ></ div >
</ 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 >
< 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'?(form.force?t('apply_now_a356cc'):t('needs_confirmation_722b99')):t('automatic_secondary_fallback_7197c5')" ></ span ></ td ></ tr ></ template >
</ 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 >
2026-10-07 13:28:43 +01:00
< template x-if = "modal==='member-permissions'" >
2026-09-24 14:35:05 +03:00
< div >
2026-10-07 13:28:43 +01:00
< 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" >
2026-10-07 13:28:43 +01:00
< 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 >
2026-10-07 13:28:43 +01:00
< 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 >
< select :aria-label = "t('operation_430d32')" x-model = "c.operation" >
< 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 >
< button type = "button" class = "force-action" x-show = "can('force_overrides') && !form.force_mode" @ click = "form.force_mode=true" >< span x-text = "t('apply_without_approval_d72926')" ></ span ></ button >
< 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 >
< button type = "button" class = "primary" @ click = "sendQuickRequest()" :disabled = "busy || !canSendRequest" x-show = "can('request_overrides') || form.force_mode" x-text = "form.force_mode?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 >
</ div >
< div class = "modal-footer" x-show = "!['request','day'].includes(modal)" >
< 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 >
2026-10-07 13:28:43 +01:00
< 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'?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 >