@ -1,5 +1,5 @@
export const portalHtml = ` <!doctype html>
export const portalHtml = ` <!doctype html>
< html lang = "ru" >
< html lang = "ru" data-omni-theme = "noble-green" data-omni-density = "workspace" >
< head >
< head >
< meta charset = "utf-8" >
< meta charset = "utf-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
< meta name = "viewport" content = "width=device-width, initial-scale=1" >
@ -7,7 +7,7 @@ export const portalHtml = `<!doctype html>
< title > Krona Staff < / title >
< title > Krona Staff < / title >
< link rel = "manifest" href = "/staff/manifest.webmanifest" >
< link rel = "manifest" href = "/staff/manifest.webmanifest" >
< link rel = "stylesheet" href = "/staff/styles.css" >
< link rel = "stylesheet" href = "/staff/styles.css" >
< style > [ hidden ] { display :none ! important } . auth - screen { min - height :100vh ; display :grid ; place - items :center ; padding :28px ; background :radial- gradient ( circle at 25 % 10 % , rgba ( 183 , 131 , 53 , . 18 ) , transparent 24 rem ) , linear - gradient ( 135 deg , # f7f0e3 , # e8dcc5 ) } . auth - card { width :min ( 100 % , 460 px ) ; padding :42px ; background :rgba ( 252 , 248 , 239 , . 96 ) ; border :1px solid # d9c9ac ; border - radius :20px ; box - shadow :0 24 px 70 px rgba ( 48 , 42 , 35 , . 16 ) } . auth - brand { display :flex ; align - items :center ; gap :12px ; margin - bottom :42px ; font - size :20px } . auth - card h1 { margin :6px 0 12 px ; font :500 38 px / 1.08 Georgia , serif } . auth - copy { margin :0 0 26 px ; color : # 756 a5b ; line - height :1.55 } . auth - card form { display :grid ; gap :10px } . auth - card label { font - size :13px ; font - weight :700 } . auth - card input { width :100 % ; min - height :50px ; padding :0 14 px ; border :1px solid # cbbb9e ; border - radius :10px ; background : # fffdf8 ; color : # 302 a23 } . auth - card button { margin - top :6px ; min - height :50px } . auth - card small { display :block ; margin - top :20px ; color : # 756 a5b ; line - height :1.45 } . login - error { margin - top :16px ; padding :12px 14 px ; border :1px solid # dfb2aa ; border - radius :10px ; background : # fff1ee ; color : # 78382 d } @media ( max - width :520px ) { . auth - screen { padding :16px } . auth - card { padding :28px 22 px } . auth - card h1 { font - size :32px } . auth - brand { margin - bottom :32px } } < / style >
< style > [ hidden ] { display :none ! important } . auth - screen { min - height :100vh ; display :grid ; place - items :center ; padding :28px ; background :var( -- omni - bg - workspace ) } . auth - card { width :min ( 100 % , 460 px ) ; padding :42px ; background :var ( -- omni - surface - raised ) ; border :1px solid var ( -- omni - border ) ; border - radius :var ( -- omni - radius - xl ) ; box - shadow :var ( -- omni - shadow - strong ) } . auth - brand { display :flex ; align - items :center ; gap :12px ; margin - bottom :42px ; font - size :20px } . auth - card h1 { margin :6px 0 12 px ; font :500 38 px / 1.08 var ( -- omni - font - serif ) } . auth - copy { margin :0 0 26 px ; color :var ( -- omni - text - secondary ) ; line - height :1.55 } . auth - card form { display :grid ; gap :10px } . auth - card label { font - size :13px ; font - weight :700 } . auth - card input { width :100 % ; min - height :50px ; padding :0 14 px ; border :1px solid var ( -- omni - border - strong ) ; border - radius :var ( -- omni - radius - md ) ; background :var ( -- omni - surface - raised ) ; color :var ( -- omni - text - primary ) } . auth - card button { margin - top :6px ; min - height :50px } . auth - card small { display :block ; margin - top :20px ; color :var ( -- omni - text - secondary ) ; line - height :1.45 } . login - error { margin - top :16px ; padding :12px 14 px ; border :1px solid var ( -- omni - danger ) ; border - radius :var ( -- omni - radius - md ) ; background :var ( -- omni - danger - soft ) ; color :var ( -- omni - danger ) } @media ( max - width :520px ) { . auth - screen { padding :16px } . auth - card { padding :28px 22 px } . auth - card h1 { font - size :32px } . auth - brand { margin - bottom :32px } } < / style >
< / head >
< / head >
< body >
< body >
< section id = "auth-screen" class = "auth-screen" aria-labelledby = "login-title" >
< section id = "auth-screen" class = "auth-screen" aria-labelledby = "login-title" >
@ -28,7 +28,7 @@ export const portalHtml = `<!doctype html>
< div id = "app-shell" hidden >
< div id = "app-shell" hidden >
< header class = "topbar" >
< header class = "topbar" >
< a class = "brand" href = "/staff" aria-label = "Krona Staff" > < span class = "mark" > К < / span > < span > Krona < b > Staff < / b > < / span > < / a >
< a class = "brand" href = "/staff" aria-label = "Krona Staff" > < span class = "mark" > К < / span > < span > Krona < b > Staff < / b > < / span > < / a >
< div class = "actions" > < span id = "connection" class = "status" > П о д к л ю ч е н и е … < / span > < button id = "notify" class = "quiet" type = "button" > У в е д о м л е н и я < / button > < button id = "logout" class = "quiet" type = "button" > В ы й т и < / button > < button id = "refresh" type = "button" > О б н о в и т ь < / button > < / div >
< div class = "actions" > < span id = "connection" class = "status" > П о д к л ю ч е н и е … < / span > < label class = "theme-control" > Т е м а < select id = "theme-select" aria-label = "Тема интерфейса" > < option value = "noble-green" > З е л ё н а я < / option > < option value = "parchment-light" > П е р г а м е н т < / option > < option value = "graphite-forest-dark" > Т ё м н а я < / option > < / select > < / label > < button id = "notify" class = "quiet" type = "button" > У в е д о м л е н и я < / button > < button id = "logout" class = "quiet" type = "button" > В ы й т и < / button > < button id = "refresh" type = "button" > О б н о в и т ь < / button > < / div >
< / header >
< / header >
< nav class = "section-nav" aria-label = "Разделы кабинета" >
< nav class = "section-nav" aria-label = "Разделы кабинета" >
< button class = "nav-item selected" type = "button" data-target = "overview-view" > О б з о р < / button >
< button class = "nav-item selected" type = "button" data-target = "overview-view" > О б з о р < / button >
@ -51,7 +51,7 @@ export const portalHtml = `<!doctype html>
< / section >
< / section >
< section class = "layout" >
< section class = "layout" >
< article class = "panel attendance-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > П о с л е д н и е с о б ы т и я < / p > < h2 > О т м е т к и т е р м и н а л а < / h2 > < / div > < span class = "live" > ● в р е а л ь н о м в р е м е н и < / span > < / div > < div id = "attendance" class = "feed" > < / div > < / article >
< article class = "panel attendance-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > П о с л е д н и е с о б ы т и я < / p > < h2 > О т м е т к и т е р м и н а л а < / h2 > < / div > < span class = "live" > ● в р е а л ь н о м в р е м е н и < / span > < / div > < div id = "attendance" class = "feed" > < / div > < / article >
< aside class = "panel people-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > К о м а н д а < / p > < h2 > С о т р у д н и к и < / h2 > < / div > < button id = "add-employee" class = "quiet compact" type = "button" > Д о б а в и т ь < / button > < / div > < div id = "employees" class = "people" > < / div > < / aside >
< aside class = "panel people-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > К о м а н д а < / p > < h2 > С о т р у д н и к и < / h2 > < / div > < div class = "actions" > < button id = "manage-professions" class = "quiet compact" type = "button" > П р о ф е с с и и < / button > < button id = "add-employee" class = "quiet compact" type = "button" > Д о б а в и т ь < / button > < / div > < / div > < div id = "employees" class = "people" > < / div > < / aside >
< / section >
< / section >
< section class = "panel sessions-panel timesheet-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > С в е р к а т е р м и н а л а < / p > < h2 > Р а б о ч и е с м е н ы < / h2 > < / div > < button id = "reconcile-sessions" class = "quiet" type = "button" > С в е р и т ь 7 д н е й < / button > < / div > < div class = "table-wrap" > < table > < thead > < tr > < th > Д а т а < / th > < th > С о т р у д н и к < / th > < th > П о д р а з д е л е н и е < / th > < th > И н т е р в а л < / th > < th > Ч а с ы < / th > < th > С о с т о я н и е < / th > < th > П р и ч и н а п р а в к и < / th > < th > < / th > < / tr > < / thead > < tbody id = "work-sessions" > < / tbody > < / table > < / div > < / section >
< section class = "panel sessions-panel timesheet-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > С в е р к а т е р м и н а л а < / p > < h2 > Р а б о ч и е с м е н ы < / h2 > < / div > < button id = "reconcile-sessions" class = "quiet" type = "button" > С в е р и т ь 7 д н е й < / button > < / div > < div class = "table-wrap" > < table > < thead > < tr > < th > Д а т а < / th > < th > С о т р у д н и к < / th > < th > П о д р а з д е л е н и е < / th > < th > И н т е р в а л < / th > < th > Ч а с ы < / th > < th > С о с т о я н и е < / th > < th > П р и ч и н а п р а в к и < / th > < th > < / th > < / tr > < / thead > < tbody id = "work-sessions" > < / tbody > < / table > < / div > < / section >
< section class = "panel timesheet-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > У ч ё т в р е м е н и < / p > < h2 > Т а б е л ь з а 40 д н е й < / h2 > < / div > < span id = "timesheet-help" > И з м е н е н и я с о х р а н я е т р у к о в о д и т е л ь < / span > < / div > < form id = "timesheet-create" class = "timesheet-create" > < label > С о т р у д н и к < select id = "new-time-employee" required > < / select > < / label > < label > Д а т а < input id = "new-time-date" type = "date" required > < / label > < label > Ч а с ы < input id = "new-time-hours" type = "number" min = "0" max = "24" step = "0.25" value = "8" required > < / label > < label > С т а т у с < select id = "new-time-status" > < option value = "draft" > Ч е р н о в и к < / option > < option value = "submitted" > Н а п р о в е р к е < / option > < option value = "approved" > У т в е р ж д ё н < / option > < option value = "rejected" > В о з в р а щ ё н < / option > < / select > < / label > < label class = "grow" > П р и м е ч а н и е < input id = "new-time-note" maxlength = "1000" placeholder = "Необязательно" > < / label > < button type = "submit" > Д о б а в и т ь с т р о к у < / button > < / form > < div class = "table-wrap" > < table > < thead > < tr > < th > Д а т а < / th > < th > С о т р у д н и к < / th > < th > Ч а с ы < / th > < th > С т а т у с < / th > < th > П р и м е ч а н и е < / th > < th > < / th > < / tr > < / thead > < tbody id = "timesheet" > < / tbody > < / table > < / div > < / section >
< section class = "panel timesheet-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > У ч ё т в р е м е н и < / p > < h2 > Т а б е л ь з а 40 д н е й < / h2 > < / div > < span id = "timesheet-help" > И з м е н е н и я с о х р а н я е т р у к о в о д и т е л ь < / span > < / div > < form id = "timesheet-create" class = "timesheet-create" > < label > С о т р у д н и к < select id = "new-time-employee" required > < / select > < / label > < label > Д а т а < input id = "new-time-date" type = "date" required > < / label > < label > Ч а с ы < input id = "new-time-hours" type = "number" min = "0" max = "24" step = "0.25" value = "8" required > < / label > < label > С т а т у с < select id = "new-time-status" > < option value = "draft" > Ч е р н о в и к < / option > < option value = "submitted" > Н а п р о в е р к е < / option > < option value = "approved" > У т в е р ж д ё н < / option > < option value = "rejected" > В о з в р а щ ё н < / option > < / select > < / label > < label class = "grow" > П р и м е ч а н и е < input id = "new-time-note" maxlength = "1000" placeholder = "Необязательно" > < / label > < button type = "submit" > Д о б а в и т ь с т р о к у < / button > < / form > < div class = "table-wrap" > < table > < thead > < tr > < th > Д а т а < / th > < th > С о т р у д н и к < / th > < th > Ч а с ы < / th > < th > С т а т у с < / th > < th > П р и м е ч а н и е < / th > < th > < / th > < / tr > < / thead > < tbody id = "timesheet" > < / tbody > < / table > < / div > < / section >
@ -77,7 +77,9 @@ export const portalHtml = `<!doctype html>
< section class = "panel timesheet-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > И с т о р и я < / p > < h2 > Н а ч и с л е н и я и в ы п л а т ы < / h2 > < / div > < / div > < div class = "table-wrap" > < table > < thead > < tr > < th > Д а т а < / th > < th > С о т р у д н и к < / th > < th > О п е р а ц и я < / th > < th > С у м м а < / th > < th > П р и м е ч а н и е < / th > < / tr > < / thead > < tbody id = "payroll-entries" > < / tbody > < / table > < / div > < / section >
< section class = "panel timesheet-panel" > < div class = "panel-head" > < div > < p class = "eyebrow" > И с т о р и я < / p > < h2 > Н а ч и с л е н и я и в ы п л а т ы < / h2 > < / div > < / div > < div class = "table-wrap" > < table > < thead > < tr > < th > Д а т а < / th > < th > С о т р у д н и к < / th > < th > О п е р а ц и я < / th > < th > С у м м а < / th > < th > П р и м е ч а н и е < / th > < / tr > < / thead > < tbody id = "payroll-entries" > < / tbody > < / table > < / div > < / section >
< / div >
< / div >
< / main >
< / main >
< dialog id = "employee-dialog" class = "employee-dialog" > < form id = "employee-form" method = "dialog" > < div class = "dialog-head" > < div > < p class = "eyebrow" > К а р т о ч к а к о м а н д ы < / p > < h2 id = "employee-dialog-title" > Н о в ы й с о т р у д н и к < / h2 > < / div > < button id = "employee-close" class = "icon-button" type = "button" aria-label = "Закрыть" > × < / button > < / div > < input id = "employee-id" type = "hidden" > < label > Т а б е л ь н ы й н о м е р < input id = "employee-number" maxlength = "80" required > < / label > < label > Ф а м и л и я , и м я и о т ч е с т в о < input id = "employee-name" maxlength = "240" required > < / label > < label > Д о л ж н о с т ь < input id = "employee-position" maxlength = "160" > < / label > < label > П о л < select id = "employee-gender" > < option value = "" > Н е у к а з а н < / option > < option value = "female" > Ж е н с к и й < / option > < option value = "male" > М у ж с к о й < / option > < / select > < / label > < label id = "employee-status-wrap" hidden > С т а т у с < select id = "employee-status" > < option value = "active" > Р а б о т а е т < / option > < option value = "blocked" > З а б л о к и р о в а н < / option > < option value = "dismissed" > У в о л е н < / option > < / select > < / label > < div id = "employee-pay-section" class = "pay-profile-section manager-only" > < p class = "eyebrow" > З а р п л а т н ы й п р о ф и л ь < / p > < label > Т о р г о в а я т о ч к а < select id = "employee-pay-unit" > < / select > < / label > < label > С х е м а р а с ч ё т а < select id = "employee-pay-rule" > < / select > < / label > < div class = "form-row-2" > < label > С т а в к а в ч а с , ₽ < input id = "employee-pay-rate" type = "number" min = "0" step = "1" value = "100" > < / label > < label > П р о ц е н т о т в ы р у ч к и , % < input id = "employee-pay-percent" type = "number" min = "0" max = "100" step = "0.1" value = "0" > < / label > < / div > < div class = "form-row-2" > < label > Г а р а н т и я з а с м е н у , ₽ < input id = "employee-pay-guarantee" type = "number" min = "0" step = "100" value = "0" > < / label > < label > Д о л я в ф о н д е , % < input id = "employee-pay-pool-weight" type = "number" min = "0" max = "200" step = "10" value = "100" > < / label > < / div > < label > Д е й с т в у е т с д а т ы < input id = "employee-pay-effective" type = "date" > < / label > < / div > < div id = "employee-error" class = "login-error" role = "alert" hidden > < / div > < div class = "dialog-actions" > < button id = "employee-cancel" class = "quiet" type = "button" > О т м е н а < / button > < button id = "employee-save" type = "submit" > С о х р а н и т ь < / button > < / div > < / form > < / dialog >
< input id = "employee-position" type = "hidden" >
< dialog id = "employee-dialog" class = "employee-dialog" > < form id = "employee-form" method = "dialog" > < div class = "dialog-head" > < div > < p class = "eyebrow" > К а р т о ч к а к о м а н д ы < / p > < h2 id = "employee-dialog-title" > Н о в ы й с о т р у д н и к < / h2 > < / div > < button id = "employee-close" class = "icon-button" type = "button" aria-label = "Закрыть" > × < / button > < / div > < input id = "employee-id" type = "hidden" > < label > Т а б е л ь н ы й н о м е р < input id = "employee-number" maxlength = "80" required > < / label > < label > Ф а м и л и я , и м я и о т ч е с т в о < input id = "employee-name" maxlength = "240" required > < / label > < label > П р о ф е с с и я < select id = "employee-profession" > < option value = "" > Н е у к а з а н а < / option > < / select > < / label > < p class = "role-note" > Р о л ь доступа : сотрудник. Р о л и и п р а в а п л а т ф о р м ы у п р а в л я ю т с я в Krona Core о т д е л ь н о о т к а д р о в о й п р о ф е с с и и . < / p > < label > П о л < select id = "employee-gender" > < option value = "" > Н е у к а з а н < / option > < option value = "female" > Ж е н с к и й < / option > < option value = "male" > М у ж с к о й < / option > < / select > < / label > < label id = "employee-status-wrap" hidden > С т а т у с < select id = "employee-status" > < option value = "active" > Р а б о т а е т < / option > < option value = "blocked" > З а б л о к и р о в а н < / option > < option value = "dismissed" > У в о л е н < / option > < / select > < / label > < div id = "employee-pay-section" class = "pay-profile-section manager-only" > < p class = "eyebrow" > З а р п л а т н ы й п р о ф и л ь < / p > < label > Т о р г о в а я т о ч к а < select id = "employee-pay-unit" > < / select > < / label > < label > С х е м а р а с ч ё т а < select id = "employee-pay-rule" > < / select > < / label > < div class = "form-row-2" > < label > С т а в к а в ч а с , ₽ < input id = "employee-pay-rate" type = "number" min = "0" step = "1" value = "100" > < / label > < label > П р о ц е н т о т в ы р у ч к и , % < input id = "employee-pay-percent" type = "number" min = "0" max = "100" step = "0.1" value = "0" > < / label > < / div > < div class = "form-row-2" > < label > Г а р а н т и я з а с м е н у , ₽ < input id = "employee-pay-guarantee" type = "number" min = "0" step = "100" value = "0" > < / label > < label > Д о л я в ф о н д е , % < input id = "employee-pay-pool-weight" type = "number" min = "0" max = "200" step = "10" value = "100" > < / label > < / div > < label > Д е й с т в у е т с д а т ы < input id = "employee-pay-effective" type = "date" > < / label > < small > Н о в а я в е р с и я д е й с т в у е т с в ы б р а н н о й д а т ы ; п р е д ы д у щ а я а в т о м а т и ч е с к и з а к р ы в а е т с я д н ё м р а н е е . < / small > < / div > < div id = "employee-error" class = "login-error" role = "alert" hidden > < / div > < div class = "dialog-actions" > < button id = "employee-cancel" class = "quiet" type = "button" > О т м е н а < / button > < button id = "employee-save" type = "submit" > С о х р а н и т ь < / button > < / div > < / form > < / dialog >
< dialog id = "profession-dialog" class = "employee-dialog" > < form id = "profession-form" method = "dialog" > < div class = "dialog-head" > < div > < p class = "eyebrow" > К а д р о в ы й с п р а в о ч н и к < / p > < h2 > П р о ф е с с и и < / h2 > < / div > < button id = "profession-close" class = "icon-button" type = "button" aria-label = "Закрыть" > × < / button > < / div > < label > Н о в а я п р о ф е с с и я < input id = "profession-name" maxlength = "160" required placeholder = "Например, Повар" > < / label > < button type = "submit" > Д о б а в и т ь п р о ф е с с и ю < / button > < div id = "profession-list" class = "people" > < / div > < div id = "profession-error" class = "login-error" role = "alert" hidden > < / div > < / form > < / dialog >
< dialog id = "access-dialog" class = "employee-dialog" > < form method = "dialog" > < div class = "dialog-head" > < div > < p class = "eyebrow" > Л и ч н ы й к а б и н е т < / p > < h2 > К л ю ч с о т р у д н и к а < / h2 > < / div > < button id = "access-close" class = "icon-button" type = "submit" aria-label = "Закрыть" > × < / button > < / div > < p > С к о п и р у й т е к л ю ч с е й ч а с . В п р о г р а м м е х р а н и т с я т о л ь к о е г о з а щ и щ ё н н ы й о т п е ч а т о к , п о в т о р н о п о к а з а т ь к л ю ч н е л ь з я . < / p > < label > К л ю ч д о с т у п а < input id = "access-token" readonly > < / label > < div class = "dialog-actions" > < button id = "access-copy" type = "button" > С к о п и р о в а т ь < / button > < button class = "quiet" type = "submit" > З а к р ы т ь < / button > < / div > < / form > < / dialog >
< dialog id = "access-dialog" class = "employee-dialog" > < form method = "dialog" > < div class = "dialog-head" > < div > < p class = "eyebrow" > Л и ч н ы й к а б и н е т < / p > < h2 > К л ю ч с о т р у д н и к а < / h2 > < / div > < button id = "access-close" class = "icon-button" type = "submit" aria-label = "Закрыть" > × < / button > < / div > < p > С к о п и р у й т е к л ю ч с е й ч а с . В п р о г р а м м е х р а н и т с я т о л ь к о е г о з а щ и щ ё н н ы й о т п е ч а т о к , п о в т о р н о п о к а з а т ь к л ю ч н е л ь з я . < / p > < label > К л ю ч д о с т у п а < input id = "access-token" readonly > < / label > < div class = "dialog-actions" > < button id = "access-copy" type = "button" > С к о п и р о в а т ь < / button > < button class = "quiet" type = "submit" > З а к р ы т ь < / button > < / div > < / form > < / dialog >
< footer > Krona Business Platform · Staff < / footer >
< footer > Krona Business Platform · Staff < / footer >
< / div >
< / div >
@ -122,16 +124,26 @@ export const portalJs = `(() => {
if ( 'serviceWorker' in navigator ) navigator . serviceWorker . register ( '/staff/sw.js' ) ; load ( false ) ; setInterval ( ( ) = > { if ( ! el ( 'app-shell' ) . hidden ) load ( true ) ; } , 15000 ) ;
if ( 'serviceWorker' in navigator ) navigator . serviceWorker . register ( '/staff/sw.js' ) ; load ( false ) ; setInterval ( ( ) = > { if ( ! el ( 'app-shell' ) . hidden ) load ( true ) ; } , 15000 ) ;
} ) ( ) ; ` ;
} ) ( ) ; ` ;
export const portalCssEnhanced = portalCss + `
export const portalCssEnhanced =
portalCss +
`
[ data - omni - theme = "parchment-light" ] { -- omni - bg - app : # EFE3CC ; -- omni - bg - workspace : # F3EAD7 ; -- omni - surface : # F8F2E6 ; -- omni - surface - raised : # FCF8EF ; -- omni - surface - muted : # E9DDC5 ; -- omni - surface - hover : # EDE0C7 ; -- omni - text - primary : # 302 A23 ; -- omni - text - secondary : # 756 A5B ; -- omni - text - inverse : # FFF ; -- omni - border : # D9C9AC ; -- omni - border - strong : # CBB99B ; -- omni - border - focus : # 9 A743D ; -- omni - primary : # 9 A743D ; -- omni - primary - hover : # 7 D5B2D ; -- omni - primary - soft : # EADBC1 ; -- omni - gold : # B78335 ; -- omni - gold - soft : # EFE0C2 ; -- omni - danger : # A44E3F ; -- omni - danger - soft : # F1DDD5 ; -- omni - info : # 5 F7480 ; -- omni - shadow - medium :0 6 px 18 px rgb ( 72 55 34 / . 10 ) ; -- omni - shadow - strong :0 14 px 36 px rgb ( 52 39 24 / . 16 ) }
[ data - omni - theme = "noble-green" ] { -- omni - bg - app : # EDE5D3 ; -- omni - bg - workspace : # F2EAD9 ; -- omni - surface : # F8F2E6 ; -- omni - surface - raised : # FCF8EF ; -- omni - surface - muted : # E6DBC5 ; -- omni - surface - hover : # E3E9DE ; -- omni - text - primary : # 2 E302A ; -- omni - text - secondary : # 6 B6C60 ; -- omni - text - inverse : # FFF ; -- omni - border : # D2CCB9 ; -- omni - border - strong : # 9 DAA98 ; -- omni - border - focus : # 315 F46 ; -- omni - primary : # 315 F46 ; -- omni - primary - hover : # 294 F3B ; -- omni - primary - soft : # DDE7DB ; -- omni - gold : # B78335 ; -- omni - gold - soft : # EFE0C2 ; -- omni - danger : # A44E3F ; -- omni - danger - soft : # F1DDD5 ; -- omni - info : # 527286 ; -- omni - shadow - medium :0 6 px 18 px rgb ( 72 55 34 / . 10 ) ; -- omni - shadow - strong :0 14 px 36 px rgb ( 52 39 24 / . 16 ) }
[ data - omni - theme = "graphite-forest-dark" ] { -- omni - bg - app : # 171 A18 ; -- omni - bg - workspace : # 1 D211E ; -- omni - surface : # 252 A26 ; -- omni - surface - raised : # 2 B312C ; -- omni - surface - muted : # 212621 ; -- omni - surface - hover : # 2 B342D ; -- omni - text - primary : # EEE7DA ; -- omni - text - secondary : # B7B3A8 ; -- omni - text - inverse : # 171 A18 ; -- omni - border : # 39423 A ; -- omni - border - strong : # 465249 ; -- omni - border - focus : # 6 F9A79 ; -- omni - primary : # 527 D60 ; -- omni - primary - hover : # 648 E70 ; -- omni - primary - soft : # 29382 E ; -- omni - gold : # C99A4D ; -- omni - gold - soft : # 403522 ; -- omni - danger : # C06B5F ; -- omni - danger - soft : # 432 B28 ; -- omni - info : # 7693 A0 ; -- omni - shadow - medium :0 8 px 22 px rgb ( 0 0 0 / . 24 ) ; -- omni - shadow - strong :0 16 px 40 px rgb ( 0 0 0 / . 34 ) }
: root { -- omni - radius - sm :6px ; -- omni - radius - md :8px ; -- omni - radius - lg :12px ; -- omni - radius - xl :16px ; -- omni - font - sans :Inter , "Segoe UI" , system - ui , sans - serif ; -- omni - font - serif :Georgia , serif ; -- omni - warning : # 7 A5B16 ; -- omni - warning - soft : # F7EAC9 ; -- bg :var ( -- omni - bg - app ) ; -- workspace :var ( -- omni - bg - workspace ) ; -- surface :var ( -- omni - surface ) ; -- raised :var ( -- omni - surface - raised ) ; -- text :var ( -- omni - text - primary ) ; -- muted :var ( -- omni - text - secondary ) ; -- border :var ( -- omni - border ) ; -- primary :var ( -- omni - primary ) ; -- gold :var ( -- omni - gold ) ; -- danger :var ( -- omni - danger ) ; -- info :var ( -- omni - info ) ; -- shadow :var ( -- omni - shadow - medium ) }
body , . auth - screen { background :var ( -- omni - bg - workspace ) ; color :var ( -- omni - text - primary ) } . topbar , . section - nav { background :color - mix ( in srgb , var ( -- omni - surface ) 94 % , transparent ) } . hero { background :linear - gradient ( 120 deg , var ( -- omni - primary - active , var ( -- omni - primary ) ) , var ( -- omni - primary ) ) } . stats article , . panel , . employee - dialog { background :var ( -- omni - surface ) ; border - color :var ( -- omni - border ) ; box - shadow :var ( -- omni - shadow - medium ) } button { background :var ( -- omni - primary ) ; color :var ( -- omni - text - inverse ) } button :hover { background :var ( -- omni - primary - hover ) ; filter :none } . quiet { background :transparent ; color :var ( -- omni - primary ) ; border - color :var ( -- omni - border ) } input , select , . timesheet - create input , . timesheet - create select , . employee - dialog input , . employee - dialog select , . period - control input , . stack - form input , . stack - form select , . inline - form input , . inline - form select { background :var ( -- omni - surface - raised ) ; color :var ( -- omni - text - primary ) ; border - color :var ( -- omni - border ) } button :focus - visible , input :focus - visible , select :focus - visible { outline - color :var ( -- omni - border - focus ) } . theme - control { display :flex ; align - items :center ; gap :6px ; color :var ( -- omni - text - secondary ) ; font - size :12px } . theme - control select { min - height :36px ; border :1px solid var ( -- omni - border ) ; border - radius :var ( -- omni - radius - md ) ; padding :0 8 px } . role - note { margin :0 ; padding :10px 12 px ; border - radius :var ( -- omni - radius - md ) ; background :var ( -- omni - primary - soft ) ; color :var ( -- omni - text - secondary ) ; font - size :12px ; line - height :1.45 } . payroll - warnings { background :var ( -- omni - warning - soft ) ! important ; color :var ( -- omni - warning ) ! important } . message , . login - error { background :var ( -- omni - danger - soft ) ; color :var ( -- omni - danger ) ; border - color :var ( -- omni - danger ) }
. section - nav { position :sticky ; top :72px ; z - index :4 ; display :flex ; align - items :center ; gap :8px ; padding :10px max ( 20 px , calc ( ( 100 vw - 1240 px ) / 2 ) ) ; border - bottom :1px solid var ( -- border ) ; background :rgba ( 243 , 234 , 215 , . 96 ) ; backdrop - filter :blur ( 12 px ) ; overflow - x :auto } . nav - item { flex :0 0 auto ; background :transparent ; color :var ( -- muted ) ; border :1px solid transparent } . nav - item . selected { background :var ( -- surface ) ; color :var ( -- primary ) ; border - color :var ( -- border ) } . period - control { margin - left :auto ; display :flex ; align - items :center ; gap :8px ; color :var ( -- muted ) ; font - size :13px ; font - weight :700 ; white - space :nowrap } . period - control input , . stack - form input , . stack - form select , . inline - form input , . inline - form select { min - height :42px ; border :1px solid var ( -- border ) ; border - radius :8px ; background : # fffdf8 ; color :var ( -- text ) ; padding :7px 9 px } . view - head { display :flex ; justify - content :space - between ; align - items :end ; gap :24px ; margin - bottom :22px } . view - head h1 { margin :4px 0 8 px ; font :500 clamp ( 30 px , 4 vw , 46 px ) / 1.05 Georgia , serif } . view - head p { margin :0 ; color :var ( -- muted ) } . finance - layout { display :grid ; grid - template - columns :minmax ( 0 , 1.7f r ) minmax ( 260 px , . 7 fr ) ; gap :18px ; margin - bottom :18px } . stack - form { display :grid ; gap :13px } . stack - form label , . inline - form label { display :grid ; gap :6px ; color :var ( -- muted ) ; font - size :12px ; font - weight :700 } . inline - form { display :grid ; grid - template - columns :repeat ( 6 , minmax ( 120 px , 1 fr ) ) auto ; align - items :end ; gap :10px } . finance - chart { min - height :280px } . finance - chart svg { display :block ; width :100 % ; height :280px } . chart - legend { display :flex ; gap :16px ; margin - top :10px ; color :var ( -- muted ) ; font - size :12px } . legend - dot { display :inline - block ; width :9px ; height :9px ; margin - right :5px ; border - radius :50 % } . income - dot { background :var ( -- primary ) } . expense - dot { background :var ( -- gold ) } . calendar - panel { overflow :auto } . calendar - weekdays , . calendar - grid { display :grid ; grid - template - columns :repeat ( 7 , minmax ( 145 px , 1 fr ) ) ; min - width :1015px } . calendar - weekdays span { padding :8px 10 px ; color :var ( -- muted ) ; font - size :12px ; font - weight :750 ; text - align :center } . calendar - day { min - height :142px ; padding :10px ; border - top :1px solid var ( -- border ) ; border - right :1px solid var ( -- border ) ; background :var ( -- raised ) } . calendar - day . outside { opacity :.45 } . calendar - day . today { outline :2px solid var ( -- gold ) ; outline - offset : - 2 px } . day - number { display :block ; margin - bottom :8px ; font - weight :750 } . shift - card { margin :5px 0 ; padding :7px ; border - left :3px solid var ( -- primary ) ; border - radius :6px ; background : # e8eadf ; font - size :11px ; line - height :1.35 } . shift - card . actual { border - left - color :var ( -- gold ) ; background : # f3e5cd } . shift - card strong , . shift - card span { display :block } . positive { color :var ( -- primary ) } . negative { color :var ( -- danger ) } . person - access { min - height :36px ; padding :0 10 px ; margin - left :5px } . app - view [ hidden ] { display :none ! important } @media ( max - width :900px ) { . section - nav { top :68px ; padding - inline :14px } . period - control { margin - left :8px } . finance - layout { grid - template - columns :1fr } . inline - form { grid - template - columns :repeat ( 2 , minmax ( 0 , 1 fr ) ) } . inline - form button { grid - column :1 / - 1 } . view - head { align - items :flex - start } . calendar - panel { padding :12px } } @media ( max - width :520px ) { . view - head { display :block } . view - head > button { margin - top :14px } . stats { grid - template - columns :1fr } . stats article :last - child { grid - column :auto } . inline - form { grid - template - columns :1fr } . period - control { order : - 1 ; margin - left :0 } . section - nav { top :92px } . finance - chart { min - height :220px } . finance - chart svg { height :220px } }
. section - nav { position :sticky ; top :72px ; z - index :4 ; display :flex ; align - items :center ; gap :8px ; padding :10px max ( 20 px , calc ( ( 100 vw - 1240 px ) / 2 ) ) ; border - bottom :1px solid var ( -- border ) ; background :rgba ( 243 , 234 , 215 , . 96 ) ; backdrop - filter :blur ( 12 px ) ; overflow - x :auto } . nav - item { flex :0 0 auto ; background :transparent ; color :var ( -- muted ) ; border :1px solid transparent } . nav - item . selected { background :var ( -- surface ) ; color :var ( -- primary ) ; border - color :var ( -- border ) } . period - control { margin - left :auto ; display :flex ; align - items :center ; gap :8px ; color :var ( -- muted ) ; font - size :13px ; font - weight :700 ; white - space :nowrap } . period - control input , . stack - form input , . stack - form select , . inline - form input , . inline - form select { min - height :42px ; border :1px solid var ( -- border ) ; border - radius :8px ; background : # fffdf8 ; color :var ( -- text ) ; padding :7px 9 px } . view - head { display :flex ; justify - content :space - between ; align - items :end ; gap :24px ; margin - bottom :22px } . view - head h1 { margin :4px 0 8 px ; font :500 clamp ( 30 px , 4 vw , 46 px ) / 1.05 Georgia , serif } . view - head p { margin :0 ; color :var ( -- muted ) } . finance - layout { display :grid ; grid - template - columns :minmax ( 0 , 1.7f r ) minmax ( 260 px , . 7 fr ) ; gap :18px ; margin - bottom :18px } . stack - form { display :grid ; gap :13px } . stack - form label , . inline - form label { display :grid ; gap :6px ; color :var ( -- muted ) ; font - size :12px ; font - weight :700 } . inline - form { display :grid ; grid - template - columns :repeat ( 6 , minmax ( 120 px , 1 fr ) ) auto ; align - items :end ; gap :10px } . finance - chart { min - height :280px } . finance - chart svg { display :block ; width :100 % ; height :280px } . chart - legend { display :flex ; gap :16px ; margin - top :10px ; color :var ( -- muted ) ; font - size :12px } . legend - dot { display :inline - block ; width :9px ; height :9px ; margin - right :5px ; border - radius :50 % } . income - dot { background :var ( -- primary ) } . expense - dot { background :var ( -- gold ) } . calendar - panel { overflow :auto } . calendar - weekdays , . calendar - grid { display :grid ; grid - template - columns :repeat ( 7 , minmax ( 145 px , 1 fr ) ) ; min - width :1015px } . calendar - weekdays span { padding :8px 10 px ; color :var ( -- muted ) ; font - size :12px ; font - weight :750 ; text - align :center } . calendar - day { min - height :142px ; padding :10px ; border - top :1px solid var ( -- border ) ; border - right :1px solid var ( -- border ) ; background :var ( -- raised ) } . calendar - day . outside { opacity :.45 } . calendar - day . today { outline :2px solid var ( -- gold ) ; outline - offset : - 2 px } . day - number { display :block ; margin - bottom :8px ; font - weight :750 } . shift - card { margin :5px 0 ; padding :7px ; border - left :3px solid var ( -- primary ) ; border - radius :6px ; background : # e8eadf ; font - size :11px ; line - height :1.35 } . shift - card . actual { border - left - color :var ( -- gold ) ; background : # f3e5cd } . shift - card strong , . shift - card span { display :block } . positive { color :var ( -- primary ) } . negative { color :var ( -- danger ) } . person - access { min - height :36px ; padding :0 10 px ; margin - left :5px } . app - view [ hidden ] { display :none ! important } @media ( max - width :900px ) { . section - nav { top :68px ; padding - inline :14px } . period - control { margin - left :8px } . finance - layout { grid - template - columns :1fr } . inline - form { grid - template - columns :repeat ( 2 , minmax ( 0 , 1 fr ) ) } . inline - form button { grid - column :1 / - 1 } . view - head { align - items :flex - start } . calendar - panel { padding :12px } } @media ( max - width :520px ) { . view - head { display :block } . view - head > button { margin - top :14px } . stats { grid - template - columns :1fr } . stats article :last - child { grid - column :auto } . inline - form { grid - template - columns :1fr } . period - control { order : - 1 ; margin - left :0 } . section - nav { top :92px } . finance - chart { min - height :220px } . finance - chart svg { height :220px } }
. form - row - 2 { display :grid ; grid - template - columns :1fr 1 fr ; gap :10px }
. form - row - 2 { display :grid ; grid - template - columns :1fr 1 fr ; gap :10px }
. pay - profile - section { display :grid ; gap :10px ; padding :14px ; background :var ( -- raised ) ; border :1px solid var ( -- border ) ; border - radius :10px ; margin - top :6px }
. pay - profile - section { display :grid ; gap :10px ; padding :14px ; background :var ( -- raised ) ; border :1px solid var ( -- border ) ; border - radius :10px ; margin - top :6px }
. payroll - warnings { background : # fff8e6 ! important ; border :1px solid # e2cb91 ! important ; color : # 6 b5314 ! important ; margin - bottom :18px }
. payroll - warnings { background : # fff8e6 ! important ; border :1px solid # e2cb91 ! important ; color : # 6 b5314 ! important ; margin - bottom :18px }
. payroll - warnings ul { margin :6px 0 0 18 px ; padding :0 }
. payroll - warnings ul { margin :6px 0 0 18 px ; padding :0 }
. payroll - warnings li { margin - bottom :4px }
. payroll - warnings li { margin - bottom :4px }
. section - nav { background :color - mix ( in srgb , var ( -- omni - surface ) 94 % , transparent ) } . period - control input , . stack - form input , . stack - form select , . inline - form input , . inline - form select { background :var ( -- omni - surface - raised ) } . shift - card { background :var ( -- omni - primary - soft ) } . shift - card . actual { background :var ( -- omni - gold - soft ) } . payroll - warnings { background :var ( -- omni - warning - soft ) ! important ; border - color :color - mix ( in srgb , var ( -- omni - warning ) 35 % , transparent ) ! important ; color :var ( -- omni - warning ) ! important } [ data - omni - theme = "graphite-forest-dark" ] { -- omni - warning : # E0B96C ; -- omni - warning - soft : # 403522 }
` ;
` ;
export const portalJsEnhanced = portalJs . replace ( 'Неверный ключ администратора.' , 'Неверный ключ доступа.' ) + `
export const portalJsEnhanced =
portalJs . replace ( 'Неверный ключ администратора.' , 'Неверный ключ доступа.' ) +
`
( ( ) = > {
( ( ) = > {
const api = '/api/v1/staff' , byId = id = > document . getElementById ( id ) , esc = value = > String ( value ? ? '' ) . replace ( /[&<>"']/g , c = > ( { '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' } [ c ] ) ) , rub = value = > ( Number ( value || 0 ) / 100 ) . toLocaleString ( 'ru-RU' , { style : 'currency' , currency : 'RUB' , maximumFractionDigits :2 } ) , hours = value = > ( Number ( value || 0 ) / 60 ) . toLocaleString ( 'ru-RU' , { maximumFractionDigits :1 } ) , jsonHeaders = { 'content-type' : 'application/json' } ;
const api = '/api/v1/staff' , byId = id = > document . getElementById ( id ) , esc = value = > String ( value ? ? '' ) . replace ( /[&<>"']/g , c = > ( { '&' : '&' , '<' : '<' , '>' : '>' , '"' : '"' , "'" : ''' } [ c ] ) ) , rub = value = > ( Number ( value || 0 ) / 100 ) . toLocaleString ( 'ru-RU' , { style : 'currency' , currency : 'RUB' , maximumFractionDigits :2 } ) , hours = value = > ( Number ( value || 0 ) / 60 ) . toLocaleString ( 'ru-RU' , { maximumFractionDigits :1 } ) , jsonHeaders = { 'content-type' : 'application/json' } ;
let role = 'manager' , units = [ ] , initialized = false , active = 'overview-view' , payRulesCache = [ ] , currentRunId = null ;
let role = 'manager' , units = [ ] , initialized = false , active = 'overview-view' , payRulesCache = [ ] , currentRunId = null ;
@ -153,6 +165,7 @@ export const portalJsEnhanced = portalJs.replace('Неверный ключ ад
const state = await get ( '/portal' ) ;
const state = await get ( '/portal' ) ;
role = state . viewer && state . viewer . role || 'manager' ;
role = state . viewer && state . viewer . role || 'manager' ;
document . querySelectorAll ( '.manager-only' ) . forEach ( node = > node . hidden = role !== 'manager' ) ;
document . querySelectorAll ( '.manager-only' ) . forEach ( node = > node . hidden = role !== 'manager' ) ;
byId ( 'manage-professions' ) . hidden = role !== 'manager' ;
byId ( 'viewer' ) . textContent = role === 'employee' ? 'Личный кабинет сотрудника' : 'Кабинет руководителя' ;
byId ( 'viewer' ) . textContent = role === 'employee' ? 'Личный кабинет сотрудника' : 'Кабинет руководителя' ;
units = ( await get ( '/org-units' ) ) . org_units || [ ] ;
units = ( await get ( '/org-units' ) ) . org_units || [ ] ;
try { payRulesCache = ( await get ( '/pay-rules' ) ) . pay_rules || [ ] } catch { }
try { payRulesCache = ( await get ( '/pay-rules' ) ) . pay_rules || [ ] } catch { }
@ -273,6 +286,29 @@ export const portalJsEnhanced = portalJs.replace('Неверный ключ ад
} ) ;
} ) ;
}
}
const themeSelect = byId ( 'theme-select' ) ;
const allowedThemes = [ 'noble-green' , 'parchment-light' , 'graphite-forest-dark' ] ;
const savedTheme = localStorage . getItem ( 'krona-staff-theme' ) ;
if ( savedTheme && allowedThemes . includes ( savedTheme ) ) document . documentElement . dataset . omniTheme = savedTheme ;
themeSelect . value = document . documentElement . dataset . omniTheme || 'noble-green' ;
themeSelect . addEventListener ( 'change' , ( ) = > { document . documentElement . dataset . omniTheme = themeSelect . value ; localStorage . setItem ( 'krona-staff-theme' , themeSelect . value ) } ) ;
let professionsCache = [ ] ;
async function loadProfessions ( ) {
const result = await get ( '/professions' ) ;
professionsCache = result . professions || [ ] ;
const select = byId ( 'employee-profession' ) ;
const current = select . value ;
select . innerHTML = '<option value="">Н е указана</option>' + professionsCache . filter ( x = > x . status === 'active' ) . map ( x = > option ( x . id , x . name ) ) . join ( '' ) ;
if ( [ . . . select . options ] . some ( x = > x . value === current ) ) select . value = current ;
byId ( 'profession-list' ) . innerHTML = professionsCache . length ? professionsCache . map ( x = > '<div class="person"><span class="avatar">' + esc ( x . name ) . slice ( 0 , 1 ) . toUpperCase ( ) + '</span><div><strong>' + esc ( x . name ) + '</strong><small>' + ( x . status === 'active' ? 'Активна' : 'В архиве' ) + '</small></div></div>' ) . join ( '' ) : '<div class="empty">Профессии ещё не добавлены</div>' ;
}
byId ( 'employee-profession' ) . addEventListener ( 'change' , ( ) = > { const selected = byId ( 'employee-profession' ) . selectedOptions [ 0 ] ; byId ( 'employee-position' ) . value = selected && selected . value ? selected . textContent : '' } ) ;
document . addEventListener ( 'click' , event = > { const target = event . target . closest ? . ( '.edit-employee,#add-employee' ) ; if ( ! target ) return ; setTimeout ( async ( ) = > { await loadProfessions ( ) ; const position = byId ( 'employee-position' ) . value ; const match = professionsCache . find ( x = > x . name === position ) ; byId ( 'employee-profession' ) . value = match ? . id || '' } , 0 ) } , true ) ;
byId ( 'manage-professions' ) . addEventListener ( 'click' , async ( ) = > { byId ( 'profession-error' ) . hidden = true ; await loadProfessions ( ) ; byId ( 'profession-dialog' ) . showModal ( ) } ) ;
byId ( 'profession-close' ) . addEventListener ( 'click' , ( ) = > byId ( 'profession-dialog' ) . close ( ) ) ;
byId ( 'profession-form' ) . addEventListener ( 'submit' , async event = > { event . preventDefault ( ) ; const name = byId ( 'profession-name' ) . value . trim ( ) ; if ( ! name ) return ; try { const created = await send ( '/professions' , 'POST' , { name } ) ; byId ( 'profession-name' ) . value = '' ; await loadProfessions ( ) ; byId ( 'employee-profession' ) . value = created . id ; byId ( 'employee-position' ) . value = created . name } catch ( error ) { byId ( 'profession-error' ) . textContent = error . message || 'Н е удалось сохранить профессию.' ; byId ( 'profession-error' ) . hidden = false } } ) ;
if ( byId ( 'payroll-approve-btn' ) ) {
if ( byId ( 'payroll-approve-btn' ) ) {
byId ( 'payroll-approve-btn' ) . addEventListener ( 'click' , async ( ) = > {
byId ( 'payroll-approve-btn' ) . addEventListener ( 'click' , async ( ) = > {
if ( ! currentRunId ) return ;
if ( ! currentRunId ) return ;
@ -292,6 +328,17 @@ export const portalJsEnhanced = portalJs.replace('Неверный ключ ад
} ) ( ) ;
} ) ( ) ;
` ;
` ;
export const portalManifest = JSON . stringify ( { name : 'Krona Staff' , short_name : 'Staff' , start_url : '/staff' , display : 'standalone' , background_color : '#F3EAD7' , theme_color : '#657255' , lang : 'ru' } ) ;
export const portalManifest = JSON . stringify ( {
name : 'Krona Staff' ,
short_name : 'Staff' ,
start_url : '/staff' ,
display : 'standalone' ,
background_color : '#F3EAD7' ,
theme_color : '#657255' ,
lang : 'ru' ,
} ) ;
export const portalServiceWorker = ` const CACHE='krona-staff-v3';const ASSETS=['/staff','/staff/styles.css','/staff/app.js','/staff/manifest.webmanifest'];self.addEventListener('install',e=>e.waitUntil(caches.open(CACHE).then(c=>c.addAll(ASSETS))));self.addEventListener('activate',e=>e.waitUntil(caches.keys().then(keys=>Promise.all(keys.filter(k=>k!==CACHE).map(k=>caches.delete(k))))));self.addEventListener('fetch',e=>{if(e.request.method==='GET'&&new URL(e.request.url).pathname.startsWith('/staff'))e.respondWith(fetch(e.request).then(r=>{const copy=r.clone();caches.open(CACHE).then(c=>c.put(e.request,copy));return r;}).catch(()=>caches.match(e.request)));}); ` ;
export const portalServiceWorker = ` const CACHE='krona-staff-v3';const ASSETS=['/staff','/staff/styles.css','/staff/app.js','/staff/manifest.webmanifest'];self.addEventListener('install',e=>e.waitUntil(caches.open(CACHE).then(c=>c.addAll(ASSETS))));self.addEventListener('activate',e=>e.waitUntil(caches.keys().then(keys=>Promise.all(keys.filter(k=>k!==CACHE).map(k=>caches.delete(k))))));self.addEventListener('fetch',e=>{if(e.request.method==='GET'&&new URL(e.request.url).pathname.startsWith('/staff'))e.respondWith(fetch(e.request).then(r=>{const copy=r.clone();caches.open(CACHE).then(c=>c.put(e.request,copy));return r;}).catch(()=>caches.match(e.request)));}); ` ;
export const portalServiceWorkerEnhanced = portalServiceWorker . replace ( 'krona-staff-v3' , 'krona-staff-v4' ) ;
export const portalServiceWorkerEnhanced = portalServiceWorker . replace (
'krona-staff-v3' ,
'krona-staff-v4' ,
) ;