2 Commits

Author SHA1 Message Date
Arnaud Delcasse
b1323867c7 Add public web functionalities 2026-01-05 17:50:05 +01:00
Arnaud Delcasse
6cda7509c1 MMS43 changes 2025-12-29 10:50:58 +01:00
10 changed files with 477 additions and 82 deletions

View File

@@ -23,6 +23,11 @@ func ReadConfig() (*viper.Viper, error) {
"enabled": true, "enabled": true,
"listen": "0.0.0.0:8081", "listen": "0.0.0.0:8081",
}, },
"publicweb": map[string]any{
"enabled": false,
"listen": "0.0.0.0:8082",
"root_dir": "public_themes/default",
},
}, },
"identification": map[string]any{ "identification": map[string]any{
"sessions": map[string]any{ "sessions": map[string]any{

View File

@@ -24,12 +24,12 @@ import (
) )
type DashboardResult struct { type DashboardResult struct {
Accounts []mobilityaccountsstorage.Account Accounts []mobilityaccountsstorage.Account
Members []mobilityaccountsstorage.Account Members []mobilityaccountsstorage.Account
Events []agendastorage.Event Events []agendastorage.Event
Bookings []fleetstorage.Booking Bookings []fleetstorage.Booking
SolidarityDrivers []mobilityaccountsstorage.Account SolidarityDrivers []mobilityaccountsstorage.Account
OrganizedCarpoolDrivers []mobilityaccountsstorage.Account OrganizedCarpoolDrivers []mobilityaccountsstorage.Account
} }
func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddressGeoLayer, driverAddressGeoCode string) (*DashboardResult, error) { func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddressGeoLayer, driverAddressGeoCode string) (*DashboardResult, error) {
@@ -62,14 +62,7 @@ func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddress
accounts := []mobilityaccountsstorage.Account{} accounts := []mobilityaccountsstorage.Account{}
// We only display the 5 most recent here for _, account := range resp.Accounts {
count := len(resp.Accounts)
min := count - 5
if min < 0 {
min = 0
}
for _, account := range resp.Accounts[min:] {
// Check if not archived // Check if not archived
if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived { if archived, ok := account.Data.AsMap()["archived"].(bool); !ok || !archived {
a := account.ToStorageType() a := account.ToStorageType()
@@ -225,4 +218,3 @@ func (h *ApplicationHandler) GetDashboardData(ctx context.Context, driverAddress
OrganizedCarpoolDrivers: organizedCarpoolDrivers, OrganizedCarpoolDrivers: organizedCarpoolDrivers,
}, nil }, nil
} }

View File

@@ -281,7 +281,7 @@ func (h *ApplicationHandler) GetVehicleDisplay(ctx context.Context, vehicleID st
}, nil }, nil
} }
func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID, startdate, enddate, unavailablefrom, unavailableto string) error { func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID string, startdate, enddate *time.Time, unavailablefrom, unavailableto string) error {
booking, err := h.services.GetBooking(bookingID) booking, err := h.services.GetBooking(bookingID)
if err != nil { if err != nil {
return fmt.Errorf("failed to get booking: %w", err) return fmt.Errorf("failed to get booking: %w", err)
@@ -289,21 +289,19 @@ func (h *ApplicationHandler) UpdateBooking(ctx context.Context, bookingID, start
newbooking, _ := fleets.BookingFromStorageType(&booking) newbooking, _ := fleets.BookingFromStorageType(&booking)
if startdate != "" { if startdate != nil {
newstartdate, _ := time.Parse("2006-01-02", startdate) newbooking.Startdate = timestamppb.New(*startdate)
newbooking.Startdate = timestamppb.New(newstartdate)
if newstartdate.Before(newbooking.Unavailablefrom.AsTime()) { if startdate.Before(newbooking.Unavailablefrom.AsTime()) {
newbooking.Unavailablefrom = timestamppb.New(newstartdate) newbooking.Unavailablefrom = timestamppb.New(*startdate)
} }
} }
if enddate != "" { if enddate != nil {
newenddate, _ := time.Parse("2006-01-02", enddate) newbooking.Enddate = timestamppb.New(*enddate)
newbooking.Enddate = timestamppb.New(newenddate)
if newenddate.After(newbooking.Unavailableto.AsTime()) || newenddate.Equal(newbooking.Unavailableto.AsTime()) { if enddate.After(newbooking.Unavailableto.AsTime()) || enddate.Equal(newbooking.Unavailableto.AsTime()) {
newbooking.Unavailableto = timestamppb.New(newenddate.Add(24 * time.Hour)) newbooking.Unavailableto = timestamppb.New(enddate.Add(24 * time.Hour))
} }
} }

View File

@@ -31,8 +31,8 @@ type VehiclesSearchResult struct {
BeneficiaryDocuments []filestorage.FileInfo BeneficiaryDocuments []filestorage.FileInfo
Groups map[string]any Groups map[string]any
Searched bool Searched bool
StartDate string StartDate time.Time
EndDate string EndDate time.Time
VehicleType string VehicleType string
Automatic bool Automatic bool
MandatoryDocuments []string MandatoryDocuments []string
@@ -41,22 +41,13 @@ type VehiclesSearchResult struct {
Beneficiaries []mobilityaccountsstorage.Account Beneficiaries []mobilityaccountsstorage.Account
} }
func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID, startDateStr, endDateStr, vehicleType string, automatic bool) (*VehiclesSearchResult, error) { func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID string, startdate, enddate time.Time, vehicleType string, automatic bool) (*VehiclesSearchResult, error) {
var beneficiary mobilityaccountsstorage.Account var beneficiary mobilityaccountsstorage.Account
beneficiarydocuments := []filestorage.FileInfo{} beneficiarydocuments := []filestorage.FileInfo{}
vehicles := []storage.Vehicle{} vehicles := []storage.Vehicle{}
searched := false searched := false
administrators := []string{} administrators := []string{}
startdate, err := time.Parse("2006-01-02", startDateStr)
if err != nil {
startdate = time.Time{}
}
enddate, err := time.Parse("2006-01-02", endDateStr)
if err != nil {
enddate = time.Time{}
}
if beneficiaryID != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) { if beneficiaryID != "" && startdate.After(time.Now().Add(-24*time.Hour)) && enddate.After(startdate) {
// Handler form // Handler form
searched = true searched = true
@@ -165,8 +156,8 @@ func (h *ApplicationHandler) SearchVehicles(ctx context.Context, beneficiaryID,
BeneficiaryDocuments: beneficiarydocuments, BeneficiaryDocuments: beneficiarydocuments,
Groups: groups, Groups: groups,
Searched: searched, Searched: searched,
StartDate: startDateStr, StartDate: startdate,
EndDate: endDateStr, EndDate: enddate,
VehicleType: vehicleType, VehicleType: vehicleType,
Automatic: automatic, Automatic: automatic,
MandatoryDocuments: mandatoryDocuments, MandatoryDocuments: mandatoryDocuments,
@@ -180,7 +171,7 @@ type BookVehicleResult struct {
BookingID string BookingID string
} }
func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, beneficiaryID, startDateStr, endDateStr string, documents map[string]io.Reader, documentHeaders map[string]string, existingDocs map[string]string, currentUserID string, currentUserClaims map[string]any, currentGroup any) (*BookVehicleResult, error) { func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, beneficiaryID string, startdate, enddate time.Time, documents map[string]io.Reader, documentHeaders map[string]string, existingDocs map[string]string, currentUserID string, currentUserClaims map[string]any, currentGroup any) (*BookVehicleResult, error) {
group := currentGroup.(groupsmanagementstorage.Group) group := currentGroup.(groupsmanagementstorage.Group)
vehicle, err := h.services.GRPC.Fleets.GetVehicle(ctx, &fleets.GetVehicleRequest{ vehicle, err := h.services.GRPC.Fleets.GetVehicle(ctx, &fleets.GetVehicleRequest{
@@ -190,15 +181,6 @@ func (h *ApplicationHandler) BookVehicle(ctx context.Context, vehicleID, benefic
return nil, fmt.Errorf("vehicle not found: %w", err) return nil, fmt.Errorf("vehicle not found: %w", err)
} }
startdate, err := time.Parse("2006-01-02", startDateStr)
if err != nil {
return nil, fmt.Errorf("invalid start date: %w", err)
}
enddate, err := time.Parse("2006-01-02", endDateStr)
if err != nil {
return nil, fmt.Errorf("invalid end date: %w", err)
}
data := map[string]any{ data := map[string]any{
"booked_by": map[string]any{ "booked_by": map[string]any{
"user": map[string]any{ "user": map[string]any{

16
main.go
View File

@@ -10,6 +10,7 @@ import (
"git.coopgo.io/coopgo-apps/parcoursmob/core/application" "git.coopgo.io/coopgo-apps/parcoursmob/core/application"
"git.coopgo.io/coopgo-apps/parcoursmob/renderer" "git.coopgo.io/coopgo-apps/parcoursmob/renderer"
"git.coopgo.io/coopgo-apps/parcoursmob/servers/mcp" "git.coopgo.io/coopgo-apps/parcoursmob/servers/mcp"
"git.coopgo.io/coopgo-apps/parcoursmob/servers/publicweb"
"git.coopgo.io/coopgo-apps/parcoursmob/servers/web" "git.coopgo.io/coopgo-apps/parcoursmob/servers/web"
"git.coopgo.io/coopgo-apps/parcoursmob/services" "git.coopgo.io/coopgo-apps/parcoursmob/services"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
@@ -24,9 +25,10 @@ func main() {
} }
var ( var (
dev_env = cfg.GetBool("dev_env") dev_env = cfg.GetBool("dev_env")
webEnabled = cfg.GetBool("server.web.enabled") webEnabled = cfg.GetBool("server.web.enabled")
mcpEnabled = cfg.GetBool("server.mcp.enabled") mcpEnabled = cfg.GetBool("server.mcp.enabled")
publicwebEnabled = cfg.GetBool("server.publicweb.enabled")
) )
if dev_env { if dev_env {
@@ -80,5 +82,13 @@ func main() {
}() }()
} }
if publicwebEnabled {
wg.Add(1)
go func() {
defer wg.Done()
publicweb.Run(cfg, svc, applicationHandler, kv, filestorage)
}()
}
wg.Wait() wg.Wait()
} }

View File

@@ -0,0 +1,142 @@
package publicweb
import (
"net/http"
"time"
"github.com/paulmach/orb/geojson"
"github.com/rs/zerolog/log"
)
// JourneySearchResponse represents the search results for hydration
type JourneySearchResponse struct {
Searched bool `json:"searched"`
DepartureDate string `json:"departure_date,omitempty"`
DepartureTime string `json:"departure_time,omitempty"`
Departure any `json:"departure,omitempty"`
Destination any `json:"destination,omitempty"`
Error string `json:"error,omitempty"`
Results struct {
SolidarityDrivers struct {
Number int `json:"number"`
} `json:"solidarity_drivers"`
OrganizedCarpools struct {
Number int `json:"number"`
} `json:"organized_carpools"`
Carpools struct {
Number int `json:"number"`
Results any `json:"results,omitempty"`
} `json:"carpools"`
PublicTransit struct {
Number int `json:"number"`
Results any `json:"results,omitempty"`
} `json:"public_transit"`
Vehicles struct {
Number int `json:"number"`
Results any `json:"results,omitempty"`
} `json:"vehicles"`
LocalSolutions struct {
Number int `json:"number"`
Results any `json:"results,omitempty"`
} `json:"local_solutions"`
} `json:"results"`
}
// journeySearchDataProvider provides data for the journey search page
func (s *PublicWebServer) journeySearchDataProvider(r *http.Request) (any, error) {
if err := r.ParseForm(); err != nil {
log.Error().Err(err).Msg("error parsing form")
return JourneySearchResponse{Error: "invalid request"}, nil
}
departureDate := r.FormValue("departuredate")
departureTime := r.FormValue("departuretime")
departure := r.FormValue("departure")
destination := r.FormValue("destination")
response := JourneySearchResponse{
DepartureDate: departureDate,
DepartureTime: departureTime,
}
// If no search parameters, return empty response
if departure == "" || destination == "" || departureDate == "" || departureTime == "" {
return response, nil
}
// Parse timezone and datetime
locTime, err := time.LoadLocation("Europe/Paris")
if err != nil {
log.Error().Err(err).Msg("timezone error")
response.Error = "internal error"
return response, nil
}
departureDateTime, err := time.ParseInLocation("2006-01-02 15:04", departureDate+" "+departureTime, locTime)
if err != nil {
log.Error().Err(err).Msg("error parsing datetime")
response.Error = "invalid date/time format"
return response, nil
}
// Parse departure location
departureGeo, err := geojson.UnmarshalFeature([]byte(departure))
if err != nil {
log.Error().Err(err).Msg("error unmarshalling departure")
response.Error = "invalid departure location"
return response, nil
}
response.Departure = departureGeo
// Parse destination location
destinationGeo, err := geojson.UnmarshalFeature([]byte(destination))
if err != nil {
log.Error().Err(err).Msg("error unmarshalling destination")
response.Error = "invalid destination location"
return response, nil
}
response.Destination = destinationGeo
// Call business logic
result, err := s.applicationHandler.SearchJourneys(
r.Context(),
departureDateTime,
departureGeo,
destinationGeo,
"", // passengerID
"", // solidarityTransportExcludeDriver
"", // solidarityExcludeGroupId
nil, // options - use defaults
)
if err != nil {
log.Error().Err(err).Msg("error in journey search")
response.Error = "search failed"
return response, nil
}
response.Searched = result.Searched
// Solidarity drivers
response.Results.SolidarityDrivers.Number = len(result.DriverJourneys)
// Organized carpools
response.Results.OrganizedCarpools.Number = len(result.OrganizedCarpools)
// Carpools (from external operators like Movici)
response.Results.Carpools.Number = len(result.CarpoolResults)
response.Results.Carpools.Results = result.CarpoolResults
// Public transit
response.Results.PublicTransit.Number = len(result.TransitResults)
response.Results.PublicTransit.Results = result.TransitResults
// Fleet vehicles
response.Results.Vehicles.Number = len(result.VehicleResults)
response.Results.Vehicles.Results = result.VehicleResults
// Knowledge base / local solutions
response.Results.LocalSolutions.Number = len(result.KnowledgeBaseResults)
response.Results.LocalSolutions.Results = result.KnowledgeBaseResults
return response, nil
}

View File

@@ -0,0 +1,191 @@
package publicweb
import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"git.coopgo.io/coopgo-apps/parcoursmob/core/application"
cache "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/storage"
"git.coopgo.io/coopgo-apps/parcoursmob/services"
)
// DataProvider returns data to hydrate a page
type DataProvider func(r *http.Request) (any, error)
// DynamicRoute defines a route with its HTML file and data provider
type DynamicRoute struct {
HTMLFile string
DataProvider DataProvider
}
// Regex to find the placeholder script tag
var dynamicDataRegex = regexp.MustCompile(`<script\s+id="dynamic-data"\s+type="application/json">\s*</script>`)
type PublicWebServer struct {
cfg *viper.Viper
services *services.ServicesHandler
kv cache.KVHandler
filestorage cache.FileStorage
applicationHandler *application.ApplicationHandler
rootDir string
dynamicRoutes map[string]DynamicRoute
}
func Run(
cfg *viper.Viper,
svc *services.ServicesHandler,
applicationHandler *application.ApplicationHandler,
kv cache.KVHandler,
filestorage cache.FileStorage,
) {
address := cfg.GetString("server.publicweb.listen")
rootDir := cfg.GetString("server.publicweb.root_dir")
serviceName := cfg.GetString("service_name")
server := &PublicWebServer{
cfg: cfg,
services: svc,
kv: kv,
filestorage: filestorage,
applicationHandler: applicationHandler,
rootDir: rootDir,
dynamicRoutes: make(map[string]DynamicRoute),
}
server.registerDynamicRoutes()
r := mux.NewRouter()
r.HandleFunc("/health", server.healthHandler).Methods("GET")
for pattern := range server.dynamicRoutes {
r.HandleFunc(pattern, server.dynamicHandler).Methods("GET", "POST")
}
r.PathPrefix("/").Handler(server.fileServerHandler())
srv := &http.Server{
Handler: r,
Addr: address,
WriteTimeout: 30 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Info().
Str("service_name", serviceName).
Str("address", address).
Str("root_dir", rootDir).
Msg("Running Public Web HTTP server")
err := srv.ListenAndServe()
log.Error().Err(err).Msg("Public Web server error")
}
func (s *PublicWebServer) registerDynamicRoutes() {
s.RegisterDynamicRoute("/recherche/", "recherche/index.html", s.journeySearchDataProvider)
}
func (s *PublicWebServer) RegisterDynamicRoute(pattern, htmlFile string, provider DataProvider) {
s.dynamicRoutes[pattern] = DynamicRoute{
HTMLFile: htmlFile,
DataProvider: provider,
}
}
func (s *PublicWebServer) dynamicHandler(w http.ResponseWriter, r *http.Request) {
route := mux.CurrentRoute(r)
pattern, _ := route.GetPathTemplate()
dynRoute, exists := s.dynamicRoutes[pattern]
if !exists {
http.NotFound(w, r)
return
}
data, err := dynRoute.DataProvider(r)
if err != nil {
log.Error().Err(err).Str("route", pattern).Msg("Error getting data")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
if err := s.hydrate(w, dynRoute.HTMLFile, data); err != nil {
http.NotFound(w, r)
}
}
// hydrate reads an HTML file and injects JSON data into <script id="dynamic-data" type="application/json"></script>
func (s *PublicWebServer) hydrate(w http.ResponseWriter, htmlFile string, data any) error {
htmlPath := filepath.Join(s.rootDir, htmlFile)
htmlContent, err := os.ReadFile(htmlPath)
if err != nil {
log.Error().Err(err).Str("file", htmlPath).Msg("Error reading HTML file")
return err
}
jsonData, err := json.Marshal(data)
if err != nil {
log.Error().Err(err).Msg("Error marshaling data to JSON")
return err
}
// Replace the placeholder with a script that assigns data to window.__PARCOURSMOB_DATA__
replacement := []byte(`<script id="dynamic-data">window.__PARCOURSMOB_DATA__ = ` + string(jsonData) + `;</script>`)
modifiedHTML := dynamicDataRegex.ReplaceAll(htmlContent, replacement)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(modifiedHTML)
return nil
}
func (s *PublicWebServer) fileServerHandler() http.Handler {
fs := http.FileServer(http.Dir(s.rootDir))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := filepath.Join(s.rootDir, r.URL.Path)
if _, err := os.Stat(path); os.IsNotExist(err) {
if filepath.Ext(path) == "" {
if idx := filepath.Join(path, "index.html"); fileExists(idx) {
http.ServeFile(w, r, idx)
return
}
if idx := filepath.Join(s.rootDir, "index.html"); fileExists(idx) {
http.ServeFile(w, r, idx)
return
}
}
http.NotFound(w, r)
return
}
if info, _ := os.Stat(path); info != nil && info.IsDir() {
if idx := filepath.Join(path, "index.html"); fileExists(idx) && strings.HasSuffix(r.URL.Path, "/") {
http.ServeFile(w, r, idx)
return
}
}
fs.ServeHTTP(w, r)
})
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
func (s *PublicWebServer) healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"healthy"}`))
}

View File

@@ -9,10 +9,10 @@ import (
"strings" "strings"
"time" "time"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage" groupstorage "git.coopgo.io/coopgo-platform/groups-management/storage"
mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage" mobilityaccountsstorage "git.coopgo.io/coopgo-platform/mobility-accounts/storage"
gen "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen" gen "git.coopgo.io/coopgo-platform/solidarity-transport/servers/grpc/proto/gen"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@@ -25,7 +25,7 @@ func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
// Extract filter parameters using the same field names as the old handler // Extract filter parameters using the same field names as the old handler
tab := r.FormValue("tab") tab := r.FormValue("tab")
if tab == "" { if tab == "" {
tab = "solidarityService" // Default to showing current bookings tab = "solidarityService" // Default to showing current bookings
} }
// Extract archived filter // Extract archived filter
@@ -55,7 +55,7 @@ func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
// History filters (apply when on solidarityHistory tab) // History filters (apply when on solidarityHistory tab)
if tab == "solidarityHistory" { if tab == "solidarityHistory" {
histStatus = r.FormValue("status") // Note: history uses same field names as current histStatus = r.FormValue("status") // Note: history uses same field names as current
histDriverID = r.FormValue("driver_id") histDriverID = r.FormValue("driver_id")
histStartDate = r.FormValue("date_start") histStartDate = r.FormValue("date_start")
histEndDate = r.FormValue("date_end") histEndDate = r.FormValue("date_end")
@@ -135,25 +135,25 @@ func (h *Handler) SolidarityTransportOverviewHTTPHandler() http.HandlerFunc {
// Build filters map for template (matching old handler format) // Build filters map for template (matching old handler format)
filters := map[string]any{ filters := map[string]any{
"tab": tab, "tab": tab,
"date_start": startDate, "date_start": startDate,
"date_end": endDate, "date_end": endDate,
"status": status, "status": status,
"driver_id": driverID, "driver_id": driverID,
"departure_geo": departureGeo, "departure_geo": departureGeo,
"destination_geo": destinationGeo, "destination_geo": destinationGeo,
"passenger_address_geo": passengerAddressGeo, "passenger_address_geo": passengerAddressGeo,
"driver_address_geo": driverAddressGeo, "driver_address_geo": driverAddressGeo,
} }
histFilters := map[string]any{ histFilters := map[string]any{
"tab": tab, "tab": tab,
"date_start": histStartDate, "date_start": histStartDate,
"date_end": histEndDate, "date_end": histEndDate,
"status": histStatus, "status": histStatus,
"driver_id": histDriverID, "driver_id": histDriverID,
"departure_geo": histDepartureGeo, "departure_geo": histDepartureGeo,
"destination_geo": histDestinationGeo, "destination_geo": histDestinationGeo,
"passenger_address_geo": histPassengerAddressGeo, "passenger_address_geo": histPassengerAddressGeo,
} }
@@ -521,7 +521,11 @@ func (h *Handler) SolidarityTransportDriverJourneyHTTPHandler() http.HandlerFunc
vars := mux.Vars(r) vars := mux.Vars(r)
driverID := vars["driverid"] driverID := vars["driverid"]
journeyID := vars["journeyid"] journeyID := vars["journeyid"]
// Preserve passengerid from either query parameter or form data
passengerID := r.URL.Query().Get("passengerid") passengerID := r.URL.Query().Get("passengerid")
if passengerID == "" {
passengerID = r.FormValue("passengerid")
}
replacesBookingID := r.URL.Query().Get("replaces_booking_id") replacesBookingID := r.URL.Query().Get("replaces_booking_id")
if r.Method == "POST" { if r.Method == "POST" {
@@ -580,7 +584,16 @@ func (h *Handler) SolidarityTransportDriverJourneyToggleNoreturnHTTPHandler() ht
return return
} }
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/drivers/%s/journeys/%s", driverID, journeyID), http.StatusFound) // Preserve passengerid query parameter if present
passengerID := r.URL.Query().Get("passengerid")
if passengerID == "" {
passengerID = r.FormValue("passengerid")
}
redirectURL := fmt.Sprintf("/app/solidarity-transport/drivers/%s/journeys/%s", driverID, journeyID)
if passengerID != "" {
redirectURL += "?passengerid=" + passengerID
}
http.Redirect(w, r, redirectURL, http.StatusFound)
} }
} }
@@ -641,11 +654,11 @@ func (h *Handler) SolidarityTransportBookingDisplayHTTPHandler() http.HandlerFun
if err == nil { if err == nil {
pricing := map[string]interface{}{ pricing := map[string]interface{}{
"passenger": map[string]interface{}{ "passenger": map[string]interface{}{
"amount": pricingResult["passenger"].Amount, "amount": pricingResult["passenger"].Amount,
"currency": pricingResult["passenger"].Currency, "currency": pricingResult["passenger"].Currency,
}, },
"driver": map[string]interface{}{ "driver": map[string]interface{}{
"amount": pricingResult["driver"].Amount, "amount": pricingResult["driver"].Amount,
"currency": pricingResult["driver"].Currency, "currency": pricingResult["driver"].Currency,
}, },
} }
@@ -716,7 +729,7 @@ func (h *Handler) SolidarityTransportCreateReplacementBookingHTTPHandler() http.
journeyID, journeyID,
passengerID, passengerID,
motivation, motivation,
message, // message from form message, // message from form
doNotSend, // doNotSend from form checkbox doNotSend, // doNotSend from form checkbox
returnWaitingTimeMinutes, returnWaitingTimeMinutes,
oldBookingID, oldBookingID,
@@ -771,4 +784,5 @@ func (h *Handler) SolidarityTransportBookingStatusHTTPHandler(action string) htt
http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/bookings/%s", bookingID), http.StatusSeeOther) http.Redirect(w, r, fmt.Sprintf("/app/solidarity-transport/bookings/%s", bookingID), http.StatusSeeOther)
} }
} }

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"time"
"git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification" "git.coopgo.io/coopgo-apps/parcoursmob/core/utils/identification"
"git.coopgo.io/coopgo-platform/groups-management/storage" "git.coopgo.io/coopgo-platform/groups-management/storage"
@@ -18,11 +19,31 @@ func (h *Handler) VehiclesSearchHTTPHandler() http.HandlerFunc {
// Extract parameters // Extract parameters
beneficiaryID := r.FormValue("beneficiaryid") beneficiaryID := r.FormValue("beneficiaryid")
startDate := r.FormValue("startdate") startDateStr := r.FormValue("startdate")
endDate := r.FormValue("enddate") endDateStr := r.FormValue("enddate")
startTimeStr := r.FormValue("starttime")
endTimeStr := r.FormValue("endtime")
vehicleType := r.FormValue("type") vehicleType := r.FormValue("type")
automatic := r.FormValue("automatic") == "on" automatic := r.FormValue("automatic") == "on"
// Default times if not provided
if startTimeStr == "" {
startTimeStr = "00:00"
}
if endTimeStr == "" {
endTimeStr = "23:59"
}
// Parse dates and times in Europe/Paris timezone
paris, _ := time.LoadLocation("Europe/Paris")
var startDate, endDate time.Time
if startDateStr != "" {
startDate, _ = time.ParseInLocation("2006-01-02 15:04", startDateStr+" "+startTimeStr, paris)
}
if endDateStr != "" {
endDate, _ = time.ParseInLocation("2006-01-02 15:04", endDateStr+" "+endTimeStr, paris)
}
// Call business logic // Call business logic
result, err := h.applicationHandler.SearchVehicles(r.Context(), beneficiaryID, startDate, endDate, vehicleType, automatic) result, err := h.applicationHandler.SearchVehicles(r.Context(), beneficiaryID, startDate, endDate, vehicleType, automatic)
if err != nil { if err != nil {
@@ -58,8 +79,23 @@ func (h *Handler) BookVehicleHTTPHandler() http.HandlerFunc {
} }
// Extract form data // Extract form data
startDate := r.FormValue("startdate") startDateStr := r.FormValue("startdate")
endDate := r.FormValue("enddate") endDateStr := r.FormValue("enddate")
startTimeStr := r.FormValue("starttime")
endTimeStr := r.FormValue("endtime")
// Default times if not provided
if startTimeStr == "" {
startTimeStr = "00:00"
}
if endTimeStr == "" {
endTimeStr = "23:59"
}
// Parse dates and times in Europe/Paris timezone
paris, _ := time.LoadLocation("Europe/Paris")
startDate, _ := time.ParseInLocation("2006-01-02 15:04", startDateStr+" "+startTimeStr, paris)
endDate, _ := time.ParseInLocation("2006-01-02 15:04", endDateStr+" "+endTimeStr, paris)
// Extract documents // Extract documents
documents := make(map[string]io.Reader) documents := make(map[string]io.Reader)

View File

@@ -178,9 +178,34 @@ func (h *Handler) VehicleManagementBookingDisplayHTTPHandler() http.HandlerFunc
// Parse form data // Parse form data
r.ParseForm() r.ParseForm()
// Extract date and time values
startDateStr := r.FormValue("startdate")
startTimeStr := r.FormValue("starttime")
endDateStr := r.FormValue("enddate")
endTimeStr := r.FormValue("endtime")
// Parse dates with times in Europe/Paris timezone
paris, _ := time.LoadLocation("Europe/Paris")
var startDate, endDate *time.Time
if startDateStr != "" {
if startTimeStr == "" {
startTimeStr = "00:00"
}
t, _ := time.ParseInLocation("2006-01-02 15:04", startDateStr+" "+startTimeStr, paris)
startDate = &t
}
if endDateStr != "" {
if endTimeStr == "" {
endTimeStr = "23:59"
}
t, _ := time.ParseInLocation("2006-01-02 15:04", endDateStr+" "+endTimeStr, paris)
endDate = &t
}
err := h.applicationHandler.UpdateBooking(r.Context(), bookingID, err := h.applicationHandler.UpdateBooking(r.Context(), bookingID,
r.FormValue("startdate"), startDate,
r.FormValue("enddate"), endDate,
r.FormValue("unavailablefrom"), r.FormValue("unavailablefrom"),
r.FormValue("unavailableto"), r.FormValue("unavailableto"),
) )