Skip to content

Instantly share code, notes, and snippets.

@forki
Created June 20, 2018 06:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save forki/a9020ccc387800afd6d66bc2608113ee to your computer and use it in GitHub Desktop.
Save forki/a9020ccc387800afd6d66bc2608113ee to your computer and use it in GitHub Desktop.
module Client.TimeTable
open Elmish
open Elmish.Toastr
open Fable.Helpers.React
open Fable.Helpers.React.Props
open Shared
open Fulma
open Fable.PowerPack
open System
open CalendarTimeline
open CalendarTimeline.Props
open Fable.PowerPack.Fetch
open Fable.Core.JsInterop
open Fulma.FontAwesome
let getChangeReqsForPlanning () =
promise {
let url = "/api/entries"
let props = [
Fetch.requestHeaders [ ]
]
return! Fetch.fetchAs<MeterChangeRequestDTO []> url props
}
let moveChangeRequest (referenceId:string,startTime:string,duration,jnlBatch:string) =
promise {
let request: MoveRequest = {
JnlBatch = jnlBatch
Duration = duration
ReferenceId = referenceId
AppointmentDate = startTime
}
let body = toJson request
let url = sprintf "/api/entries/%s/move" referenceId
let props =
[ RequestProperties.Method HttpMethod.POST
Fetch.requestHeaders [
HttpRequestHeaders.ContentType "application/json; charset=utf-8" ]
RequestProperties.Body !^body ]
try
let! response = Fetch.fetch url props
if not response.Ok then
return! failwithf "Error: %d" response.Status
else
let! data = response.text()
return data
with
| _ -> return! failwithf "Kunde konnte nicht gespeichert werden."
}
type Model = {
Entries: Map<string,MeterChangeRequestDTO>
Items : Item []
Groups : Group []
MinDate : DateTime
MaxDate : DateTime
Loaded : bool
}
type Msg =
| NewEntries of MeterChangeRequestDTO []
| Refresh
| NewEntry of MeterChangeRequestDTO
| ScheduleNow of MeterChangeRequestDTO
| Moved of string * DateTime * int
| Resized of string * DateTime * string
| Success of string
| Error of exn
| MoveError of exn
| ToastClicked
| ToastClosed
let toast title message =
Toastr.message message
|> Toastr.title title
|> Toastr.position TopRight
|> Toastr.timeout 3000
|> Toastr.hideEasing Easing.Swing
|> Toastr.showCloseButton
|> Toastr.closeButtonClicked ToastClosed
|> Toastr.onClick ToastClicked
let errorToast title message : Cmd<Msg> =
toast title message
|> Toastr.error
let succesToast title message : Cmd<Msg> =
toast title message
|> Toastr.success
let init () : Model * Cmd<Msg> =
{ Entries = Map.empty
Groups = [||]
Items = [||]
Loaded = false
MinDate = DateTime.MinValue
MaxDate = DateTime.MaxValue }, Cmd.ofPromise getChangeReqsForPlanning () NewEntries Error
let update (msg : Msg) (model : Model) : Model * Cmd<Msg> =
match msg with
| NewEntries entries ->
let entries =
entries
|> Array.map (fun x -> x.ReferenceId,x)
|> Map.ofArray
{ model with
Entries = entries
Loaded = true }, Cmd.ofMsg Refresh
| Refresh ->
let now = DateTime.UtcNow
let mutable minDate = DateTime.MaxValue
let mutable maxDate = now.AddMinutes 20.
let groups =
model.Entries
|> Map.toArray
|> Array.filter (fun (_,x) -> x.IsScheduled)
|> Array.map snd
|> Array.groupBy (fun e -> e.JnlBatch)
|> Array.map (fun (batchName,entries) ->
let group = { id = batchName; title = batchName }
let items =
entries
|> Array.choose (fun e ->
match e.StartTime with
| Domain.Dates.NAVDate.DateFromNav navDate ->
let startTime = if navDate.IsDaylightSavingTime() then navDate.AddHours -1. else navDate
let endTime = startTime.AddMinutes (float e.Duration)
if startTime > now then
minDate <- min startTime minDate
maxDate <- max endTime maxDate
Some (e,
{ id = e.ReferenceId
title = e.DeviceNo
group = group.id
start_time = toUnixTime startTime
end_time = toUnixTime endTime
})
| _ -> None)
group,items)
let temp =
groups
|> Array.collect snd
let items =
temp
|> Array.map snd
let minDate = now.AddMinutes -100.
let maxDate = max (minDate.AddMinutes 300.) maxDate
{ model with
Loaded = true
Groups = groups |> Array.map fst |> Array.sortBy (fun e -> e.id)
Items = items |> Array.sortBy (fun e -> e.id)
MinDate = minDate
MaxDate = maxDate }, Cmd.none
| NewEntry entry ->
let entries =
model.Entries
|> Map.add entry.ReferenceId entry
{ model with Entries = entries }, Cmd.ofMsg Refresh
| Resized (referenceId,newTime,edge) ->
match edge with
| "right" ->
match model.Entries |> Map.tryFind referenceId with
| None -> model, errorToast "Auftrag nicht gefunden" "Auftrag nicht gefunden"
| Some entry ->
match entry.StartTime with
| Domain.Dates.NAVDate.DateFromNav navDate ->
let navDate = if navDate.IsDaylightSavingTime() then navDate.AddHours -1. else navDate
let startTime = toUnixTime navDate
let endTime = toUnixTime newTime
let duration = (((int64 endTime) - startTime) / (int64 (1000 * 60)))
let entry =
{ entry with
Duration = int duration }
model,
Cmd.batch [
Cmd.ofMsg (NewEntry entry)
Cmd.ofPromise
moveChangeRequest
(referenceId,
entry.AppointmentDate,
entry.Duration,
entry.JnlBatch)
Success
MoveError
]
| _ ->
model, Cmd.none
| _ ->
model, Cmd.none
| ScheduleNow entry ->
let newGroup = entry.JnlBatch
let newStartTime = DateTime.UtcNow
let newDate = (newStartTime).ToString("yyyy-MM-ddTHH:mm:ssZ")
let entry =
{ entry with
JnlBatch = newGroup
Duration = 30
AppointmentDate = newDate }
model,
Cmd.batch [
Cmd.ofMsg (NewEntry entry)
Cmd.ofPromise
moveChangeRequest
(entry.ReferenceId,
newDate,
entry.Duration,
entry.JnlBatch)
Success
MoveError
]
| Moved (referenceId,newStartTime,newGroup) ->
let newGroup = model.Groups.[newGroup].title
match model.Entries |> Map.tryFind referenceId with
| None -> model, errorToast "Auftrag nicht gefunden" "Auftrag nicht gefunden"
| Some entry ->
let newStartTime = if newStartTime.IsDaylightSavingTime() then newStartTime.AddHours -1. else newStartTime
let newDate = (newStartTime).ToString("yyyy-MM-ddTHH:mm:ssZ")
let entry =
{ entry with
JnlBatch = newGroup
AppointmentDate = newDate }
model,
Cmd.batch [
Cmd.ofMsg (NewEntry entry)
Cmd.ofPromise
moveChangeRequest
(referenceId,
newDate,
entry.Duration,
entry.JnlBatch)
Success
MoveError
]
| Success message ->
let message = message.Replace("\"","")
if String.IsNullOrWhiteSpace message then
model, Cmd.none
else
model,
Cmd.batch [
errorToast "Fehler beim Verschieben" message
Cmd.ofPromise getChangeReqsForPlanning () NewEntries Error
]
| MoveError exn ->
model,
Cmd.batch [
errorToast "Fehler beim Verschieben" exn.Message
Cmd.ofPromise getChangeReqsForPlanning () NewEntries Error
]
| ToastClicked ->
model, Cmd.none
| ToastClosed ->
model, Cmd.none
| Error exn ->
model, errorToast "Fehler beim Abrufen" exn.Message
let info groups entries items =
let elements = Map.count entries
section [ Class "info-tiles" ]
[ Tile.ancestor [ Tile.CustomClass TextAlignment.Classes.HasTextCentered ]
[ Tile.parent [ ]
[ article [ Class "tile is-child box" ]
[ p [ Class "title" ] [ str (string (Array.length groups)) ]
p [ Class "subtitle" ] [ str "Monteure" ] ] ]
Tile.parent [ ]
[ article [ Class "tile is-child box" ]
[ p [ Class "title" ] [ str (string elements) ]
p [ Class "subtitle" ] [ str "Termine" ] ] ]
Tile.parent [ ]
[ article [ Class "tile is-child box" ]
[ p [ Class "title" ] [ str (string (elements - Array.length items)) ]
p [ Class "subtitle" ] [ str "Ungeplant" ] ] ] ] ]
let timeLineChart dispatch (model:Model) =
timeLine [
TimelineProperties.OnItemMove (Func<_,_,_,_>(fun itemId newStartTime newGroup -> dispatch (Moved(itemId,fromUnixTime newStartTime,newGroup))))
TimelineProperties.OnItemResize (Func<_,_,_,_>(fun itemId newStartTime edge -> dispatch (Resized(itemId,fromUnixTime newStartTime,edge))))
TimelineProperties.DefaultTimeStart model.MinDate
TimelineProperties.DefaultTimeEnd model.MaxDate
TimelineProperties.SidebarContent (div [ Class "has-text-centered" ] [str "Monteur"])
TimelineProperties.Groups model.Groups
TimelineProperties.Items model.Items
] []
let search (model : Model) (dispatch : Msg -> unit) =
Control.div
[ Control.HasIconLeft
Control.HasIconRight ]
[ Input.text
[ Input.Size IsLarge ]
Icon.faIcon
[ Icon.Size IsMedium
Icon.IsLeft ]
[ Fa.icon Fa.I.Search ]
Icon.faIcon
[ Icon.Size IsMedium
Icon.IsRight ]
[ Fa.icon Fa.I.Check ] ]
let tableView (dispatch : Msg -> unit) (model : Model) =
Table.table [ Table.IsBordered
Table.IsNarrow
Table.IsStriped ] [
thead [] [
tr [] [
th [ClassName "text-left"; Style[Width "10%"]] [ str "Zählernr." ]
th [ClassName "text-left"; Style[Width "10%"]] [ str "Monteur" ]
th [ClassName "text-left"; Style[Width "20%"]] [ str "Name" ]
th [ClassName "text-left"; Style[Width "10%"]] [ str "Beginn" ]
th [ClassName "text-left"; Style[Width "10%"]] [ str "Ende" ]
th [ClassName "text-left"; Style[Width "10%"]] [ str "Dauer" ]
]
]
tbody[] [
let items =
model.Entries
|> Map.toArray
|> Array.map snd
|> Array.sortBy (fun i -> -i.Duration,i.Name)
|> Array.truncate 50
for entry in items do
yield tr [Key entry.ReferenceId ] [
td [ClassName "text-left"; Style [Width "10%"]] [ str entry.DeviceNo ]
td [ClassName "text-left"; Style [Width "10%"]] [ str entry.JnlBatch ]
td [ClassName "text-left"; Style [Width "20%"]] [ str entry.Name ]
td [ClassName "text-left"; Style [Width "10%"]] [ str (entry.StartTime.ToString()) ]
td [ClassName "text-left"; Style [Width "10%"]] [ str (entry.EndTime |> Option.map (fun d -> d.ToString("dd.MM.yy HH:mm")) |> Option.defaultValue "") ]
td [ClassName "text-left"; Style [Width "10%"]] [
if entry.IsScheduled then
yield str (entry.Duration.ToString()); yield str " min"
else
yield Button.button [Button.Option.OnClick (fun _ -> dispatch (ScheduleNow entry))] [
str "Termin planen" ] ]
]
]
]
let view (model : Model) (dispatch : Msg -> unit) =
div [ ]
[ yield info model.Groups model.Entries model.Items
if model.Loaded then
yield search model dispatch
yield timeLineChart dispatch model
yield br []
yield tableView dispatch model
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment