diff --git a/.gitignore b/.gitignore index f702e7b..3346355 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -config.yaml +/config.yaml .vscode __debug_bin \ No newline at end of file diff --git a/README.md b/README.md index 7383122..8819f67 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ # PARCOURSMOB -The PARCOURSMOB inclusive mobility platform. \ No newline at end of file +The PARCOURSMOB inclusive mobility platform. + +## Differences with previous "RIDYGO Insertion Sociale" + +This new version of PARCOURSMOB brings : + +- A delegation on authentication and identification to an external OpenID Connect Provider like [COOPGO Mobility Accounts](https://git.coopgo.io/coopgo-platform/mobility-accounts) +- A configurable and themeable approach of rendering web pages : the default theme is located in the folder [themes/default/](themes/default/) +- A modular architecture based on groups and access rights, using [COOPGO Groups Management](https://git.coopgo.io/coopgo-groups-management) +- A distributed cache system through [etcd](https://etcd.io/) to handle distributed state management like pagination in a cloud native way \ No newline at end of file diff --git a/go.mod b/go.mod index a5e9b03..7335778 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,10 @@ replace git.coopgo.io/coopgo-platform/mobility-accounts => ../../coopgo-platform replace git.coopgo.io/coopgo-platform/groups-management => ../../coopgo-platform/groups-management/ +replace git.coopgo.io/coopgo-platform/fleets => ../../coopgo-platform/fleets/ + require ( + git.coopgo.io/coopgo-platform/fleets v0.0.0-00010101000000-000000000000 git.coopgo.io/coopgo-platform/groups-management v0.0.0-00010101000000-000000000000 git.coopgo.io/coopgo-platform/mobility-accounts v0.0.0-00010101000000-000000000000 github.com/coreos/go-oidc v2.2.1+incompatible diff --git a/handlers/application/beneficiaries.go b/handlers/application/beneficiaries.go index 025ad37..422a619 100644 --- a/handlers/application/beneficiaries.go +++ b/handlers/application/beneficiaries.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "image/png" "log" @@ -34,34 +35,14 @@ type BeneficiariesForm struct { } func (h *ApplicationHandler) BeneficiariesList(w http.ResponseWriter, r *http.Request) { - g := r.Context().Value(identification.GroupKey) - if g == nil { - w.WriteHeader(http.StatusBadRequest) - return - } - group := g.(storage.Group) - - request := &mobilityaccounts.GetAccountsBatchRequest{ - Accountids: group.Members, - } - - resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request) + accounts, err := h.beneficiaries(r) if err != nil { fmt.Println(err) - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusBadRequest) return } - var accounts = []any{} - - for _, account := range resp.Accounts { - if filterAccount(r, account) { - a := account.ToStorageType() - accounts = append(accounts, a) - } - } - cacheid := uuid.NewString() h.cache.PutWithTTL(cacheid, accounts, 1*time.Hour) @@ -303,3 +284,31 @@ func filterAccount(r *http.Request, a *mobilityaccounts.Account) bool { return true } + +func (h *ApplicationHandler) beneficiaries(r *http.Request) ([]any, error) { + var accounts = []any{} + g := r.Context().Value(identification.GroupKey) + if g == nil { + return accounts, errors.New("no group provided") + } + + group := g.(storage.Group) + + request := &mobilityaccounts.GetAccountsBatchRequest{ + Accountids: group.Members, + } + + resp, err := h.services.GRPC.MobilityAccounts.GetAccountsBatch(context.TODO(), request) + if err != nil { + return accounts, err + } + + for _, account := range resp.Accounts { + if filterAccount(r, account) { + a := account.ToStorageType() + accounts = append(accounts, a) + } + } + + return accounts, err +} diff --git a/handlers/application/vehicles-management.go b/handlers/application/vehicles-management.go index d85b121..f5e9e5d 100644 --- a/handlers/application/vehicles-management.go +++ b/handlers/application/vehicles-management.go @@ -1,24 +1,208 @@ package application import ( + "context" + "encoding/json" "fmt" "net/http" + + "git.coopgo.io/coopgo-apps/parcoursmob/utils/identification" + fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" + groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi" + "git.coopgo.io/coopgo-platform/groups-management/storage" + mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi" + "github.com/google/uuid" + "github.com/gorilla/mux" + "google.golang.org/protobuf/types/known/structpb" ) func (h *ApplicationHandler) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request) { - h.Renderer.VehiclesManagementOverview(w, r) + //Get Vehicles + request := &fleets.GetVehiclesRequest{ + Namespaces: []string{"parcoursmob"}, + } + resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + } + + var vehicles = []any{} + + for _, vehicle := range resp.Vehicles { + if filterVehicle(r, vehicle) { + v := vehicle.ToStorageType() + vehicles = append(vehicles, v) + } + } + h.Renderer.VehiclesManagementOverview(w, r, vehicles) } func (h *ApplicationHandler) VehiclesFleetAdd(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { + g := r.Context().Value(identification.GroupKey) + if g == nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + group := g.(storage.Group) + if err := r.ParseForm(); err != nil { fmt.Println(err) w.WriteHeader(http.StatusBadRequest) return } - fmt.Println(r.Form) + dataMap := map[string]any{} + if v := r.FormValue("name"); v != "" { + dataMap["name"] = v + } + if v := r.FormValue("address"); v != "" { + var address map[string]any + err := json.Unmarshal([]byte(v), &address) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + dataMap["address"] = address + } + if v := r.FormValue("informations"); v != "" { + dataMap["informations"] = v + } + if v := r.FormValue("licence_plate"); v != "" { + dataMap["licence_plate"] = v + } + + data, err := structpb.NewValue(dataMap) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + vehicle := &fleets.Vehicle{ + Id: uuid.NewString(), + Namespace: "parcoursmob", + Type: r.FormValue("type"), + Administrators: []string{group.ID}, + Data: data.GetStructValue(), + } + + request := &fleets.AddVehicleRequest{ + Vehicle: vehicle, + } + + _, err = h.services.GRPC.Fleets.AddVehicle(context.TODO(), request) + + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + http.Redirect(w, r, fmt.Sprintf("/app/vehicles-management/fleet/%s", vehicle.Id), http.StatusFound) return } h.Renderer.VehiclesFleetAdd(w, r) } + +func (h *ApplicationHandler) VehiclesFleetDisplay(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + vehicleid := vars["vehicleid"] + + request := &fleets.GetVehicleRequest{ + Vehicleid: vehicleid, + } + + resp, err := h.services.GRPC.Fleets.GetVehicle(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Renderer.VehiclesFleetDisplay(w, r, resp.Vehicle.ToStorageType()) +} + +func (h *ApplicationHandler) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + vehicleid := vars["vehicleid"] + + if r.Method == "POST" { + w.WriteHeader(http.StatusNotFound) + return + } + + request := &fleets.GetVehicleRequest{ + Vehicleid: vehicleid, + } + + resp, err := h.services.GRPC.Fleets.GetVehicle(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Renderer.VehiclesFleetUpdate(w, r, resp.Vehicle.ToStorageType()) +} + +func filterVehicle(r *http.Request, v *fleets.Vehicle) bool { + g := r.Context().Value(identification.GroupKey) + if g == nil { + return false + } + + group := g.(storage.Group) + + for _, n := range v.Administrators { + if n == group.ID { + return true + } + } + + return false +} + +func (h ApplicationHandler) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bookingid := vars["bookingid"] + + request := &fleets.GetBookingRequest{ + Bookingid: bookingid, + } + resp, err := h.services.GRPC.Fleets.GetBooking(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + booking := resp.Booking.ToStorageType() + + beneficiaryrequest := &mobilityaccounts.GetAccountRequest{ + Id: booking.Driver, + } + + beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), beneficiaryrequest) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + grouprequest := &groupsmanagement.GetGroupRequest{ + Id: booking.Vehicle.Administrators[0], + } + + groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), grouprequest) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + h.Renderer.VehicleManagementBookingDisplay(w, r, booking, booking.Vehicle, beneficiaryresp.Account.ToStorageType(), groupresp.Group.ToStorageType()) +} diff --git a/handlers/application/vehicles.go b/handlers/application/vehicles.go new file mode 100644 index 0000000..5d26ddc --- /dev/null +++ b/handlers/application/vehicles.go @@ -0,0 +1,151 @@ +package application + +import ( + "context" + "fmt" + "net/http" + "time" + + fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" + mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi" + "github.com/google/uuid" + "github.com/gorilla/mux" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func (h ApplicationHandler) VehiclesSearch(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + var beneficiary any + + vehicles := []any{} + searched := false + start := r.FormValue("startdate") + end := r.FormValue("enddate") + + startdate, _ := time.Parse("2006-01-02", start) + enddate, _ := time.Parse("2006-01-02", end) + + if r.FormValue("beneficiaryid") != "" { + // Handler form + searched = true + + requestbeneficiary := &mobilityaccounts.GetAccountRequest{ + Id: r.FormValue("beneficiaryid"), + } + + respbeneficiary, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), requestbeneficiary) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + beneficiary = respbeneficiary.Account.ToStorageType() + + request := &fleets.GetVehiclesRequest{ + Namespaces: []string{"parcoursmob"}, + } + resp, err := h.services.GRPC.Fleets.GetVehicles(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + } + + for _, vehicle := range resp.Vehicles { + v := vehicle.ToStorageType() + if v.Free(startdate, enddate) { + vehicles = append(vehicles, v) + } + } + } + + accounts, err := h.beneficiaries(r) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusBadRequest) + return + } + + h.Renderer.VehiclesSearch(w, r, accounts, searched, vehicles, beneficiary, r.FormValue("startdate"), r.FormValue("enddate")) +} + +func (h ApplicationHandler) Book(w http.ResponseWriter, r *http.Request) { + + vars := mux.Vars(r) + vehicleid := vars["vehicleid"] + beneficiaryid := vars["beneficiaryid"] + + r.ParseForm() + + start := r.FormValue("startdate") + end := r.FormValue("enddate") + + startdate, _ := time.Parse("2006-01-02", start) + enddate, _ := time.Parse("2006-01-02", end) + + booking := &fleets.Booking{ + Id: uuid.NewString(), + Vehicleid: vehicleid, + Driver: beneficiaryid, + Startdate: timestamppb.New(startdate), + Enddate: timestamppb.New(enddate), + Unavailablefrom: timestamppb.New(startdate), + Unavailableto: timestamppb.New(enddate.Add(72 * time.Hour)), + } + + request := &fleets.CreateBookingRequest{ + Booking: booking, + } + + _, err := h.services.GRPC.Fleets.CreateBooking(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusBadRequest) + return + } + + http.Redirect(w, r, fmt.Sprintf("/app/vehicles/bookings/%s", booking.Id), http.StatusFound) + +} + +func (h ApplicationHandler) VehicleBookingDisplay(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + bookingid := vars["bookingid"] + + request := &fleets.GetBookingRequest{ + Bookingid: bookingid, + } + resp, err := h.services.GRPC.Fleets.GetBooking(context.TODO(), request) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + booking := resp.Booking.ToStorageType() + + beneficiaryrequest := &mobilityaccounts.GetAccountRequest{ + Id: booking.Driver, + } + + beneficiaryresp, err := h.services.GRPC.MobilityAccounts.GetAccount(context.TODO(), beneficiaryrequest) + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + // grouprequest := &groupsmanagement.GetGroupRequest{ + // Id: booking.Vehicle.Administrators[0], + // } + + // groupresp, err := h.services.GRPC.GroupsManagement.GetGroup(context.TODO(), grouprequest) + // if err != nil { + // fmt.Println(err) + // w.WriteHeader(http.StatusInternalServerError) + // return + // } + + h.Renderer.VehicleBookingDisplay(w, r, booking, booking.Vehicle, beneficiaryresp.Account.ToStorageType(), "") +} diff --git a/main.go b/main.go index e6f27d3..859959e 100644 --- a/main.go +++ b/main.go @@ -68,8 +68,14 @@ func main() { application.HandleFunc("/beneficiaries/{beneficiaryid}", applicationHandler.BeneficiaryDisplay) application.HandleFunc("/beneficiaries/{beneficiaryid}/update", applicationHandler.BeneficiaryUpdate) application.HandleFunc("/beneficiaries/{beneficiaryid}/picture", applicationHandler.BeneficiaryPicture) + application.HandleFunc("/vehicles/", applicationHandler.VehiclesSearch) + application.HandleFunc("/vehicles/bookings/{bookingid}", applicationHandler.VehicleBookingDisplay) + application.HandleFunc("/vehicles/v/{vehicleid}/b/{beneficiaryid}", applicationHandler.Book) application.HandleFunc("/vehicles-management/", applicationHandler.VehiclesManagementOverview) application.HandleFunc("/vehicles-management/fleet/add", applicationHandler.VehiclesFleetAdd) + application.HandleFunc("/vehicles-management/fleet/{vehicleid}", applicationHandler.VehiclesFleetDisplay) + application.HandleFunc("/vehicles-management/fleet/{vehicleid}/update", applicationHandler.VehiclesFleetUpdate) + application.HandleFunc("/vehicles-management/bookings/{bookingid}", applicationHandler.VehicleManagementBookingDisplay) //TODO Subrouters with middlewares checking security for each module ? application.Use(idp.Middleware) application.Use(idp.GroupsMiddleware) diff --git a/renderer/func-maps.go b/renderer/func-maps.go index 1b062c3..561fee2 100644 --- a/renderer/func-maps.go +++ b/renderer/func-maps.go @@ -1,7 +1,9 @@ package renderer import ( + "encoding/json" "fmt" + "html/template" "time" ) @@ -47,6 +49,11 @@ func GenderISO5218(d any) string { return "" } +func JSON(v any) template.JS { + result, _ := json.Marshal(v) + return template.JS(result) +} + func Dict(v ...interface{}) map[string]interface{} { dict := map[string]interface{}{} lenv := len(v) diff --git a/renderer/renderer.go b/renderer/renderer.go index 7673bbc..e4f9f66 100644 --- a/renderer/renderer.go +++ b/renderer/renderer.go @@ -49,6 +49,7 @@ func (renderer *Renderer) Render(name string, w http.ResponseWriter, r *http.Req "timeFrom": TimeFrom, "genderISO5218": GenderISO5218, "dict": Dict, + "json": JSON, }, ) t = template.Must(t.ParseFiles(prefixed_files...)) @@ -70,6 +71,7 @@ func (renderer *Renderer) RenderNoLayout(name string, w http.ResponseWriter, r * "timeFrom": TimeFrom, "genderISO5218": GenderISO5218, "dict": Dict, + "json": JSON, }, ) @@ -144,13 +146,13 @@ func NewState(r *http.Request, themeConfig *viper.Viper, menuState string) Rende Title: "Déplacements", Link: "/app/journeys/", Active: menuState == "journeys", - Icon: "hero:outline/user-group", + Icon: "hero:outline/map", }) } if modules["vehicles"].(bool) { ls.MenuItems = append(ls.MenuItems, MenuItem{ - Title: "Véhicules", + Title: "Véhicules partagés", Link: "/app/vehicles/", Active: menuState == "vehicles", Icon: "hero:outline/user-group", @@ -162,7 +164,7 @@ func NewState(r *http.Request, themeConfig *viper.Viper, menuState string) Rende Title: "Gestion des véhicules", Link: "/app/vehicles-management/", Active: menuState == "vehicles_management", - Icon: "hero:outline/user-group", + Icon: "hero:outline/briefcase", }) } @@ -171,7 +173,7 @@ func NewState(r *http.Request, themeConfig *viper.Viper, menuState string) Rende Title: "Dispositifs", Link: "/app/events/", Active: menuState == "events", - Icon: "hero:outline/user-group", + Icon: "hero:outline/calendar", }) } diff --git a/renderer/vehicle-management.go b/renderer/vehicle-management.go index 36c4ffd..dbb9217 100644 --- a/renderer/vehicle-management.go +++ b/renderer/vehicle-management.go @@ -4,16 +4,52 @@ import "net/http" const vehiclesmanagementMenu = "vehicles_management" -func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request) { +func (renderer *Renderer) VehiclesManagementOverview(w http.ResponseWriter, r *http.Request, vehicles []any) { files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.overview.files") state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu) + state.ViewState = map[string]any{ + "vehicles": vehicles, + } - renderer.Render("fleet", w, r, files, state) + renderer.Render("fleet overview", w, r, files, state) } func (renderer *Renderer) VehiclesFleetAdd(w http.ResponseWriter, r *http.Request) { files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_add.files") state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu) - renderer.Render("fleet", w, r, files, state) + renderer.Render("fleet add vehicle", w, r, files, state) +} + +func (renderer *Renderer) VehiclesFleetDisplay(w http.ResponseWriter, r *http.Request, vehicle any) { + files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_display.files") + state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu) + state.ViewState = map[string]any{ + "vehicle": vehicle, + } + + renderer.Render("fleet display vehicle", w, r, files, state) +} + +func (renderer *Renderer) VehiclesFleetUpdate(w http.ResponseWriter, r *http.Request, vehicle any) { + files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.fleet_update.files") + state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu) + state.ViewState = map[string]any{ + "vehicle": vehicle, + } + + renderer.Render("fleet display vehicle", w, r, files, state) +} + +func (renderer *Renderer) VehicleManagementBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any) { + files := renderer.ThemeConfig.GetStringSlice("views.vehicles_management.booking_display.files") + state := NewState(r, renderer.ThemeConfig, vehiclesmanagementMenu) + state.ViewState = map[string]any{ + "booking": booking, + "vehicle": vehicle, + "beneficiary": beneficiary, + "group": group, + } + + renderer.Render("vehicles search", w, r, files, state) } diff --git a/renderer/vehicles.go b/renderer/vehicles.go new file mode 100644 index 0000000..50982df --- /dev/null +++ b/renderer/vehicles.go @@ -0,0 +1,40 @@ +package renderer + +import "net/http" + +const vehiclesMenu = "vehicles" + +func (renderer *Renderer) VehiclesSearch(w http.ResponseWriter, r *http.Request, beneficiaries []any, searched bool, vehicles []any, beneficiary any, startdate any, enddate any) { + files := renderer.ThemeConfig.GetStringSlice("views.vehicles.search.files") + state := NewState(r, renderer.ThemeConfig, vehiclesMenu) + viewstate := map[string]any{ + "beneficiaries": beneficiaries, + "searched": searched, + } + + if searched { + viewstate["search"] = map[string]any{ + "startdate": startdate, + "enddate": enddate, + "vehicles": vehicles, + "beneficiary": beneficiary, + } + } + + state.ViewState = viewstate + + renderer.Render("vehicles search", w, r, files, state) +} + +func (renderer *Renderer) VehicleBookingDisplay(w http.ResponseWriter, r *http.Request, booking any, vehicle any, beneficiary any, group any) { + files := renderer.ThemeConfig.GetStringSlice("views.vehicles.booking_display.files") + state := NewState(r, renderer.ThemeConfig, vehiclesMenu) + state.ViewState = map[string]any{ + "booking": booking, + "vehicle": vehicle, + "beneficiary": beneficiary, + "group": group, + } + + renderer.Render("vehicles search", w, r, files, state) +} diff --git a/services/fleets.go b/services/fleets.go new file mode 100644 index 0000000..a47a7b2 --- /dev/null +++ b/services/fleets.go @@ -0,0 +1,23 @@ +package services + +import ( + fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" + "google.golang.org/grpc" +) + +type FleetsService struct { + fleets.FleetsClient +} + +func NewFleetsService(dial string) (*FleetsService, error) { + conn, err := grpc.Dial(dial, grpc.WithInsecure()) + + client := fleets.NewFleetsClient(conn) + if err != nil { + return nil, err + } + + return &FleetsService{ + FleetsClient: client, + }, nil +} diff --git a/services/services.go b/services/services.go index ac6be54..bc17da5 100644 --- a/services/services.go +++ b/services/services.go @@ -1,6 +1,7 @@ package services import ( + fleets "git.coopgo.io/coopgo-platform/fleets/grpcapi" groupsmanagement "git.coopgo.io/coopgo-platform/groups-management/grpcapi" mobilityaccounts "git.coopgo.io/coopgo-platform/mobility-accounts/grpcapi" "github.com/spf13/viper" @@ -13,12 +14,14 @@ type ServicesHandler struct { type GRPCServices struct { MobilityAccounts mobilityaccounts.MobilityAccountsClient GroupsManagement groupsmanagement.GroupsManagementClient + Fleets fleets.FleetsClient } func NewServicesHandler(cfg *viper.Viper) (*ServicesHandler, error) { var ( mobilityAccountsDial = cfg.GetString("services.grpc.mobilityaccounts.dial") groupsManagementDial = cfg.GetString("services.grpc.groupsmanagement.dial") + fleetsDial = cfg.GetString("services.grpc.fleets.dial") ) mobilityAccounts, err := NewMobilityAccountService(mobilityAccountsDial) if err != nil { @@ -30,10 +33,16 @@ func NewServicesHandler(cfg *viper.Viper) (*ServicesHandler, error) { return nil, err } + fleetsSvc, err := NewFleetsService(fleetsDial) + if err != nil { + return nil, err + } + return &ServicesHandler{ GRPC: GRPCServices{ MobilityAccounts: mobilityAccounts, GroupsManagement: groupsManagement, + Fleets: fleetsSvc, }, }, nil } diff --git a/themes/default/config.yaml b/themes/default/config.yaml new file mode 100644 index 0000000..13f42f5 --- /dev/null +++ b/themes/default/config.yaml @@ -0,0 +1,99 @@ +name: PARCOURSMOB + +views: + generic: + files: + - layouts/layout.html + - layouts/_partials/mainmenu.html + dashboard: + files: + - layouts/dashboard/_partials/beneficiaries-widget.html + - layouts/dashboard/dashboard.html + beneficiaries: + list: + files: + - layouts/beneficiaries/list.html + create: + files: + - layouts/_partials/address_autocomplete.html + - layouts/beneficiaries/create.html + display: + files: + - layouts/vehicles_management/_partials/vehicle-type-select.html + - layouts/beneficiaries/_partials/beneficiary-vehicles.html + - layouts/beneficiaries/_partials/beneficiary-notes.html + - layouts/beneficiaries/_partials/beneficiary-journeys.html + - layouts/beneficiaries/_partials/beneficiary-events.html + - layouts/beneficiaries/_partials/beneficiary-files.html + - layouts/beneficiaries/display.html + update: + files: + - layouts/_partials/address_autocomplete.html + - layouts/beneficiaries/update.html + vehicles: + search: + files: + - layouts/_partials/address_autocomplete.html + - layouts/vehicles_management/_partials/vehicle-type-select.html + - layouts/vehicles/search.html + booking_display: + files: + - layouts/vehicles/booking-display.html + vehicles_management: + overview: + files: + - layouts/vehicles_management/_partials/bookings-list.html + - layouts/vehicles_management/_partials/vehicles-list.html + - layouts/vehicles_management/overview.html + fleet_add: + files: + - layouts/_partials/address_autocomplete.html + - layouts/vehicles_management/_partials/vehicle-type-select.html + - layouts/vehicles_management/fleet-add.html + fleet_display: + files: + - layouts/vehicles_management/_partials/calendar.html + - layouts/vehicles_management/fleet-display.html + fleet_update: + files: + - layouts/_partials/address_autocomplete.html + - layouts/vehicles_management/_partials/vehicle-type-select.html + - layouts/vehicles_management/fleet-update.html + booking_display: + files: + - layouts/vehicles_management/booking-display.html + administration: + home: + files: + - layouts/administration/home.html + create_group: + files: + - layouts/administration/create_group.html + display_group: + files: + - layouts/administration/_partials/groups_admins.html + - layouts/administration/display_group.html + auth: + groups: + files: + - layouts/auth/groups.html + +icons: + svg: + coopgo:parcoursmob/monogram: + hero:outline/briefcase: + hero:outline/calendar: + hero:outline/cog: + hero:outline/home: + hero:outline/map: + hero:outline/office-building: + hero:outline/plus-circle: + hero:outline/shield-check: + hero:outline/user-group: + hero:outline/x: + hero:solid/chevron-right: + hero:solid/question-mark-icon: + hero:solid/search: + hero:solid/selector: + img:profile-picture-placeholder: + diff --git a/themes/default/layouts/administration/_partials/groups_admins.html b/themes/default/layouts/administration/_partials/groups_admins.html index 64292ef..34040cc 100644 --- a/themes/default/layouts/administration/_partials/groups_admins.html +++ b/themes/default/layouts/administration/_partials/groups_admins.html @@ -7,13 +7,13 @@
- +
-

Leslie Alexander

-

aaa@bbb.fr

+

Arnaud Delcasse

+

arnaud.delcasse@coopgo.fr

diff --git a/themes/default/layouts/administration/display_group.html b/themes/default/layouts/administration/display_group.html index fce538f..c669351 100644 --- a/themes/default/layouts/administration/display_group.html +++ b/themes/default/layouts/administration/display_group.html @@ -14,10 +14,10 @@
- + class="w-full px-4 py-2 border border-transparent text-sm font-medium rounded-2xl shadow-sm text-white bg-co-blue hover:bg-co-blue focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-co-blue">Modifier -->
diff --git a/themes/default/layouts/administration/home.html b/themes/default/layouts/administration/home.html index 7f7dea8..3a561c6 100644 --- a/themes/default/layouts/administration/home.html +++ b/themes/default/layouts/administration/home.html @@ -15,7 +15,7 @@ class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-3xl truncate">
Bénéficiaires -

160 bénéficiaires

+

{{len (index .ViewState.groups 0).Members}} bénéficiaires

@@ -29,7 +29,7 @@ class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-3xl truncate">
Organisations -

12 organisations

+

{{len .ViewState.groups}} organisations

@@ -43,7 +43,7 @@ class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-3xl truncate">
Référents -

53 membres

