The cards drag too, held by their headings
Same arrangement the provider panel gives its readings, applied to the four charging cards: drag one and the column reorders live under the pointer, the card being dragged goes half-transparent, the one it is over takes a ring, and the rail's lock holds the lot still. Two things differ from the readings row, both because these are four different things rather than four of one. A card is placed with the CSS order property instead of by moving markup, so each keeps its own template and its own v-if. And the handle is the card's heading rather than the whole card — a card that was draggable everywhere would fight the current-limit slider and the address fields for the pointer. Saved on the profile as charger_card_order, beside the tab order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1418a566fd
commit
3c64d6e84c
@@ -43,7 +43,8 @@ type userRecord struct {
|
||||
CarOrder json.RawMessage `json:"car_order"`
|
||||
// The charging page's tab arrangement, stored the same way and for the same
|
||||
// reason.
|
||||
ChargerTabOrder json.RawMessage `json:"charger_tab_order"`
|
||||
ChargerTabOrder json.RawMessage `json:"charger_tab_order"`
|
||||
ChargerCardOrder json.RawMessage `json:"charger_card_order"`
|
||||
}
|
||||
|
||||
// carOrder decodes the stored garage arrangement, treating anything unexpected
|
||||
@@ -52,6 +53,8 @@ func (rec userRecord) carOrder() []string { return decodeStringList(rec.CarOrder
|
||||
|
||||
func (rec userRecord) chargerTabOrder() []string { return decodeStringList(rec.ChargerTabOrder) }
|
||||
|
||||
func (rec userRecord) chargerCardOrder() []string { return decodeStringList(rec.ChargerCardOrder) }
|
||||
|
||||
// decodeStringList reads a PocketBase json field that holds a list of strings,
|
||||
// treating anything unexpected as empty rather than failing the whole read.
|
||||
func decodeStringList(raw json.RawMessage) []string {
|
||||
@@ -82,9 +85,10 @@ func (rec userRecord) toModel() models.User {
|
||||
Role: orDefault(rec.Role, "user"),
|
||||
Created: rec.Created,
|
||||
|
||||
Organization: rec.Organization,
|
||||
CarOrder: rec.carOrder(),
|
||||
ChargerTabOrder: rec.chargerTabOrder(),
|
||||
Organization: rec.Organization,
|
||||
CarOrder: rec.carOrder(),
|
||||
ChargerTabOrder: rec.chargerTabOrder(),
|
||||
ChargerCardOrder: rec.chargerCardOrder(),
|
||||
}
|
||||
if t := parsePBDate(rec.DeletionRequestedAt); !t.IsZero() {
|
||||
u.DeletionRequestedAt = &t
|
||||
@@ -133,16 +137,17 @@ func (s *Server) handleGetMe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
type updateMeRequest struct {
|
||||
Name *string `json:"name"`
|
||||
Bio *string `json:"bio"`
|
||||
Theme *string `json:"theme"`
|
||||
Locale *string `json:"locale"`
|
||||
DateFormat *string `json:"dateFormat"`
|
||||
Currency *string `json:"currency"`
|
||||
FontSize *string `json:"fontSize"`
|
||||
DragLocked *bool `json:"dragLocked"`
|
||||
CarOrder *[]string `json:"carOrder"`
|
||||
ChargerTabOrder *[]string `json:"chargerTabOrder"`
|
||||
Name *string `json:"name"`
|
||||
Bio *string `json:"bio"`
|
||||
Theme *string `json:"theme"`
|
||||
Locale *string `json:"locale"`
|
||||
DateFormat *string `json:"dateFormat"`
|
||||
Currency *string `json:"currency"`
|
||||
FontSize *string `json:"fontSize"`
|
||||
DragLocked *bool `json:"dragLocked"`
|
||||
CarOrder *[]string `json:"carOrder"`
|
||||
ChargerTabOrder *[]string `json:"chargerTabOrder"`
|
||||
ChargerCardOrder *[]string `json:"chargerCardOrder"`
|
||||
}
|
||||
|
||||
// maxCarOrder bounds the stored arrangement. listCars fetches at most 200 owned
|
||||
@@ -168,6 +173,10 @@ func normalizeChargerTabOrder(in []string) ([]string, error) {
|
||||
return normalizeOrder("chargerTabOrder", in, maxChargerTabOrder)
|
||||
}
|
||||
|
||||
func normalizeChargerCardOrder(in []string) ([]string, error) {
|
||||
return normalizeOrder("chargerCardOrder", in, maxChargerTabOrder)
|
||||
}
|
||||
|
||||
func normalizeOrder(field string, in []string, max int) ([]string, error) {
|
||||
if len(in) > max {
|
||||
return nil, fmt.Errorf("%s is too long (max %d)", field, max)
|
||||
@@ -284,6 +293,14 @@ func (s *Server) handleUpdateMe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
payload["charger_tab_order"] = keys
|
||||
}
|
||||
if in.ChargerCardOrder != nil {
|
||||
keys, err := normalizeChargerCardOrder(*in.ChargerCardOrder)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
payload["charger_card_order"] = keys
|
||||
}
|
||||
|
||||
var rec userRecord
|
||||
if err := s.pb.Update(r.Context(), s.usersCollection(), claims.ID, payload, &rec); err != nil {
|
||||
|
||||
@@ -255,6 +255,8 @@ var collectionsSchema = map[string][]fieldDef{
|
||||
// The charging page's tab order. Per user like the garage order, and for
|
||||
// the same reason: it is this person's arrangement of their own page.
|
||||
fJSON("charger_tab_order", 2000),
|
||||
// The charging page's card order, stored the same way as its tab order.
|
||||
fJSON("charger_card_order", 2000),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -495,8 +495,9 @@ type User struct {
|
||||
// CarOrder is the garage arrangement: car ids in the order this user dragged
|
||||
// them into. The car list is already returned in this order, so a client only
|
||||
// needs it to send an updated arrangement back.
|
||||
CarOrder []string `json:"carOrder"`
|
||||
ChargerTabOrder []string `json:"chargerTabOrder"`
|
||||
CarOrder []string `json:"carOrder"`
|
||||
ChargerTabOrder []string `json:"chargerTabOrder"`
|
||||
ChargerCardOrder []string `json:"chargerCardOrder"`
|
||||
|
||||
// Non-empty while an account-deletion request is pending its cooldown.
|
||||
DeletionRequestedAt *time.Time `json:"deletionRequestedAt,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user