account page: can now reorder entries under shops sections by drag and drop

This commit is contained in:
2026-01-26 13:51:40 -07:00
parent a2d8993e0e
commit a50c0ef18d
14 changed files with 682 additions and 88 deletions
+273
View File
@@ -0,0 +1,273 @@
package accounts
import (
"context"
"errors"
"fmt"
"ruben/inventory2/internal/consts"
"slices"
"strings"
"github.com/jackc/pgx/v5"
)
// TODO: simpify, if possible (single query ideal)
func (db *Store) SetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int64, platform Platform, orderIndex int) error {
tx, err := db.db.Begin(ctx)
if err != nil {
return fmt.Errorf("failed to start transaction: %w", err)
}
defer tx.Rollback(ctx)
// look up specified indexes
rows, err := tx.Query(
ctx,
`
SELECT
platform,
order_index
FROM
accounts_page_platform_order_indexes
WHERE
account_id = @account_id
ORDER BY
order_index ASC
`,
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return fmt.Errorf("failed to perform query to look up existing indexes: %w", err)
}
indexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Platform Platform
Order_index int
}])
if err != nil {
return fmt.Errorf("failed to scan rows for query to look up existing indexes: %w", err)
}
// compute the implied indexes,
// and construct the full sequence of platforms
indexPerPlatform := make(map[Platform]int, len(allPlatforms))
platformPerIndex := make(map[int]Platform, len(allPlatforms))
for _, v := range indexes {
indexPerPlatform[v.Platform] = v.Order_index
platformPerIndex[v.Order_index] = v.Platform
}
prevIndex := -1
for _, p := range allPlatforms {
if _, indexSet := indexPerPlatform[p]; !indexSet {
index := prevIndex
for indexUsed := true; indexUsed; _, indexUsed = platformPerIndex[index] {
index += 1
}
prevIndex = index
indexPerPlatform[p] = index
platformPerIndex[index] = p
}
}
prevOrderIndex := indexPerPlatform[platform]
if indexPerPlatform[platform] == orderIndex {
return nil
}
if increased := orderIndex > prevOrderIndex; increased {
// decrement in indexes between the previous index and new index
for index := prevOrderIndex + 1; index <= orderIndex; index += 1 {
p := platformPerIndex[index]
indexPerPlatform[p] = index - 1
platformPerIndex[index-1] = p
}
indexPerPlatform[platform] = orderIndex
platformPerIndex[orderIndex] = platform
} else {
// increment in indexes between the previous index and new index
for index := prevOrderIndex - 1; index >= orderIndex; index -= 1 {
p := platformPerIndex[index]
indexPerPlatform[p] = index + 1
platformPerIndex[index+1] = p
}
indexPerPlatform[platform] = orderIndex
platformPerIndex[orderIndex] = platform
}
// delete all indexes for the acct in the db,
// then insert all updated indexes
valuesLines := make([]string, len(allPlatforms))
args := pgx.NamedArgs{
"account_id": acctID,
}
for i, p := range allPlatforms {
valuesLines[i] = fmt.Sprintf("(@account_id, @platform_%d, @order_index_%d::smallint)", i, i)
args[fmt.Sprintf("platform_%d", i)] = p
args[fmt.Sprintf("order_index_%d", i)] = indexPerPlatform[p]
}
_, err = tx.Exec(
ctx,
fmt.Sprintf(`
WITH deleted_indexes AS (
DELETE FROM
accounts_page_platform_order_indexes
WHERE
account_id = @account_id
RETURNING
account_id
), new_indexes(account_id, platform, order_index) AS (
SELECT DISTINCT
x.account_id, x.platform, x.order_index
FROM
(VALUES %s) AS x(account_id, platform, order_index)
LEFT JOIN
deleted_indexes
ON
x.account_id = deleted_indexes.account_id
)
INSERT INTO
accounts_page_platform_order_indexes (
account_id,
platform,
order_index
)
SELECT
account_id,
platform::Platform,
order_index
FROM
new_indexes
`, strings.Join(valuesLines, ", ")),
args,
)
if err != nil {
return fmt.Errorf("failed to perform query to delete old indexes and insert new indexes: %w", err)
}
if tx.Commit(ctx); err != nil {
return fmt.Errorf("failed to commit txn: %w", err)
}
return nil
}
func (db *Store) GetOrderOfPlatformsOnAccountPage(ctx context.Context, acctID int64) ([]Platform, error) {
// get specified order indexes per platform
rows, err := db.db.Query(
ctx,
`
SELECT
platform,
order_index
FROM
accounts_page_platform_order_indexes
WHERE
account_id = @account_id
`,
pgx.NamedArgs{
"account_id": acctID,
},
)
if err != nil {
return nil, fmt.Errorf("failed to perform query: %w", err)
}
type Row struct {
Platform Platform
Order_index int
}
platformsWithIndexes, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[struct {
Platform Platform
Order_index int
}])
if err != nil {
return nil, fmt.Errorf("failed to scan rows: %w", err)
}
// hash the specified platforms by index
platformsByIndex := make(map[int]Platform, len(platformsWithIndexes))
for _, v := range platformsWithIndexes {
platformsByIndex[v.Order_index] = v.Platform
}
// form a sorted list of platforms without indexes specified, sorted alphabetically
allPlatformsByName := make(map[Platform]struct{}, len(allPlatforms))
for _, p := range allPlatforms {
allPlatformsByName[p] = struct{}{}
}
for _, v := range platformsWithIndexes {
delete(allPlatformsByName, v.Platform)
}
platformsWithoutAnIndexSpecified := make([]Platform, 0, len(allPlatformsByName))
for p := range allPlatformsByName {
platformsWithoutAnIndexSpecified = append(platformsWithoutAnIndexSpecified, p)
}
slices.SortFunc(platformsWithoutAnIndexSpecified, func(a, b Platform) int {
al := strings.ToLower(string(a))
bl := strings.ToLower(string(b))
if al < bl {
return -1
}
if bl < al {
return 1
}
return 0
})
// construct the list of the ordered platforms
res := make([]Platform, len(allPlatforms))
nextIndex := 0
for i := range res {
if p, ok := platformsByIndex[i]; ok {
res[i] = p
} else {
res[i] = platformsWithoutAnIndexSpecified[nextIndex]
nextIndex += 1
}
}
return res, nil
}
func (db *Store) GetOrderOfPlatformOnAccountPage(ctx context.Context, acctID int64, platform Platform) (int, error) {
rows, err := db.db.Query(
ctx,
`
SELECT
order_index
FROM
accounts_page_platform_order_indexes
WHERE
account_id = @account_id
AND platform = @platform
`,
pgx.NamedArgs{
"account_id": acctID,
"platform": platform,
},
)
if err != nil {
return 0, fmt.Errorf("failed to perform query: %w", err)
}
i, err := pgx.CollectExactlyOneRow(rows, pgx.RowTo[int])
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return 0, consts.ErrNotFound
}
return 0, fmt.Errorf("failed to scan rows: %w", err)
}
return i, nil
}
+48 -16
View File
@@ -10,41 +10,41 @@ type (
)
const (
Etsy Platform = "Etsy"
Tiktok Platform = "Tiktok"
Wix Platform = "Wix"
Ebay Platform = "ebay"
WalmartMarketplace Platform = "walmart_marketplace"
Amazon Platform = "amazon"
BigCartel Platform = "big_cartel"
Ebay Platform = "ebay"
Ecwid Platform = "ecwid"
Zoho Platform = "zoho"
Etsy Platform = "Etsy"
Shopify Platform = "shopify"
SquareOnline Platform = "square_online"
Squarespace Platform = "squarespace"
Tiktok Platform = "Tiktok"
WalmartMarketplace Platform = "walmart_marketplace"
Wix Platform = "Wix"
WooCommerce Platform = "woo_commerce"
Shopify Platform = "shopify"
Zoho Platform = "zoho"
)
var (
allValues = []Platform{
Etsy,
Tiktok,
Wix,
Ebay,
WalmartMarketplace,
allPlatforms = []Platform{
Amazon,
BigCartel,
Ebay,
Ecwid,
Zoho,
Etsy,
Shopify,
SquareOnline,
Squarespace,
Tiktok,
WalmartMarketplace,
Wix,
WooCommerce,
Shopify,
Zoho,
}
)
func NewPlatform(s string) (Platform, error) {
for _, v := range allValues {
for _, v := range allPlatforms {
if strings.ToLower(s) == strings.ToLower(string(v)) {
return v, nil
}
@@ -52,6 +52,38 @@ func NewPlatform(s string) (Platform, error) {
return "", fmt.Errorf("unrecognized constant: %q", s)
}
func (p Platform) PrettyPrint() string {
switch p {
case Etsy:
return "Etsy"
case Tiktok:
return "Tiktok"
case Wix:
return "Wix"
case Ebay:
return "Ebay"
case WalmartMarketplace:
return "Walmart Marketplace"
case Amazon:
return "Amazon"
case BigCartel:
return "Big Cartel"
case Ecwid:
return "Ecwid"
case Zoho:
return "Zoho"
case SquareOnline:
return "Square Online"
case Squarespace:
return "Squarespace"
case WooCommerce:
return "Woo Commerce"
case Shopify:
return "Shopify"
}
return ""
}
// sql.Scanner implementation
func (p *Platform) Scan(src any) error {
var s string
@@ -51,6 +51,18 @@ func (v_ctx *StoreWithContext) GetAccountPointerByUserID(userID string) (*Accoun
return v_ctx.Store.GetAccountPointerByUserID(v_ctx.ctx, userID)
}
func (v_ctx *StoreWithContext) SetOrderOfPlatformOnAccountPage(acctID int64, platform Platform, orderIndex int) error {
return v_ctx.Store.SetOrderOfPlatformOnAccountPage(v_ctx.ctx, acctID, platform, orderIndex)
}
func (v_ctx *StoreWithContext) GetOrderOfPlatformsOnAccountPage(acctID int64) ([]Platform, error) {
return v_ctx.Store.GetOrderOfPlatformsOnAccountPage(v_ctx.ctx, acctID)
}
func (v_ctx *StoreWithContext) GetOrderOfPlatformOnAccountPage(acctID int64, platform Platform) (int, error) {
return v_ctx.Store.GetOrderOfPlatformOnAccountPage(v_ctx.ctx, acctID, platform)
}
func (v_ctx *StoreWithContext) CreateSyncGroupListingDraft(acctID int64) (int, error) {
return v_ctx.Store.CreateSyncGroupListingDraft(v_ctx.ctx, acctID)
}