+

1 membres

@@ -57,7 +57,7 @@ class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-3xl truncate">
Accompagnement -

363 actions réalisées

+

0 actions réalisées

@@ -106,10 +106,10 @@
Dries Vincent - Lindsay Walton @@ -119,7 +119,7 @@ Tom Cook + alt="Tom Cook"> -->
diff --git a/themes/default/layouts/beneficiaries/_partials/beneficiary-events.html b/themes/default/layouts/beneficiaries/_partials/beneficiary-events.html index 1256a08..684622f 100644 --- a/themes/default/layouts/beneficiaries/_partials/beneficiary-events.html +++ b/themes/default/layouts/beneficiaries/_partials/beneficiary-events.html @@ -1,5 +1,4 @@ {{define "beneficiary_events"}}
- TODO Dispositifs
{{end}} \ No newline at end of file diff --git a/themes/default/layouts/beneficiaries/_partials/beneficiary-journeys.html b/themes/default/layouts/beneficiaries/_partials/beneficiary-journeys.html index 1e3f850..7d31762 100644 --- a/themes/default/layouts/beneficiaries/_partials/beneficiary-journeys.html +++ b/themes/default/layouts/beneficiaries/_partials/beneficiary-journeys.html @@ -1,5 +1,4 @@ {{define "beneficiary_journeys"}}
- TODO Déplacements
{{end}} \ No newline at end of file diff --git a/themes/default/layouts/beneficiaries/_partials/beneficiary-notes.html b/themes/default/layouts/beneficiaries/_partials/beneficiary-notes.html index 34c64c4..a793a17 100644 --- a/themes/default/layouts/beneficiaries/_partials/beneficiary-notes.html +++ b/themes/default/layouts/beneficiaries/_partials/beneficiary-notes.html @@ -1,7 +1,7 @@ {{define "beneficiary_notes"}}
diff --git a/themes/default/layouts/beneficiaries/_partials/beneficiary-vehicles.html b/themes/default/layouts/beneficiaries/_partials/beneficiary-vehicles.html new file mode 100644 index 0000000..b5d4a77 --- /dev/null +++ b/themes/default/layouts/beneficiaries/_partials/beneficiary-vehicles.html @@ -0,0 +1,31 @@ +{{define "beneficiary_vehicles"}} +
+

