diff --git a/elm/Main.elm b/elm/Main.elm index e7ab596..3f86e4c 100644 --- a/elm/Main.elm +++ b/elm/Main.elm @@ -86,6 +86,15 @@ disableItems ids = Http.post , expect = Http.expectWhatever (\_ -> RcvOther) } +deleteSnack : Int -> Cmd Msg +deleteSnack snack = Http.post + { url = "/rpc/deleteSnack" + , body = Http.jsonBody (Enc.object + [ ("snack", Enc.int snack) + ]) + , expect = Http.expectWhatever (\_ -> RcvOther) + } + decodeOI = Dec.succeed OverviewItem |> requiredAt ["item", "id"] Dec.int |> requiredAt ["item", "barcode"] Dec.string @@ -94,6 +103,8 @@ decodeOI = Dec.succeed OverviewItem |> requiredAt ["item", "unitPrice"] Dec.float |> requiredAt ["item", "bought"] Dec.string |> requiredAt ["overview", "activeMappings"] Dec.int + |> requiredAt ["item", "group"] Dec.int + |> requiredAt ["overview", "groupName"] Dec.string getSnacksByItem : OverviewItem -> Cmd Msg getSnacksByItem item = rpc @@ -129,6 +140,8 @@ type alias OverviewItem = , price : Float , bought : String , activeMappings : Int + , groupId : Int + , groupName : String } type alias Location = @@ -147,7 +160,7 @@ type State , desiredInventory : Dict Int Int , overviewItems : List OverviewItem } - | SnacksEditor + | ViewingItem { item : OverviewItem , snacks : List Snack } @@ -163,6 +176,7 @@ type Msg | CallDisableItems (List Int) | CallAdjustInventory Int Int String | CallGetSnacksById OverviewItem + | CallDeleteSnack Int -- Responses | RcvLocations (Result Http.Error (List Location)) | RcvOverview (Result Http.Error (List OverviewItem)) @@ -175,6 +189,7 @@ update msg outerState = case msg of CallAdjustInventory item amount desc -> (outerState, adjustInventory item amount desc) CallDisableItems items -> (outerState, disableItems items) CallGetSnacksById item -> (outerState, getSnacksByItem item) + CallDeleteSnack snack -> (outerState, deleteSnack snack) _ -> case outerState of LoadingLocations -> case msg of RcvLocations (Ok locations) -> @@ -212,7 +227,7 @@ stateMachine msg global state = case state of , Cmd.none ) RcvSnacks item (Ok snacks) -> - (SnacksEditor { item = item, snacks = snacks }, Cmd.none) + (ViewingItem { item = item, snacks = snacks }, Cmd.none) RcvOther -> (state, getOverviewItems global.location.id) SelectItem itemId selected -> @@ -234,9 +249,13 @@ stateMachine msg global state = case state of (state, transferInventory transfers) _ -> (state, Cmd.none) - SnacksEditor { snacks } -> case msg of + ViewingItem { item } -> case msg of GoBack -> (Overview { selectedItems = Set.empty, desiredInventory = Dict.empty, overviewItems = [] }, getOverviewItems global.location.id) + RcvSnacks item_ (Ok snacks) -> + (ViewingItem { item = item_, snacks = snacks }, Cmd.none) + RcvOther -> + (state, getSnacksByItem item) _ -> (state, Cmd.none) @@ -263,7 +282,7 @@ view outerState = case outerState of viewState global state = case state of Overview { selectedItems, desiredInventory, overviewItems } -> let - header = tableCells th <| List.map text [ "", "ID", "Artikel", "Barcode", "Preis", "Kaufdatum", "Snackeinträge", "Inventar", "Aktionen" ] + header = tableCells th <| List.map text [ "", "ID", "Artikel", "EAN", "Preis", "Kaufdatum", "Snackeinträge", "Inventar", "Aktionen" ] viewOverviewItem oi = let adjustedInventory = Maybe.withDefault oi.unitsLeft <| Dict.get oi.id desiredInventory @@ -309,7 +328,7 @@ viewState global state = case state of else [ onClick <| mkAdjustInventoryMsg oi.id <| adjustedInventory - oi.unitsLeft ]) [ text <| "Inventar korrigieren" ++ viewAdjustedInventory (adjustedInventory - oi.unitsLeft) ] , button - (if oi.activeMappings /= 0 || oi.unitsLeft /= 0 + (if oi.unitsLeft /= 0 then [ disabled True ] else [ onClick <| CallDisableItems [oi.id] ]) [ text "Eintrag deaktivieren" ] @@ -318,26 +337,47 @@ viewState global state = case state of then [ disabled True ] else [ onClick <| TransferInventory oi.id ]) [ text <| String.fromInt sumSelected ++ " Einheiten umbuchen" ] - , button [ onClick <| CallGetSnacksById oi ] [ text "Snackeinträge bearbeiten" ] + , button [ onClick <| CallGetSnacksById oi ] [ text "Anzeigen" ] ] ] in div [] [ table [] <| [header] ++ List.map viewOverviewItem overviewItems ] - SnacksEditor { item, snacks } -> + ViewingItem { item, snacks } -> let - header = tableCells th <| List.map text [ "ID", "Artikel", "Barcode", "Brutto" ] + header = tableCells th <| List.map text [ "ID", "Artikel", "B arcode", "Bruttoverkaufspreis", "Aktionen" ] viewSnack snack = tableCells td [ text <| String.fromInt snack.id , text snack.name - , text snack.barcode - , text <| showEuros snack.price + , code [] [ text snack.barcode ] + , text <| showEuros snack.price ++ " (+" ++ showEuros (roundTo 2 <| snack.price - item.price) ++ ")" + , div [] + [ button [ onClick <| CallDeleteSnack snack.id ] [ text "Deaktivieren" ] + ] + ] + itemProp label value = tr [] + [ th [ style "text-align" "left" ] [ text label ] + , td [] value ] in div [] [ button [ onClick GoBack ] [ text "Zurück" ] - , p [] [ text <| "Snackeinträge für Inventareintrag " ++ String.fromInt item.id ++ " (" ++ showEuros item.price ++ " Netto)" ] + , fieldset [] + [ legend [] [ text <| "Inventareintrag " ++ String.fromInt item.id ] + , table [] + [ tbody [] + [ itemProp "ID" [ text <| String.fromInt item.id ] + , itemProp "EAN" [ code [] [ text item.barcode ] ] + , itemProp "Artikel" [ text item.name ] + , itemProp "Gruppe" [ text <| item.groupName ++ " (" ++ String.fromInt item.groupId ++ ")" ] + , itemProp "Inventar" [ text <| String.fromInt item.unitsLeft ] + , itemProp "Kaufdatum" [ text <| Tuple.first <| splitAt 'T' item.bought ] + , itemProp "Nettoeinkaufspreis" [ text <| showEuros item.price ] + ] + ] + ] + , h3 [] [ text "Snacks" ] , table [] [ thead [] [ header ] , tbody [] <| List.map viewSnack snacks @@ -376,3 +416,10 @@ showEuros x = (whole, fractional) = splitAt '.' (String.fromFloat x) in whole ++ "," ++ String.slice 0 2 (fractional ++ "00") ++ "€" + +roundTo : Int -> Float -> Float +roundTo decimals x = + let + m = toFloat <| 10^decimals + in + toFloat (round (x * m)) / m diff --git a/src/Jon/Server.hs b/src/Jon/Server.hs index cb34c61..80fd0f3 100644 --- a/src/Jon/Server.hs +++ b/src/Jon/Server.hs @@ -67,6 +67,9 @@ type SnackAPI = :<|> "updateSnack" :> Summary "Update a snack" :> ReqBody '[JSON] UpdateSnackP :> Post '[JSON] SnackId + :<|> "deleteSnack" :> Summary "Delete a snack" + :> ReqBody '[JSON] DeleteSnackP + :> PostNoContent data GetUnsoundBarcodesP = GetUnsoundBarcodesP { location :: LocationId @@ -135,6 +138,10 @@ data UpdateSnackP = UpdateSnackP , taxGroup :: TaxGroupId } deriving (Generic, FromJSON, ToSchema) +data DeleteSnackP = DeleteSnackP + { snack :: SnackId + } deriving (Generic, FromJSON, ToSchema) + -- Orphan instances for database types -- needed for serialization and swagger doc @@ -197,6 +204,7 @@ server conn = :<|> createSnack :<|> getSnacksByItemId :<|> updateSnack + :<|> deleteSnack where getUnsoundBarcodes :: GetUnsoundBarcodesP -> Handler [UnsoundBarcodeDTO] getUnsoundBarcodes params = do @@ -252,6 +260,10 @@ server conn = params.price params.taxGroup + deleteSnack params = do + liftIO $ Queries.runFunction conn $ Queries.snackDelete params.snack + pure NoContent + jonSwaggerDoc :: Swagger jonSwaggerDoc = toSwagger (Proxy :: Proxy JonAPI) & info . title .~ "jon API" diff --git a/static/jon.css b/static/jon.css index 162206c..087265d 100644 --- a/static/jon.css +++ b/static/jon.css @@ -1,5 +1,5 @@ html { - font-size: 12px; + font-size: 16px; } button, input {