001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.petstore.console.pages;
018
019import java.util.*;
020
021import org.apache.juneau.petstore.dto.*;
022import org.apache.juneau.petstore.service.*;
023import org.apache.juneau.console.freemarker.*;
024import org.apache.juneau.console.views.*;
025import org.apache.juneau.console.views.RowAction.*;
026import org.apache.juneau.console.views.TableSpec.*;
027import org.apache.juneau.console.widgets.*;
028
029/**
030 * Builder twin of the Store page (P1, {@code store.ftlh}).
031 *
032 * <h5 class='section'>Example:</h5>
033 * <p class='bjava'>
034 *    <jk>return</jk> <jsm>twin</jsm>(<jv>req</jv>, StorePageBuilders.<jsm>store</jsm>(QuickStats.<jsm>of</jsm>(store())));
035 * </p>
036 */
037@SuppressWarnings({
038   "java:S1192" // Page-definition ids and labels repeat as inline literals by design.
039})
040public final class StorePageBuilders {
041
042   private StorePageBuilders() {}
043
044   /**
045    * The Store page's quick-stat tiles, shared by both render paths.
046    *
047    * <h5 class='section'>Example:</h5>
048    * <p class='bjava'>
049    *    <jk>var</jk> <jv>qs</jv> = QuickStats.<jsm>of</jsm>(<jv>store</jv>);
050    * </p>
051    *
052    * @param available Available pets.
053    * @param pending Pending pets.
054    * @param sold Sold pets.
055    * @param ordersPlaced Placed orders.
056    * @param ordersApproved Approved orders.
057    * @param ordersDelivered Delivered orders.
058    * @param petTotal All pets, the inventory meter's maximum.
059    */
060   public record QuickStats(long available, long pending, long sold, long ordersPlaced, long ordersApproved, long ordersDelivered, long petTotal) {
061
062      /**
063       * Counts the tiles from the store.
064       *
065       * @param store The store.
066       * @return The tiles.
067       */
068      public static QuickStats of(PetStore store) {
069         var pets = store.getPets();
070         var orders = store.getOrders();
071         return new QuickStats(count(pets, PetStatus.AVAILABLE), count(pets, PetStatus.PENDING), count(pets, PetStatus.SOLD),
072            count(orders, OrderStatus.PLACED), count(orders, OrderStatus.APPROVED), count(orders, OrderStatus.DELIVERED), pets.size());
073      }
074
075      private static long count(Collection<Pet> pets, PetStatus status) {
076         return pets.stream().filter(x -> x.getStatus() == status).count();
077      }
078
079      private static long count(Collection<Order> orders, OrderStatus status) {
080         return orders.stream().filter(x -> x.getStatus() == status).count();
081      }
082   }
083
084   /** The store strip: three status tiles, an order breakdown, and an inventory meter (a tile when there are no pets). */
085   private static QuickStat[] storeQuickStats(QuickStats qs) {
086      var inventory = qs.petTotal() > 0
087         ? QuickStat.meter("inventory", "Inventory", qs.available(), qs.petTotal()).text(qs.available() + " of " + qs.petTotal())
088         : QuickStat.of("inventory", "Inventory", "0 of 0");
089      return new QuickStat[] {
090         QuickStat.of("available", "Available", qs.available()),
091         QuickStat.of("pending", "Pending", qs.pending()),
092         QuickStat.of("sold", "Sold", qs.sold()),
093         QuickStat.segmented("orders", "Orders").segment("Placed", qs.ordersPlaced())
094            .segment("Approved", qs.ordersApproved(), StatusTone.INFO).segment("Delivered", qs.ordersDelivered(), StatusTone.SUCCESS),
095         inventory
096      };
097   }
098
099   /**
100    * P1 Store.
101    *
102    * @param qs The quick-stat tiles.
103    * @return The page spec.
104    */
105   public static PageSpec store(QuickStats qs) {
106      return PageSpec.create().tab("store").toolkit("views").init("/console/dev/cards/petstore-cards.js")
107         .html("welcome", "Welcome to the petstore", """
108            <div class="jc-prose"><p>Browse pets, place orders and review what changed. Every page here is built from the same
109            Juneau console pieces your own app can use.</p>
110            <p data-slot="welcome-note">Signed in as a guest.</p></div>""")
111         .table(TableSpec.create("stats").rowType("Order").dataMode(DataMode.CLIENT).dataUrl("/console/store/recent-orders")
112            .columns(
113               Column.create("id").label("Order").href("/console/pets/{petId}/orders/{id}"),
114               Column.create("petId").label("Pet").href("/console/pets/{petId}/summary"),
115               Column.create("shipDate").label("Ship date").render("date"),
116               Column.create("status").label("Status").render("pill"))
117            .quickStats("store", storeQuickStats(qs)), "Recent orders")
118         .card(CardSpec.of("kpi", "revenue").title("Revenue")
119            .body("label", "Revenue today").body("dataUrl", "/console/store/kpi/revenue"))
120         .card(ChartDef.line("orders-trend").title("Orders, last 30 days").src("/console/store/stats/daily")
121            .x("day", null, "Day").series("placed", "Placed").series("delivered", "Delivered", "ok")
122            .onClickHref("/console/orders?state=day({day})").toCardSpec())
123         .card(ChartDef.donut("by-species").title("Pets by species").src("/console/store/stats/species")
124            .label("species").value("count").center("total").toCardSpec())
125         .card(ChartDef.bar("by-status").title("Pets by status").src("/console/store/stats/status")
126            .x("status").series("count", "Pets").toCardSpec())
127         .card(ChartDef.treemap("pets-map").title("Pets by species and status").src("/console/store/stats/pets")
128            .groupBy("species", "status").label("name")
129            .colorBy("status", ChartDef.legendItem("AVAILABLE", "Available", "ok"),
130               ChartDef.legendItem("PENDING", "Pending", "warning"),
131               ChartDef.legendItem("SOLD", "Sold", null))
132            .tileFields("id", "name", "species", "status")
133            .onClickAction(PetsPageBuilders.editPetAction(OnSuccess.REDRAW)).toCardSpec())
134         .htmlSrc("hours", "Store hours", "/console/store/fragments/hours");
135   }
136}