Réserver un véhicule

+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ {{template "vehicle_type_select" .}} + + +
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/beneficiaries/display.html b/themes/default/layouts/beneficiaries/display.html index c14b847..2f42ebc 100644 --- a/themes/default/layouts/beneficiaries/display.html +++ b/themes/default/layouts/beneficiaries/display.html @@ -120,10 +120,10 @@ :class="tab == 'events' ? 'border-co-blue text-co-blue' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'"> Dispositifs - - Documents + Documents -->
@@ -131,6 +131,7 @@
{{template "beneficiary_notes" .}}
{{template "beneficiary_journeys" .}}
+
{{template "beneficiary_vehicles" .}}
{{template "beneficiary_events" .}}
{{template "beneficiary_files" .}}
@@ -141,162 +142,7 @@

Actions réalisées

- - -
-
    -
  • -
    - -
    -
    - - - - -
    -
    -
    -

    Applied to Front End Developer

    -
    -
    - -
    -
    -
    -
    -
  • - -
  • -
    - -
    -
    - - - - -
    -
    -
    -

    Advanced to phone screening by Bethany Blake

    -
    -
    - -
    -
    -
    -
    -
  • - -
  • -
    - -
    -
    - - - - -
    -
    -
    -

    Completed phone screening with Martha Gardner

    -
    -
    - -
    -
    -
    -
    -
  • - -
  • -
    - -
    -
    - - - - -
    -
    -
    -

    Advanced to interview by Bethany Blake

    -
    -
    - -
    -
    -
    -
    -
  • - -
  • -
    -
    -
    - - - - -
    -
    -
    -

    Completed interview with Katherine Snyder

    -
    -
    - -
    -
    -
    -
    -
  • -
-
-
- -
+

Aucune action réalisée pour le moment

diff --git a/themes/default/layouts/dashboard/dashboard.html b/themes/default/layouts/dashboard/dashboard.html index a6bf543..331c9df 100644 --- a/themes/default/layouts/dashboard/dashboard.html +++ b/themes/default/layouts/dashboard/dashboard.html @@ -3,6 +3,67 @@

Tableau de bord

+
+

Statistiques de votre organisation

+ +
+
diff --git a/themes/default/layouts/layout.html b/themes/default/layouts/layout.html index ee3448a..f2d3425 100644 --- a/themes/default/layouts/layout.html +++ b/themes/default/layouts/layout.html @@ -118,8 +118,8 @@ x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95"> - Paramètres + Changer d'organisation @@ -135,7 +135,7 @@ id="user-menu-button" aria-expanded="false" aria-haspopup="true"> Open user menu Menu utilisateur
diff --git a/themes/default/layouts/vehicles/booking-display.html b/themes/default/layouts/vehicles/booking-display.html new file mode 100644 index 0000000..afc7eb4 --- /dev/null +++ b/themes/default/layouts/vehicles/booking-display.html @@ -0,0 +1,137 @@ +{{define "content"}} +
+

Réservation de véhicule

+ +
+
+

+
+ +
+
+
+
+

Bénéficiaire

+
+
+
+
+
+
Nom
+
+ {{.ViewState.beneficiary.Data.first_name}} + {{.ViewState.beneficiary.Data.last_name}}
+
+
+
Email
+
+ {{.ViewState.beneficiary.Data.email}}
+
+
+
Téléphone
+
+ {{.ViewState.beneficiary.Data.phone_number}}
+
+ {{if .ViewState.Data.birthdate}} +
+
Date de naissance
+
{{(timeFrom + .ViewState.Data.birthdate).Format + "02/01/2006"}}
+
+ {{end}} + {{if and .ViewState.Data.gender (ne .ViewState.Data.gender "0")}} +
+
Date de naissance
+
{{genderISO5218 + .ViewState.Data.gender}}
+
+ {{end}} + {{if .ViewState.Data.address}} +
+
Adresse
+
+ {{.ViewState.Data.address.properties.label}}
+
+ {{end}} +
+
+
+
+
+
+
+
+
+
+
+

Réservation

+

Informations utiles sur la réservation.

+
+
+ + + +
+
+
+
+
+
+
+
+
Gestionnaire
+
+ COOPGO +
+
+
+
Véhicule
+
+ {{.ViewState.vehicle.Data.name}}
+
+
+
Immatriculation
+
+ {{.ViewState.vehicle.Data.licence_plate}}
+
+
+
Type
+
+ Voiture
+
+ {{if .ViewState.vehicle.Data.address}} +
+
Lieu de récupération
+
{{.ViewState.vehicle.Data.address.properties.label}}
+
+ {{end}} +
+
Date de récupération
+
{{(timeFrom .ViewState.booking.Startdate).Format + "02/01/2006"}}
+
+
+
Date de fin
+
{{(timeFrom .ViewState.booking.Enddate).Format + "02/01/2006"}}
+
+
+
+
+
+
+
+
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/vehicles/search.html b/themes/default/layouts/vehicles/search.html new file mode 100644 index 0000000..518aefb --- /dev/null +++ b/themes/default/layouts/vehicles/search.html @@ -0,0 +1,158 @@ +{{define "content"}} +
+

Véhicules partagés

+ + + +
+
+
+

Chercher un véhicule

+
+
+ + +
+ + +
+ + + + +
    + + + + +
+
+
+ + + +
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+ {{template "vehicle_type_select" .}} + + +
+
+
+
+
+ {{if .ViewState.searched}} +
+

Véhicules disponibles

+
+
+
+ + + + + + + + + + + + {{range .ViewState.search.vehicles}} + + + + + + + + {{end}} + +
VéhiculeNuméroGestionnaireLieu + Réserver +
{{.Data.name}}{{.Data.licence_plate}}COOPGO{{if .Data.address}}{{.Data.address.properties.label}}{{end}} + Réserver pour {{$.ViewState.search.beneficiary.Data.first_name}} {{$.ViewState.search.beneficiary.Data.last_name}} +
+
+
+
+
+ {{end}} +
+
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/vehicles_management/_partials/bookings-list.html b/themes/default/layouts/vehicles_management/_partials/bookings-list.html index 3b803d3..a646f4a 100644 --- a/themes/default/layouts/vehicles_management/_partials/bookings-list.html +++ b/themes/default/layouts/vehicles_management/_partials/bookings-list.html @@ -7,26 +7,23 @@ - - --> + - - - - - + {{range .ViewState.vehicles}} + {{$vehicle := .}} + {{range .Bookings}} - + + - - - + {{end}} + {{end}}
Statut - + Type + Immatriculation + Beneficiaire - Prescripteur - + Gestionnaire + Dates @@ -35,33 +32,38 @@
+ + +
Voiture
-
aa
+
{{$vehicle.Data.licence_plate}}
+
+
+
COOPGO
-
aa
+
Du {{(timeFrom .Startdate).Format "02/01/2006"}} au {{(timeFrom .Enddate).Format "02/01/2006"}}
-
aa
-
-
aa
-
-
aa
-
- Voir
diff --git a/themes/default/layouts/vehicles_management/_partials/calendar.html b/themes/default/layouts/vehicles_management/_partials/calendar.html new file mode 100644 index 0000000..5a6dbb5 --- /dev/null +++ b/themes/default/layouts/vehicles_management/_partials/calendar.html @@ -0,0 +1,259 @@ +{{define "calendar"}} +
+

Août 2022

