Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save guilherme-teodoro/3578fe844b9f5bde1e64f87610501088 to your computer and use it in GitHub Desktop.
Save guilherme-teodoro/3578fe844b9f5bde1e64f87610501088 to your computer and use it in GitHub Desktop.
(ns kitasato.components.vacation.index
(:require
[kitasato.components.box :as box]
[kitasato.components.card :as card]
[kitasato.components.container :as container]
[kitasato.components.dropdown :as dropdown]
[kitasato.components.grid :as grid]
[kitasato.components.icon :as icon]
[kitasato.components.scroll :as scroll]
[kitasato.components.spacing :as spacing]
[kitasato.components.spinner :as spinner]
[kitasato.components.table-kit :as table-kit]
[kitasato.components.tabs :as tabs]
[kitasato.components.typography :as typography]
[kitasato.components.vacation.common :as vacation-common]
[kitasato.components.vacation.form-modal :as individual-modal]
[kitasato.data.common :as common]
[kitasato.data.vacation :as vacation]
[kitasato.io.asset :as asset]
[kitasato.network.lukla :as lukla]
[kitasato.re-frame :refer [listen]]
[kitasato.router.path-helpers :as path-helpers]
[kitasato.storage.q :as q]
[kitasato.style.settings :as style]
[kitasato.time :as time]
[kitasato.ui.atoms.badge :as a-badge]
[kitasato.ui.atoms.buttons :as a-buttons]
[kitasato.ui.atoms.collapse :as a-collapse]
[kitasato.ui.atoms.layout :as a-layout]
[kitasato.ui.atoms.page :as a-page]
[kitasato.ui.molecules.mobile :as m-mobile]
[kitasato.ui.molecules.mobile.frame :as m-mobile-frame]
[kitasato.ui.molecules.user :as m-user]
[kitasato.ui.organisms.empty-state :as o-empty-state]
[kitasato.utils :as utils]
[reagent.core :as r]))
(defn- vacation-view-key
[vacation]
(let [date (vacation/attr->start_date vacation)]
[(time/year date) (time/month date)]))
(defn- same-month?
[a b]
(and (= (time/year a) (time/year b)) (= (time/month a) (time/month b))))
(defn- gantt
[{:keys [vacations company-id loading? bounds timeframe on-timeframe-change on-select]}]
(let [now (time/now)]
[:div
[:div.Gantt
[:table.Gantt-profiles
[:thead [:tr [:th "Colaboradores"]]]
[:tbody
(doall
(for [vacation vacations]
^{:key (:id vacation)}
[:tr
[:td (m-user/avatar-name-position company-id (:id (vacation/->profile vacation)))]]))]]
[:div.Gantt-calendarWrapper
[:table.Gantt-calendar
[:thead
[:tr
[:th
[:button.Gantt-arrow.Gantt-arrow--left
{:disabled (or loading?
(when-let [min-date (:min bounds)]
(when-let [time (first timeframe)]
(> (time/timestamp min-date) (time/timestamp time))))),
:on-click (utils/maybe-handler on-timeframe-change -1)}
[icon/icon
{:icon "arrow",
:size "small"}]]]
(map (fn [time]
^{:key [(time/year time) (time/month time)]}
[:th
(time/abbreviated-months (time/month time))
[:span.Gantt-calendar-year (subs (str (time/year time)) 2)]])
timeframe)
[:th
[:button.Gantt-arrow
{:disabled (or loading?
(when-let [max-date (:max bounds)]
(when-let [time (last timeframe)]
(< (time/timestamp max-date) (time/timestamp time))))),
:on-click (utils/maybe-handler on-timeframe-change 1)}
[icon/icon
{:icon "arrow",
:size "small"}]]]]]
[:tbody
(doall
(for [vacation vacations]
(let [profile& (lukla/follow-1 vacation :profile)]
^{:key (:id vacation)}
[:tr
[:td ""]
(map (fn [timebase]
^{:key [(time/year timebase) (time/month timebase)]}
[:td
(when (same-month? timebase now)
(let [pad-now-line (str (* (/ (time/date now) (time/days-in-month now))
100)
"%")]
[:span.Gantt-now {:style {:left pad-now-line}}]))
(when (same-month? timebase (vacation/attr->start_date vacation))
(let [amount-in-days (vacation/attr->amount_in_days vacation)
start-date (vacation/attr->start_date vacation)
finish-date (vacation/attr->finish_date vacation)
pad-marker (str (* (/ (time/date start-date) 30) 100) "%")
pill-width (str (* (/ amount-in-days 30) 100) "%")
pad-pill-label (str (/ (* (- (/ amount-in-days
(time/days-in-month timebase))
1)
100)
2)
"%")]
[:div.Gantt-marker
{:style {:left pad-marker},
:on-click #(utils/maybe-call-fn on-select vacation)}
[:div.Gantt-pill
{:style {:width pill-width},
:class (if (= (vacation/attr->status vacation) "approved")
"Gantt-pill--approved"
"Gantt-pill--pending")}
(str amount-in-days)
" dias"]
[:div.Gantt-pillLabel
{:style {:left pad-pill-label}}
(time/format start-date "DD/MM")
" → "
(time/format finish-date "DD/MM")]]))])
timeframe)
[:td ""]])))]]]]
(when loading?
[:div
{:style {:display :flex,
:padding-top "25px",
:justify-content :center}}
[spinner/spinner
{:style {:width "25px",
:height "25px"}}]])]))
(defn- vacation-item
[{:keys [items date company-id profile-id]}]
[:div.Vacation-listItem
[:div
{:class "f6 ttu b"}
(time/months (get date 1))
" "
[:span {:class "gray3"} (subs (str (get date 0)) 2)]]
(doall
(for [vacation items]
(let [profile& (lukla/follow-1 vacation :profile)
user& (lukla/follow-1 @profile& :user)]
^{:key (:id vacation)}
[:div
{:class "w-100 bb b--gray2 flex items-center pv3"}
(m-user/avatar-name company-id
(:id (vacation/->profile vacation))
[:div
(time/format (vacation/attr->start_date vacation) time/br-day-month)
" → "
(time/format (vacation/attr->finish_date vacation)
time/br-day-month)])
[:div
{:class
"avatar-size br-100 flex items-center justify-center b flex-none ba b--gray2 blue"}
(vacation/attr->amount_in_days vacation)]])))])
(defn- vacation-item-spinner
[]
[:div.Vacation-listItem
[spinner/spinner
{:style {:width "50px",
:height "50px"}}]])
(defn- vacations-list
[{:keys [vacations company-id]}]
(into [spacing/spacing
{:direction :vertical,
:size :xxl}]
(for [[date items] (->> vacations
(group-by vacation-view-key)
(sort-by first)
(reverse))]
^{:key date}
[vacation-item
{:company-id company-id,
:date date,
:items items}])))
(defn vacations-pending-desk
[{:keys [company-id vacations-pending create-callback]}]
[table-kit/table
{:data vacations-pending,
:header [table-kit/header
[table-kit/cell {:width 340} "Colaborador"]
[table-kit/cell
{:grow 1,
:halign :center}
"Descanso"]
[table-kit/cell
{:grow 1,
:halign :center}
"Vender 10 dias (abono)"]
[table-kit/cell
{:grow 1,
:halign :center}
"Início do descanso"]
[table-kit/cell
{:grow 1,
:halign :center}
"Final do descanso"]],
:row-fn (fn [vacation]
(let [show-form-modal
(fn []
(individual-modal/show-form-modal
{:on-delete vacation-common/handle-delete,
:delete-callback (fn []
(vacation-common/delete-callback vacation)
(create-callback)),
:on-save vacation-common/handle-save,
:save-callback (fn [data]
(create-callback)
(vacation-common/save-callback
data
(vacation-common/decorate-vacations company-id))),
:vacation vacation,
:company-id company-id,
:profile-id (:id (vacation/->profile vacation))}))]
[table-kit/row
{:on-click show-form-modal}
[table-kit/cell
{:width 340}
(m-user/avatar-name-position company-id
(:id (vacation/->profile vacation))
show-form-modal)]
[table-kit/cell
{:grow 1,
:halign :center}
(vacation/attr->amount_in_days vacation)]
[table-kit/cell
{:grow 1,
:halign :center}
(if (vacation/attr->sold_10_days vacation)
"Sim"
"Não")]
[table-kit/cell
{:grow 1,
:halign :center}
(time/format (vacation/attr->start_date vacation) time/br-date)]
[table-kit/cell
{:grow 1,
:halign :center}
(time/format (vacation/attr->finish_date vacation) time/br-date)]]))}])
(defn empty-state-desktop
[company-id]
(o-empty-state/doubtful-file-card
[card/title {:center? true} "Ainda não sabemos o suficiente"]
[box/box
{:size :s}
[:p.tc
"Fatores como férias coletivas, períodos aquisitivos e até ausências mais longas podem influenciar no prazo e no saldo das férias de cada colaborador."]
[:p.tc
"Trate o histórico de férias da empresa para ver o relatório que vai te ajudar a evitar multas e passivos relacionados a férias"]]
[:div.flex.justify-center
(a-buttons/primary "Tratar o histórico"
#(path-helpers/set-token! (path-helpers/company-vacations-path
:importation
{:company-id company-id})))]))
(defn empty-state-mobile
[company-id]
(o-empty-state/doubtful-file
[card/title {:center? true} "Ainda não sabemos o suficiente"]
[box/box
{:size :s}
[:p.tc
"Fatores como férias coletivas, períodos aquisitivos e até ausências mais longas podem influenciar no prazo e no saldo das férias de cada colaborador."]
[:p.tc
"Trate o histórico de férias da empresa para ver o relatório que vai te ajudar a evitar multas e passivos relacionados a férias"]]
m-mobile/unavailable-warning))
(defn missing-import-banner-desktop
[company-id to-import-number]
[:div
{:style {:box-shadow "0 1px 2px #cfcfcf",
:padding-left (style/sizes-string :l),
:padding-right (style/sizes-string :l),
:margin-bottom (style/sizes-string :xl)}}
[grid/grid
[grid/cell
{:flex 0}
[:div
{:style {:overflow "hidden",
:height "100px"}}
[:img
{:style {:max-width "120px"},
:src (asset/image "/kitasato/static/images/empty-states/doubtful-file-alone.svg")}]]]
[grid/cell
{:flex 1}
[:div
[:p
[:span
{:style {:font-weight (str (style/font-weight :bold))}}
"Ainda não sabemos o suficiente sobre "
(if (= 1 to-import-number)
"1 colaborador"
(str to-import-number " colaboradores"))]
[:br]
[:span
"Trate seus históricos de férias para saber sobre o saldo e os prazos de suas próximas férias."]]]]
[grid/cell
{:flex 0,
:valign "center"}
[:div
{:style {:width "150px"}}
(a-buttons/primary "Tratar o histórico"
#(path-helpers/set-token! (path-helpers/company-vacations-path
:importation
{:company-id company-id})))]]]])
(defn missing-import-banner-mobile
[to-import-number]
[:div
{:style {:box-shadow "0 1px 2px #cfcfcf",
:padding-left (style/sizes-string :m),
:padding-right (style/sizes-string :m),
:padding-bottom (style/sizes-string :s),
:margin-bottom (style/sizes-string :xl)}}
[:div
{:style {:border-bottom (str "1px solid " (style/colors :light-gray)),
:margin-bottom (style/sizes-string :s)}}
[grid/grid
[grid/cell
{:flex 1,
:valign :center}
[:div
[:span
{:style {:font-weight (str (style/font-weight :bold)),
:font-size (style/sizes-string :xs)}}
"Ainda não sabemos o suficiente sobre "
(if (= 1 to-import-number)
"1 colaborador"
(str to-import-number " colaboradores"))]]]
[grid/cell
{:flex 0}
[:div
{:style {:overflow "hidden",
:height "65px"}}
[:img
{:style {:max-width "80px"},
:src (asset/image "/kitasato/static/images/empty-states/doubtful-file-alone.svg")}]]]]]
(a-collapse/simple
"Saiba mais"
false
[:div
[:p "Trate seus históricos de férias para saber sobre saldos e prazos das próximas férias"]
m-mobile/unavailable-warning])])
(defn vacations-queue-desk
[{:keys [company-id]}]
(let [importation-status& (lukla/get-state [:vacation-importation :status])]
(fn [{:keys [vacations-queue create-callback]}]
(let [viewer-role (listen [:viewer/role-access])]
(console.log :>>>>>> viewer-role)
(cond
(:flushing? vacations-queue) [box/box
{:size :m,
:halign :center}
[spinner/spinner]]
:else
[:div
(when (not= 0 (common/attr @importation-status& :not_adjusted_profile_count))
(missing-import-banner-desktop company-id
(common/attr @importation-status&
:not_adjusted_profile_count)))
[table-kit/table
{:data (:periods vacations-queue),
:header [table-kit/header
[table-kit/cell {:width 340} "Colaborador"]
[table-kit/cell
{:grow 1,
:halign :center}
"Saldo de Férias"]
[table-kit/cell
{:grow 1,
:halign :center}
"Início da Concessão"]
[table-kit/cell
{:grow 1,
:halign :center}
"Limite para o aviso"]
[table-kit/cell
{:grow 1,
:halign :center}
"Limite para a saída"]],
:row-fn
(fn [vacation]
(let [show-form-modal (fn []
(individual-modal/show-form-modal
{:on-delete vacation-common/handle-delete,
:on-save vacation-common/handle-save,
:vacation nil,
:save-callback create-callback,
:company-id company-id,
:profile-id (:id (vacation/->profile vacation))}))]
[table-kit/row
{:disabled? (not (common/attr vacation :concession_started)),
:on-click show-form-modal}
[table-kit/cell
{:width 340}
(m-user/avatar-name-position company-id
(:id (vacation/->profile vacation))
show-form-modal)]
[table-kit/cell
{:grow 1,
:halign :center}
(common/attr vacation :balance)]
[table-kit/cell
{:grow 1,
:halign :center}
(time/format (common/attr vacation :concession_start_on) time/br-date)]
[table-kit/cell
{:grow 1,
:halign :center}
(let [text (time/format (common/attr vacation :notice_limit_on) time/br-date)]
(if (common/attr vacation :notice_warning)
(typography/dangerous-text text)
text))]
[table-kit/cell
{:grow 1,
:halign :center}
(let [text (time/format (common/attr vacation :leave_limit_on) time/br-date)]
(if (common/attr vacation :leave_warning)
(typography/dangerous-text text)
text))]]))}]
(when (:loading? vacations-queue)
[box/box
{:size :m,
:halign :center}
[spinner/spinner]])])))))
(defn vacations-queue-mobi
[{:keys [company-id]}]
(let [importation-status& (lukla/get-state [:vacation-importation :status])]
(fn [{:keys [vacations-queue on-create]}]
(if (zero? (common/attr @importation-status& :adjusted_profile_count))
(empty-state-mobile company-id)
[:div
(when (not= 0 (common/attr @importation-status& :not_adjusted_profile_count))
(missing-import-banner-mobile (common/attr @importation-status&
:not_adjusted_profile_count)))
(doall
(for [[idx period] (map-indexed vector (:periods vacations-queue))
:let [handle-click #(individual-modal/show-form-modal
{:on-delete vacation-common/handle-delete,
:on-save vacation-common/handle-save,
:vacation nil,
:company-id company-id,
:profile-id (:id (vacation/->profile period))})]]
^{:key idx}
[box/box
{:size :none}
[card/card
{:on-click handle-click,
:disabled? (not (common/attr period :concession_started))}
(m-user/avatar-name-position company-id
(:id (vacation/->profile period))
handle-click)
(a-layout/horizontal-divider :s)
[grid/grid
[grid/cell
{:size (/ 1 2)}
[box/box
{:size :none,
:halign :center}
[:div "Limite para o aviso"]
(let [text (time/format (common/attr period :notice_limit_on) time/br-date)]
(if (common/attr period :notice_warning)
(typography/dangerous-text text)
text))]]
[grid/cell
{:size (/ 1 2)}
[box/box
{:size :none,
:halign :center}
[:div "Limite para a saída"]
(let [text (time/format (common/attr period :leave_limit_on) time/br-date)]
(if (common/attr period :leave_warning)
(typography/dangerous-text text)
text))]]]]
(a-layout/gap :s)]))
(when (:loading? vacations-queue)
[box/box
{:size :m,
:halign :center}
[spinner/spinner]])]))))
(defn vacations-pending-mobi
[{:keys [vacations-pending company-id]}]
[table-kit/table
{:data vacations-pending,
:header [table-kit/header
[table-kit/cell {:grow 1} "Colaborador"]
[table-kit/cell
{:grow 1,
:halign :flex-end}
"Início"]],
:row-fn (fn [vacation]
[table-kit/row
{:on-click (fn []
(individual-modal/show-form-modal
{:on-delete vacation-common/handle-delete,
:delete-callback (fn []
(vacation-common/delete-callback vacation)),
:on-save vacation-common/handle-save,
:save-callback (fn [data]
(vacation-common/save-callback
data
(vacation-common/decorate-vacations
company-id))),
:vacation vacation,
:company-id company-id,
:profile-id (:id (vacation/->profile vacation))}))}
[table-kit/cell
{:grow 1}
(m-user/avatar-name company-id
(:id (vacation/->profile vacation))
(str (vacation/attr->amount_in_days vacation) " dias"))]
[table-kit/cell
{:grow 1,
:halign :flex-end}
(time/format (vacation/attr->start_date vacation) time/br-date)]])}])
(defn card-wrap
[{:keys [company-id]} _]
(let [open& (r/atom false)]
(fn [{:keys [on-create empty-view show-empty-view?]} content]
(if show-empty-view?
empty-view
[card/card
[spacing/spacing
{:direction :vertical,
:size :l}
[grid/grid
[grid/cell
{:flex 1,
:align "flex-end"}
[dropdown/container
(a-buttons/plus "Marcar férias" #(reset! open& true))
[dropdown/dropdown
{:position :left,
:open? @open&,
:on-close #(reset! open& false)}
[dropdown/menu
[dropdown/item
{:data-track "add-individual-vacation",
:on-click (utils/maybe-handler on-create :individual)}
"Individual"]
[dropdown/item
{:data-track "add-collective-vacation",
:on-click (utils/maybe-handler on-create :collective)}
"Coletiva"]]]]]]
content]]))))
(defn desk
[{:keys [company-id on-next-page]}]
(let [cursor& (r/atom nil)
importation-status& (lukla/get-state [:vacation-importation :status])]
(q/select {:company-id company-id}
{:vacation-queue-warning [:vacation-queue-warning]}
#(reset! cursor& %))
(scroll/bottom-wrap (utils/maybe-fn on-next-page)
(fn [{:keys [company-id vacations-queue default-active-tab on-change-tab],
:as props}]
[container/container
(a-page/title "Férias")
[tabs/tabs
{:default-active-key default-active-tab,
:on-change #(on-change-tab %)}
[tabs/item
{:title "Pedidos",
:event-key :waiting}
[card-wrap props [vacations-pending-desk props]]]
[tabs/item
{:title [:span
"Fila de Férias"
(when (-> @cursor&
:data
:vacation-queue-warning)
(a-badge/simple))],
:event-key :queue}
[card-wrap
(assoc props
:empty-view (empty-state-desktop company-id)
:show-empty-view? (zero? (common/attr @importation-status& :adjusted_profile_count)))
[vacations-queue-desk props]]]
[tabs/item
{:event-key :gannt,
:title "Férias marcadas"}
[card-wrap props [gantt props]]]]]))))
(defn mobile-card
[{:keys [loading? on-create]} content]
[m-mobile-frame/white-container
{:loading? loading?}
[spacing/spacing
{:direction :vertical,
:size :s}
[grid/grid
[grid/cell
{:flex 1,
:align :flex-end}
(a-buttons/plus-mobile (utils/maybe-handler on-create))]]
content]])
(defn mobi
[{:keys [company-id on-next-page]}]
(let [cursor& (r/atom nil)
open& (r/atom false)]
(q/select {:company-id company-id}
{:vacation-queue-warning [:vacation-queue-warning]}
#(reset! cursor& %))
(scroll/bottom-wrap on-next-page
(fn [{:keys [on-next-page default-active-tab on-change-tab],
:as props}]
[container/container
[:div.ph3.pt3 [a-page/title "Férias"]]
[tabs/tabs
{:default-active-key default-active-tab,
:on-change #(on-change-tab %)}
[tabs/item
{:title "Pedidos",
:event-key :waiting}
(mobile-card props [vacations-pending-mobi props])]
[tabs/item
{:title [:span
"Fila de Férias"
(when (-> @cursor&
:data
:vacation-queue-warning)
(a-badge/simple))],
:event-key :queue}
(mobile-card props [vacations-queue-mobi props])]
[tabs/item
{:event-key :gannt,
:title "Férias marcadas"}
(mobile-card props [vacations-list props])]]]))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment