Created
June 20, 2018 06:19
-
-
Save forki/a9020ccc387800afd6d66bc2608113ee to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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