+ + +
+
+
L
+
M
+
M
+
J
+
V
+
S
+
D
+
+
+
+ + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/vehicles_management/_partials/vehicles-list.html b/themes/default/layouts/vehicles_management/_partials/vehicles-list.html index 36fff55..c8840ae 100644 --- a/themes/default/layouts/vehicles_management/_partials/vehicles-list.html +++ b/themes/default/layouts/vehicles_management/_partials/vehicles-list.html @@ -9,16 +9,16 @@ - Immatriculation + Numéro (Immat / Bicycode) - - Gestionnaire + + Type - + Modèle - - Gestionnaire + + Lieu Actions @@ -26,24 +26,26 @@ + {{range .ViewState.vehicles}} -
aa
+
{{.Data.licence_plate}}
-
aa
+
{{if eq .Type "electric_bike"}}Vélo électrique{{else}}Voiture{{end}}
-
aa
+
{{.Data.name}}
-
aa
+
{{if .Data.address}}{{.Data.address.properties.label}}{{end}}
- Voir + {{end}} diff --git a/themes/default/layouts/vehicles_management/booking-display.html b/themes/default/layouts/vehicles_management/booking-display.html new file mode 100644 index 0000000..2dbeaeb --- /dev/null +++ b/themes/default/layouts/vehicles_management/booking-display.html @@ -0,0 +1,147 @@ +{{define "content"}} +
+

Réservation de véhicule

+ +
+
+

+
+ +
+
+
+
+

Bénéficiaire

+
+
+
+
+
+
Nom
+
+ {{.ViewState.beneficiary.Data.first_name}} + {{.ViewState.beneficiary.Data.last_name}}
+
+
+
Email
+
+ {{.ViewState.beneficiary.Data.email}}
+
+
+
Téléphone
+
+ {{.ViewState.beneficiary.Data.phone_number}}
+
+ {{if .ViewState.Data.birthdate}} +
+
Date de naissance
+
{{(timeFrom + .ViewState.Data.birthdate).Format + "02/01/2006"}}
+
+ {{end}} + {{if and .ViewState.Data.gender (ne .ViewState.Data.gender "0")}} +
+
Date de naissance
+
{{genderISO5218 + .ViewState.Data.gender}}
+
+ {{end}} + {{if .ViewState.Data.address}} +
+
Adresse
+
+ {{.ViewState.Data.address.properties.label}}
+
+ {{end}} +
+
+
+
+
+
+
+
+
+
+
+

Réservation

+

Informations utiles sur la réservation.

+
+
+ + + +
+
+
+
+
+
+
+
+
Gestionnaire
+
+ COOPGO +
+
+
+
Véhicule
+
+ {{.ViewState.vehicle.Data.name}}
+
+
+
Immatriculation
+
+ {{.ViewState.vehicle.Data.licence_plate}}
+
+
+
Type
+
+ Voiture
+
+ {{if .ViewState.vehicle.Data.address}} +
+
Lieu de récupération
+
{{.ViewState.vehicle.Data.address.properties.label}}
+
+ {{end}} +
+
Date de récupération
+
{{(timeFrom .ViewState.booking.Startdate).Format + "02/01/2006"}}
+
+
+
Date de fin
+
{{(timeFrom .ViewState.booking.Enddate).Format + "02/01/2006"}}
+
+
+
Indisponible à partir du
+
{{(timeFrom .ViewState.booking.Unavailablefrom).Format + "02/01/2006"}}
+
+
+
Sera à nouveau disponible le
+
{{(timeFrom .ViewState.booking.Unavailableto).Format + "02/01/2006"}}
+
+
+
+
+
+
+
+
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/vehicles_management/fleet-display.html b/themes/default/layouts/vehicles_management/fleet-display.html new file mode 100644 index 0000000..387f392 --- /dev/null +++ b/themes/default/layouts/vehicles_management/fleet-display.html @@ -0,0 +1,90 @@ +{{define "content"}} +
+
+
+ +
+

{{.ViewState.vehicle.Data.name}}

+
+
+
+ + +
+
+
+
+
+
+
+

Informations

+

Informations sur le véhicule

+
+
+
+ {{if .ViewState.vehicle.Data.type}} +
+
Type
+
{{if eq .ViewState.vehicle.Data.type "electric_bike"}}Vélo électrique{{else}}Voiture{{end}}
+
+ {{else}} +
+
Type
+
Voiture
+
+ {{end}} + {{if .ViewState.vehicle.Data.licence_plate}} +
+
Numéro (Immatriculation, bicycode, ...)
+
{{.ViewState.vehicle.Data.licence_plate}}
+
+ {{end}} + {{if .ViewState.vehicle.Data.address}} +
+
Lieu
+
{{.ViewState.vehicle.Data.address.properties.label}}
+
+ {{end}} + {{if .ViewState.vehicle.Data.informations}} +
+
Informations pratiques pour le bénéficiaire
+
{{.ViewState.vehicle.Data.informations}}
+
+ {{end}} + +
+
+
+
+
+
+

Réservations à venir

+ {{if eq (len .ViewState.vehicle.Bookings) 0}} +

Aucune réservation à venir

+ {{end}} + + {{template "calendar" .}} +
+
+
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/vehicles_management/fleet-update.html b/themes/default/layouts/vehicles_management/fleet-update.html new file mode 100644 index 0000000..88b6770 --- /dev/null +++ b/themes/default/layouts/vehicles_management/fleet-update.html @@ -0,0 +1,106 @@ +{{define "content"}} + + +
+

Modifier un véhicule

+
+ +
+
+
+
+
+

Identité du véhicule

+

Informations de base sur le véhicule

+
+
+
+
+ + +
+
+ {{template "vehicle_type_select" .}} +
+
+ + +
+
+
+
+
+
+
+
+

Informations pratiques

+

Informations pratiques pour la réservation

+
+
+ {{ $fieldName := "address" }} + {{ template "address_autocomplete" (dict "FieldName" $fieldName "Address" .ViewState.vehicle.Data.address) }} + +
+ +
+ +
+
+
+
+
+ +
+

Certains champs de sont pas valides.

+ + + + +
+
+
+{{end}} \ No newline at end of file diff --git a/themes/default/layouts/vehicles_management/overview.html b/themes/default/layouts/vehicles_management/overview.html index 39886ea..9ab5eda 100644 --- a/themes/default/layouts/vehicles_management/overview.html +++ b/themes/default/layouts/vehicles_management/overview.html @@ -17,7 +17,7 @@
diff --git a/themes/default/public/css/main.css b/themes/default/public/css/main.css index 4f7da2c..5d43147 100644 --- a/themes/default/public/css/main.css +++ b/themes/default/public/css/main.css @@ -842,6 +842,10 @@ html { grid-column: span 5 / span 5; } +.m-auto { + margin: auto; +} + .mx-auto { margin-left: auto; margin-right: auto; @@ -857,6 +861,21 @@ html { margin-right: -1rem; } +.-my-1\.5 { + margin-top: -0.375rem; + margin-bottom: -0.375rem; +} + +.-my-1 { + margin-top: -0.25rem; + margin-bottom: -0.25rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + .-mr-12 { margin-right: -3rem; } @@ -949,6 +968,26 @@ html { margin-top: -0.5rem; } +.-mr-1\.5 { + margin-right: -0.375rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.-mr-1 { + margin-right: -0.25rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.-mt-4 { + margin-top: -1rem; +} + .block { display: block; } @@ -1117,6 +1156,14 @@ html { flex: 1 1 0%; } +.flex-auto { + flex: 1 1 auto; +} + +.flex-none { + flex: none; +} + .flex-shrink-0 { flex-shrink: 0; } @@ -1174,6 +1221,10 @@ html { user-select: none; } +.grid-flow-row { + grid-auto-flow: row; +} + .grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); } @@ -1186,6 +1237,14 @@ html { grid-template-columns: repeat(3, minmax(0, 1fr)); } +.grid-cols-7 { + grid-template-columns: repeat(7, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + .flex-row { flex-direction: row; } @@ -1424,6 +1483,16 @@ html { border-bottom-left-radius: 1rem; } +.rounded-l-2xl { + border-top-left-radius: 1rem; + border-bottom-left-radius: 1rem; +} + +.rounded-r-2xl { + border-top-right-radius: 1rem; + border-bottom-right-radius: 1rem; +} + .border { border-width: 1px; } @@ -1452,6 +1521,14 @@ html { border-bottom-width: 2px; } +.border-r-0 { + border-right-width: 0px; +} + +.border-l-0 { + border-left-width: 0px; +} + .border-dashed { border-style: dashed; } @@ -1570,6 +1647,11 @@ html { background-color: rgb(79 70 229 / var(--tw-bg-opacity)); } +.bg-gray-900 { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity)); +} + .bg-opacity-75 { --tw-bg-opacity: 0.75; } @@ -1586,6 +1668,22 @@ html { padding: 0.5rem; } +.p-1\.5 { + padding: 0.375rem; +} + +.p-1 { + padding: 0.25rem; +} + +.p-8 { + padding: 2rem; +} + +.p-12 { + padding: 3rem; +} + .px-4 { padding-left: 1rem; padding-right: 1rem; @@ -1739,6 +1837,14 @@ html { padding-left: 0.375rem; } +.pl-3\.5 { + padding-left: 0.875rem; +} + +.pr-12 { + padding-right: 3rem; +} + .text-left { text-align: left; } @@ -1810,6 +1916,10 @@ html { font-weight: 800; } +.font-black { + font-weight: 900; +} + .uppercase { text-transform: uppercase; } @@ -1884,6 +1994,11 @@ html { color: rgb(75 85 99 / var(--tw-text-opacity)); } +.text-co-green { + --tw-text-opacity: 1; + color: rgb(108 193 31 / var(--tw-text-opacity)); +} + .placeholder-gray-500::-moz-placeholder { --tw-placeholder-opacity: 1; color: rgb(107 114 128 / var(--tw-placeholder-opacity)); @@ -2083,6 +2198,11 @@ html { background-color: rgb(67 56 202 / var(--tw-bg-opacity)); } +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + .hover\:bg-opacity-5:hover { --tw-bg-opacity: 0.05; } @@ -2121,6 +2241,16 @@ html { color: inherit; } +.hover\:text-gray-500:hover { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.hover\:text-indigo-500:hover { + --tw-text-opacity: 1; + color: rgb(99 102 241 / var(--tw-text-opacity)); +} + .focus\:border-transparent:focus { border-color: transparent; } @@ -2202,6 +2332,11 @@ html { --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity)); } +.focus\:ring-co-red:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(255 19 0 / var(--tw-ring-opacity)); +} + .focus\:ring-offset-2:focus { --tw-ring-offset-width: 2px; } @@ -2262,6 +2397,10 @@ html { display: flex; } + .sm\:grid { + display: grid; + } + .sm\:hidden { display: none; } @@ -2302,6 +2441,10 @@ html { grid-template-columns: repeat(6, minmax(0, 1fr)); } + .sm\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + .sm\:flex-row-reverse { flex-direction: row-reverse; } @@ -2326,6 +2469,10 @@ html { gap: 1.5rem; } + .sm\:gap-4 { + gap: 1rem; + } + .sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse))); @@ -2342,6 +2489,17 @@ html { --tw-space-x-reverse: 1; } + .sm\:divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); + } + + .sm\:divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity)); + } + .sm\:overflow-hidden { overflow: hidden; } @@ -2354,6 +2512,10 @@ html { border-radius: 1.5rem; } + .sm\:rounded-2xl { + border-radius: 1rem; + } + .sm\:p-6 { padding: 1.5rem; } @@ -2363,6 +2525,11 @@ html { padding-right: 1.5rem; } + .sm\:py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; + } + .sm\:pl-6 { padding-left: 1.5rem; } @@ -2371,6 +2538,10 @@ html { padding-right: 1.5rem; } + .sm\:pb-5 { + padding-bottom: 1.25rem; + } + .sm\:text-sm { font-size: 0.875rem; line-height: 1.25rem; @@ -2493,6 +2664,10 @@ html { padding-left: 0px; } + .md\:pr-0 { + padding-right: 0px; + } + .md\:text-right { text-align: right; } @@ -2515,6 +2690,10 @@ html { grid-column-start: 3; } + .lg\:col-start-2 { + grid-column-start: 2; + } + .lg\:-mx-8 { margin-left: -2rem; margin-right: -2rem;