From 374cb9383c09bb44b5bcffcc7031aba4c02502f6 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:01:14 +0100 Subject: [PATCH 01/17] migration --- .../migrations/.snapshot-medusa-product.json | 1571 +++++++++++++++++ .../src/migrations/Migration20241115084742.ts | 11 + .../src/models/product-image-product.ts | 33 + .../product/src/models/product-image.ts | 10 +- .../modules/product/src/models/product.ts | 14 +- 5 files changed, 1631 insertions(+), 8 deletions(-) create mode 100644 packages/modules/product/src/migrations/.snapshot-medusa-product.json create mode 100644 packages/modules/product/src/migrations/Migration20241115084742.ts create mode 100644 packages/modules/product/src/models/product-image-product.ts diff --git a/packages/modules/product/src/migrations/.snapshot-medusa-product.json b/packages/modules/product/src/migrations/.snapshot-medusa-product.json new file mode 100644 index 0000000000000..11caa8c0bc5ae --- /dev/null +++ b/packages/modules/product/src/migrations/.snapshot-medusa-product.json @@ -0,0 +1,1571 @@ +{ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "name": { + "name": "name", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "description": { + "name": "description", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "''", + "mappedType": "text" + }, + "handle": { + "name": "handle", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "mpath": { + "name": "mpath", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "is_internal": { + "name": "is_internal", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "rank": { + "name": "rank", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "0", + "mappedType": "integer" + }, + "parent_category_id": { + "name": "parent_category_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + } + }, + "name": "product_category", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_category_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_category_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_category_parent_category_id_foreign": { + "constraintName": "product_category_parent_category_id_foreign", + "columnNames": [ + "parent_category_id" + ], + "localTableName": "public.product_category", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_category", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "handle": { + "name": "handle", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "product_collection", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_collection_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_collection_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "url": { + "name": "url", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "image", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_image_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "image_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "value": { + "name": "value", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "product_tag", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_tag_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_tag_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "value": { + "name": "value", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "json", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "product_type", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_type_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_type_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "handle": { + "name": "handle", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "description": { + "name": "description", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "is_giftcard": { + "name": "is_giftcard", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "status": { + "name": "status", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "draft", + "proposed", + "published", + "rejected" + ], + "mappedType": "enum" + }, + "thumbnail": { + "name": "thumbnail", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "weight": { + "name": "weight", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "length": { + "name": "length", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "height": { + "name": "height", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "width": { + "name": "width", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "origin_country": { + "name": "origin_country", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "hs_code": { + "name": "hs_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "mid_code": { + "name": "mid_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "material": { + "name": "material", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "collection_id": { + "name": "collection_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "type_id": { + "name": "type_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "discountable": { + "name": "discountable", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "true", + "mappedType": "boolean" + }, + "external_id": { + "name": "external_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + } + }, + "name": "product", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_collection_id_foreign": { + "constraintName": "product_collection_id_foreign", + "columnNames": [ + "collection_id" + ], + "localTableName": "public.product", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_collection", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "product_type_id_foreign": { + "constraintName": "product_type_id_foreign", + "columnNames": [ + "type_id" + ], + "localTableName": "public.product", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_type", + "deleteRule": "set null", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "product_id": { + "name": "product_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "product_option", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_option_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_option_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_option_product_id_foreign": { + "constraintName": "product_option_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "value": { + "name": "value", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "option_id": { + "name": "option_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "product_option_value", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_option_value_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_option_value_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_option_value_option_id_foreign": { + "constraintName": "product_option_value_option_id_foreign", + "columnNames": [ + "option_id" + ], + "localTableName": "public.product_option_value", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_option", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "product_id": { + "name": "product_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "image_id": { + "name": "image_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "rank": { + "name": "rank", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "0", + "mappedType": "integer" + } + }, + "name": "product_images", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "product_id" + ], + "composite": false, + "keyName": "IDX_product_image_product_product_id", + "primary": false, + "unique": false + }, + { + "columnNames": [ + "image_id" + ], + "composite": false, + "keyName": "IDX_product_image_product_image_id", + "primary": false, + "unique": false + }, + { + "keyName": "product_images_pkey", + "columnNames": [ + "product_id", + "image_id" + ], + "composite": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_images_product_id_foreign": { + "constraintName": "product_images_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_images", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product", + "deleteRule": "cascade" + }, + "product_images_image_id_foreign": { + "constraintName": "product_images_image_id_foreign", + "columnNames": [ + "image_id" + ], + "localTableName": "public.product_images", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.image", + "deleteRule": "cascade" + } + } + }, + { + "columns": { + "product_id": { + "name": "product_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "product_tag_id": { + "name": "product_tag_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "product_tags", + "schema": "public", + "indexes": [ + { + "keyName": "product_tags_pkey", + "columnNames": [ + "product_id", + "product_tag_id" + ], + "composite": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_tags_product_id_foreign": { + "constraintName": "product_tags_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_tags", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "product_tags_product_tag_id_foreign": { + "constraintName": "product_tags_product_tag_id_foreign", + "columnNames": [ + "product_tag_id" + ], + "localTableName": "public.product_tags", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_tag", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "product_id": { + "name": "product_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "product_category_id": { + "name": "product_category_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "product_category_product", + "schema": "public", + "indexes": [ + { + "keyName": "product_category_product_pkey", + "columnNames": [ + "product_id", + "product_category_id" + ], + "composite": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_category_product_product_id_foreign": { + "constraintName": "product_category_product_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_category_product", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "product_category_product_product_category_id_foreign": { + "constraintName": "product_category_product_product_category_id_foreign", + "columnNames": [ + "product_category_id" + ], + "localTableName": "public.product_category_product", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_category", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "id": { + "name": "id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "title": { + "name": "title", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "sku": { + "name": "sku", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "barcode": { + "name": "barcode", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "ean": { + "name": "ean", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "upc": { + "name": "upc", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "allow_backorder": { + "name": "allow_backorder", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "manage_inventory": { + "name": "manage_inventory", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "true", + "mappedType": "boolean" + }, + "hs_code": { + "name": "hs_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "origin_country": { + "name": "origin_country", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "mid_code": { + "name": "mid_code", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "material": { + "name": "material", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "weight": { + "name": "weight", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "length": { + "name": "length", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "height": { + "name": "height", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "width": { + "name": "width", + "type": "numeric", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "decimal" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" + }, + "variant_rank": { + "name": "variant_rank", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "default": "0", + "mappedType": "integer" + }, + "product_id": { + "name": "product_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "text" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "product_variant", + "schema": "public", + "indexes": [ + { + "columnNames": [ + "deleted_at" + ], + "composite": false, + "keyName": "IDX_product_variant_deleted_at", + "primary": false, + "unique": false + }, + { + "keyName": "product_variant_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_variant_product_id_foreign": { + "constraintName": "product_variant_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_variant", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + }, + { + "columns": { + "variant_id": { + "name": "variant_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + }, + "option_value_id": { + "name": "option_value_id", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "text" + } + }, + "name": "product_variant_option", + "schema": "public", + "indexes": [ + { + "keyName": "product_variant_option_pkey", + "columnNames": [ + "variant_id", + "option_value_id" + ], + "composite": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "product_variant_option_variant_id_foreign": { + "constraintName": "product_variant_option_variant_id_foreign", + "columnNames": [ + "variant_id" + ], + "localTableName": "public.product_variant_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_variant", + "deleteRule": "cascade", + "updateRule": "cascade" + }, + "product_variant_option_option_value_id_foreign": { + "constraintName": "product_variant_option_option_value_id_foreign", + "columnNames": [ + "option_value_id" + ], + "localTableName": "public.product_variant_option", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_option_value", + "deleteRule": "cascade", + "updateRule": "cascade" + } + } + } + ] +} diff --git a/packages/modules/product/src/migrations/Migration20241115084742.ts b/packages/modules/product/src/migrations/Migration20241115084742.ts new file mode 100644 index 0000000000000..8da7d8bf5885d --- /dev/null +++ b/packages/modules/product/src/migrations/Migration20241115084742.ts @@ -0,0 +1,11 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241115084742 extends Migration { + async up(): Promise { + this.addSql('ALTER TABLE "product_images" ADD COLUMN "rank" integer DEFAULT 0 NOT NULL;') + } + + async down(): Promise { + this.addSql('ALTER TABLE "product_images" DROP COLUMN "rank";') + } +} diff --git a/packages/modules/product/src/models/product-image-product.ts b/packages/modules/product/src/models/product-image-product.ts new file mode 100644 index 0000000000000..666c6a743ae5e --- /dev/null +++ b/packages/modules/product/src/models/product-image-product.ts @@ -0,0 +1,33 @@ +import { Cascade, Entity, ManyToOne, PrimaryKey, Property, Rel } from "@mikro-orm/core" +import Product from "./product" +import ProductImage from "./product-image" + +@Entity({ tableName: "product_images" }) +export class ProductImageProduct { + @PrimaryKey({ columnType: "text" }) + product_id: string + + @PrimaryKey({ columnType: "text" }) + image_id: string + + @ManyToOne({ + primary: true, + entity: () => Product, + fieldName: "product_id", + index: "IDX_product_image_product_product_id", + cascade: [Cascade.REMOVE], + }) + product: Rel + + @ManyToOne({ + primary: true, + entity: () => ProductImage, + fieldName: "image_id", + index: "IDX_product_image_product_image_id", + cascade: [Cascade.REMOVE], + }) + image: Rel + + @Property({ columnType: "integer", default: 0 }) + rank: number +} diff --git a/packages/modules/product/src/models/product-image.ts b/packages/modules/product/src/models/product-image.ts index 5c0baf2445b85..886e95d84b035 100644 --- a/packages/modules/product/src/models/product-image.ts +++ b/packages/modules/product/src/models/product-image.ts @@ -8,6 +8,7 @@ import { OnInit, PrimaryKey, Property, + Rel, } from "@mikro-orm/core" import { @@ -16,6 +17,7 @@ import { generateEntityId, } from "@medusajs/framework/utils" import Product from "./product" +import { ProductImageProduct } from "./product-image-product" const imageUrlIndexName = "IDX_product_image_url" const imageUrlIndexStatement = createPsqlIndexStatementHelper({ @@ -58,8 +60,12 @@ class ProductImage { @Property({ columnType: "timestamptz", nullable: true }) deleted_at?: Date - @ManyToMany(() => Product, (product) => product.images) - products = new Collection(this) + @ManyToMany({ + entity: () => Product, + pivotEntity: () => ProductImageProduct, + mappedBy: "images" + }) + products = new Collection>(this) @OnInit() onInit() { diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index c2f89a39751af..75b98526b2cc9 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -11,6 +11,7 @@ import { OnInit, PrimaryKey, Property, + Rel, } from "@mikro-orm/core" import { @@ -24,6 +25,7 @@ import { import ProductCategory from "./product-category" import ProductCollection from "./product-collection" import ProductImage from "./product-image" +import { ProductImageProduct } from "./product-image-product" import ProductOption from "./product-option" import ProductTag from "./product-tag" import ProductType from "./product-type" @@ -166,13 +168,13 @@ class Product { }) tags = new Collection(this) - @ManyToMany(() => ProductImage, "products", { - owner: true, - pivotTable: "product_images", - joinColumn: "product_id", - inverseJoinColumn: "image_id", + @ManyToMany({ + entity: () => ProductImage, + pivotEntity: () => ProductImageProduct, + fixedOrder: true, + fixedOrderColumn: "rank", }) - images = new Collection(this) + images = new Collection>(this) @ManyToMany(() => ProductCategory, "products", { owner: true, From 4c2267388ce5d92178f0074660e986f9a3c195eb Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:31:25 +0100 Subject: [PATCH 02/17] fix snapshot --- .../migrations/.snapshot-medusa-product.json | 1571 ----------------- .../migrations/.snapshot-medusa-products.json | 10 + .../src/migrations/Migration20241115084742.ts | 4 +- 3 files changed, 12 insertions(+), 1573 deletions(-) delete mode 100644 packages/modules/product/src/migrations/.snapshot-medusa-product.json diff --git a/packages/modules/product/src/migrations/.snapshot-medusa-product.json b/packages/modules/product/src/migrations/.snapshot-medusa-product.json deleted file mode 100644 index 11caa8c0bc5ae..0000000000000 --- a/packages/modules/product/src/migrations/.snapshot-medusa-product.json +++ /dev/null @@ -1,1571 +0,0 @@ -{ - "namespaces": [ - "public" - ], - "name": "public", - "tables": [ - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "name": { - "name": "name", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "description": { - "name": "description", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "''", - "mappedType": "text" - }, - "handle": { - "name": "handle", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "mpath": { - "name": "mpath", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "false", - "mappedType": "boolean" - }, - "is_internal": { - "name": "is_internal", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "false", - "mappedType": "boolean" - }, - "rank": { - "name": "rank", - "type": "integer", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "0", - "mappedType": "integer" - }, - "parent_category_id": { - "name": "parent_category_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - } - }, - "name": "product_category", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_category_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_category_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_category_parent_category_id_foreign": { - "constraintName": "product_category_parent_category_id_foreign", - "columnNames": [ - "parent_category_id" - ], - "localTableName": "public.product_category", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_category", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "title": { - "name": "title", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "handle": { - "name": "handle", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "product_collection", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_collection_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_collection_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "url": { - "name": "url", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "image", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_image_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "image_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "value": { - "name": "value", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "product_tag", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_tag_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_tag_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "value": { - "name": "value", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "metadata": { - "name": "metadata", - "type": "json", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "product_type", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_type_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_type_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {} - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "title": { - "name": "title", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "handle": { - "name": "handle", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "subtitle": { - "name": "subtitle", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "description": { - "name": "description", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "is_giftcard": { - "name": "is_giftcard", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "false", - "mappedType": "boolean" - }, - "status": { - "name": "status", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "draft", - "proposed", - "published", - "rejected" - ], - "mappedType": "enum" - }, - "thumbnail": { - "name": "thumbnail", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "weight": { - "name": "weight", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "length": { - "name": "length", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "height": { - "name": "height", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "width": { - "name": "width", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "origin_country": { - "name": "origin_country", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "hs_code": { - "name": "hs_code", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "mid_code": { - "name": "mid_code", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "material": { - "name": "material", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "collection_id": { - "name": "collection_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "type_id": { - "name": "type_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "discountable": { - "name": "discountable", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "true", - "mappedType": "boolean" - }, - "external_id": { - "name": "external_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - } - }, - "name": "product", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_collection_id_foreign": { - "constraintName": "product_collection_id_foreign", - "columnNames": [ - "collection_id" - ], - "localTableName": "public.product", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_collection", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "product_type_id_foreign": { - "constraintName": "product_type_id_foreign", - "columnNames": [ - "type_id" - ], - "localTableName": "public.product", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_type", - "deleteRule": "set null", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "title": { - "name": "title", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "product_id": { - "name": "product_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "product_option", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_option_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_option_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_option_product_id_foreign": { - "constraintName": "product_option_product_id_foreign", - "columnNames": [ - "product_id" - ], - "localTableName": "public.product_option", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "value": { - "name": "value", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "option_id": { - "name": "option_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "product_option_value", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_option_value_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_option_value_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_option_value_option_id_foreign": { - "constraintName": "product_option_value_option_id_foreign", - "columnNames": [ - "option_id" - ], - "localTableName": "public.product_option_value", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_option", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "product_id": { - "name": "product_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "image_id": { - "name": "image_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "rank": { - "name": "rank", - "type": "integer", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "0", - "mappedType": "integer" - } - }, - "name": "product_images", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "product_id" - ], - "composite": false, - "keyName": "IDX_product_image_product_product_id", - "primary": false, - "unique": false - }, - { - "columnNames": [ - "image_id" - ], - "composite": false, - "keyName": "IDX_product_image_product_image_id", - "primary": false, - "unique": false - }, - { - "keyName": "product_images_pkey", - "columnNames": [ - "product_id", - "image_id" - ], - "composite": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_images_product_id_foreign": { - "constraintName": "product_images_product_id_foreign", - "columnNames": [ - "product_id" - ], - "localTableName": "public.product_images", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product", - "deleteRule": "cascade" - }, - "product_images_image_id_foreign": { - "constraintName": "product_images_image_id_foreign", - "columnNames": [ - "image_id" - ], - "localTableName": "public.product_images", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.image", - "deleteRule": "cascade" - } - } - }, - { - "columns": { - "product_id": { - "name": "product_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "product_tag_id": { - "name": "product_tag_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "product_tags", - "schema": "public", - "indexes": [ - { - "keyName": "product_tags_pkey", - "columnNames": [ - "product_id", - "product_tag_id" - ], - "composite": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_tags_product_id_foreign": { - "constraintName": "product_tags_product_id_foreign", - "columnNames": [ - "product_id" - ], - "localTableName": "public.product_tags", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product", - "deleteRule": "cascade", - "updateRule": "cascade" - }, - "product_tags_product_tag_id_foreign": { - "constraintName": "product_tags_product_tag_id_foreign", - "columnNames": [ - "product_tag_id" - ], - "localTableName": "public.product_tags", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_tag", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "product_id": { - "name": "product_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "product_category_id": { - "name": "product_category_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "product_category_product", - "schema": "public", - "indexes": [ - { - "keyName": "product_category_product_pkey", - "columnNames": [ - "product_id", - "product_category_id" - ], - "composite": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_category_product_product_id_foreign": { - "constraintName": "product_category_product_product_id_foreign", - "columnNames": [ - "product_id" - ], - "localTableName": "public.product_category_product", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product", - "deleteRule": "cascade", - "updateRule": "cascade" - }, - "product_category_product_product_category_id_foreign": { - "constraintName": "product_category_product_product_category_id_foreign", - "columnNames": [ - "product_category_id" - ], - "localTableName": "public.product_category_product", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_category", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "id": { - "name": "id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "title": { - "name": "title", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "sku": { - "name": "sku", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "barcode": { - "name": "barcode", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "ean": { - "name": "ean", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "upc": { - "name": "upc", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "allow_backorder": { - "name": "allow_backorder", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "false", - "mappedType": "boolean" - }, - "manage_inventory": { - "name": "manage_inventory", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "true", - "mappedType": "boolean" - }, - "hs_code": { - "name": "hs_code", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "origin_country": { - "name": "origin_country", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "mid_code": { - "name": "mid_code", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "material": { - "name": "material", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "weight": { - "name": "weight", - "type": "numeric", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "decimal" - }, - "length": { - "name": "length", - "type": "numeric", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "decimal" - }, - "height": { - "name": "height", - "type": "numeric", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "decimal" - }, - "width": { - "name": "width", - "type": "numeric", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "decimal" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "json" - }, - "variant_rank": { - "name": "variant_rank", - "type": "integer", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "default": "0", - "mappedType": "integer" - }, - "product_id": { - "name": "product_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "product_variant", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "deleted_at" - ], - "composite": false, - "keyName": "IDX_product_variant_deleted_at", - "primary": false, - "unique": false - }, - { - "keyName": "product_variant_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_variant_product_id_foreign": { - "constraintName": "product_variant_product_id_foreign", - "columnNames": [ - "product_id" - ], - "localTableName": "public.product_variant", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - }, - { - "columns": { - "variant_id": { - "name": "variant_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - }, - "option_value_id": { - "name": "option_value_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "text" - } - }, - "name": "product_variant_option", - "schema": "public", - "indexes": [ - { - "keyName": "product_variant_option_pkey", - "columnNames": [ - "variant_id", - "option_value_id" - ], - "composite": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_variant_option_variant_id_foreign": { - "constraintName": "product_variant_option_variant_id_foreign", - "columnNames": [ - "variant_id" - ], - "localTableName": "public.product_variant_option", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_variant", - "deleteRule": "cascade", - "updateRule": "cascade" - }, - "product_variant_option_option_value_id_foreign": { - "constraintName": "product_variant_option_option_value_id_foreign", - "columnNames": [ - "option_value_id" - ], - "localTableName": "public.product_variant_option", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.product_option_value", - "deleteRule": "cascade", - "updateRule": "cascade" - } - } - } - ] -} diff --git a/packages/modules/product/src/migrations/.snapshot-medusa-products.json b/packages/modules/product/src/migrations/.snapshot-medusa-products.json index a561010919d8f..adae3ef41be3c 100644 --- a/packages/modules/product/src/migrations/.snapshot-medusa-products.json +++ b/packages/modules/product/src/migrations/.snapshot-medusa-products.json @@ -1038,6 +1038,16 @@ "primary": false, "nullable": false, "mappedType": "text" + }, + "rank": { + "name": "rank", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "0", + "mappedType": "integer" } }, "name": "product_images", diff --git a/packages/modules/product/src/migrations/Migration20241115084742.ts b/packages/modules/product/src/migrations/Migration20241115084742.ts index 8da7d8bf5885d..1add0e96f21f1 100644 --- a/packages/modules/product/src/migrations/Migration20241115084742.ts +++ b/packages/modules/product/src/migrations/Migration20241115084742.ts @@ -2,10 +2,10 @@ import { Migration } from '@mikro-orm/migrations'; export class Migration20241115084742 extends Migration { async up(): Promise { - this.addSql('ALTER TABLE "product_images" ADD COLUMN "rank" integer DEFAULT 0 NOT NULL;') + this.addSql('alter table "product_images" add column "rank" integer default 0 not null;') } async down(): Promise { - this.addSql('ALTER TABLE "product_images" DROP COLUMN "rank";') + this.addSql('alter table "product_images" drop column "rank";') } } From 630c15bd8b2ae9cf57dea108c1eca635ad72054e Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:51:52 +0100 Subject: [PATCH 03/17] primarykey --- packages/modules/product/src/models/product-image-product.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/modules/product/src/models/product-image-product.ts b/packages/modules/product/src/models/product-image-product.ts index 666c6a743ae5e..a24c584955946 100644 --- a/packages/modules/product/src/models/product-image-product.ts +++ b/packages/modules/product/src/models/product-image-product.ts @@ -1,4 +1,4 @@ -import { Cascade, Entity, ManyToOne, PrimaryKey, Property, Rel } from "@mikro-orm/core" +import { Cascade, Entity, ManyToOne, PrimaryKey, PrimaryKeyProp, Property, Rel } from "@mikro-orm/core" import Product from "./product" import ProductImage from "./product-image" @@ -30,4 +30,6 @@ export class ProductImageProduct { @Property({ columnType: "integer", default: 0 }) rank: number + + [PrimaryKeyProp]?: ["product", "image"] } From d50fca49624260fcdbb2e3e4d7f85d3e6151f949 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 15 Nov 2024 14:24:29 +0100 Subject: [PATCH 04/17] init work on dnd --- .../media-grid-view/media-grid-view.tsx | 107 +++++++++++++++--- 1 file changed, 89 insertions(+), 18 deletions(-) diff --git a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx index 122636cecbbc2..00c7770b5349a 100644 --- a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx +++ b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx @@ -1,5 +1,24 @@ +import { + closestCenter, + DndContext, + DragEndEvent, + DragStartEvent, + KeyboardSensor, + PointerSensor, + UniqueIdentifier, + useSensor, + useSensors, +} from "@dnd-kit/core" +import { + arrayMove, + rectSortingStrategy, + SortableContext, + sortableKeyboardCoordinates, + useSortable, +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" import { CheckMini, Spinner, ThumbnailBadge } from "@medusajs/icons" -import { Tooltip, clx } from "@medusajs/ui" +import { clx, Tooltip } from "@medusajs/ui" import { AnimatePresence, motion } from "framer-motion" import { useCallback, useState } from "react" import { useTranslation } from "react-i18next" @@ -22,21 +41,57 @@ export const MediaGrid = ({ selection, onCheckedChange, }: MediaGridProps) => { + const [activeId, setActiveId] = useState(null) + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ) + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(event.active.id) + } + + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(null) + const { active, over } = event + + if (active.id !== over.id) { + setItems((items) => { + const oldIndex = items.indexOf(active.id) + const newIndex = items.indexOf(over.id) + + return arrayMove(items, oldIndex, newIndex) + }) + } + } + return ( -
-
- {media.map((m) => { - return ( - - ) - })} + +
+
+ + {media.map((m) => { + return ( + + ) + })} + +
-
+ ) } @@ -59,11 +114,27 @@ const MediaGridItem = ({ onCheckedChange(!checked) }, [checked, onCheckedChange]) + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: media.field_id }) + + const style = { + transform: CSS.Transform.toString(transform), + transition, + } + return ( - +
) } From 0dc62ebdf9f4bd264c7c8f8dbb873a150e9dd09a Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:31:37 +0100 Subject: [PATCH 05/17] progress --- .../media-grid-view/media-grid-view.tsx | 2 + ...cts.json => .snapshot-medusa-product.json} | 297 ++++++++++++------ .../src/migrations/Migration20241115084742.ts | 11 - .../src/migrations/Migration20241115140922.ts | 31 ++ .../src/models/product-image-product.ts | 36 +-- .../product/src/models/product-image.ts | 4 +- .../modules/product/src/models/product.ts | 7 +- 7 files changed, 254 insertions(+), 134 deletions(-) rename packages/modules/product/src/migrations/{.snapshot-medusa-products.json => .snapshot-medusa-product.json} (90%) delete mode 100644 packages/modules/product/src/migrations/Migration20241115084742.ts create mode 100644 packages/modules/product/src/migrations/Migration20241115140922.ts diff --git a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx index 00c7770b5349a..f139c54bef396 100644 --- a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx +++ b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx @@ -32,12 +32,14 @@ interface MediaView { interface MediaGridProps { media: MediaView[] + setMedia: (media: MediaView[]) => void selection: Record onCheckedChange: (id: string) => (value: boolean) => void } export const MediaGrid = ({ media, + setMedia, selection, onCheckedChange, }: MediaGridProps) => { diff --git a/packages/modules/product/src/migrations/.snapshot-medusa-products.json b/packages/modules/product/src/migrations/.snapshot-medusa-product.json similarity index 90% rename from packages/modules/product/src/migrations/.snapshot-medusa-products.json rename to packages/modules/product/src/migrations/.snapshot-medusa-product.json index adae3ef41be3c..d5b60ab6b341c 100644 --- a/packages/modules/product/src/migrations/.snapshot-medusa-products.json +++ b/packages/modules/product/src/migrations/.snapshot-medusa-product.json @@ -1,5 +1,7 @@ { - "namespaces": ["public"], + "namespaces": [ + "public" + ], "name": "public", "tables": [ { @@ -120,13 +122,24 @@ "nullable": true, "length": 6, "mappedType": "datetime" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "json" } }, "name": "product_category", "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_category_deleted_at", "primary": false, @@ -134,7 +147,9 @@ }, { "keyName": "product_category_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -144,9 +159,13 @@ "foreignKeys": { "product_category_parent_category_id_foreign": { "constraintName": "product_category_parent_category_id_foreign", - "columnNames": ["parent_category_id"], + "columnNames": [ + "parent_category_id" + ], "localTableName": "public.product_category", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_category", "deleteRule": "cascade", "updateRule": "cascade" @@ -228,7 +247,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_collection_deleted_at", "primary": false, @@ -236,7 +257,9 @@ }, { "keyName": "product_collection_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -311,7 +334,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_image_deleted_at", "primary": false, @@ -319,7 +344,9 @@ }, { "keyName": "image_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -394,7 +421,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_tag_deleted_at", "primary": false, @@ -402,7 +431,9 @@ }, { "keyName": "product_tag_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -477,7 +508,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_type_deleted_at", "primary": false, @@ -485,7 +518,9 @@ }, { "keyName": "product_type_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -558,7 +593,12 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["draft", "proposed", "published", "rejected"], + "enumItems": [ + "draft", + "proposed", + "published", + "rejected" + ], "mappedType": "enum" }, "thumbnail": { @@ -725,7 +765,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_deleted_at", "primary": false, @@ -733,7 +775,9 @@ }, { "keyName": "product_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -743,18 +787,26 @@ "foreignKeys": { "product_collection_id_foreign": { "constraintName": "product_collection_id_foreign", - "columnNames": ["collection_id"], + "columnNames": [ + "collection_id" + ], "localTableName": "public.product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_collection", "deleteRule": "set null", "updateRule": "cascade" }, "product_type_id_foreign": { "constraintName": "product_type_id_foreign", - "columnNames": ["type_id"], + "columnNames": [ + "type_id" + ], "localTableName": "public.product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_type", "deleteRule": "set null", "updateRule": "cascade" @@ -836,7 +888,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_option_deleted_at", "primary": false, @@ -844,7 +898,9 @@ }, { "keyName": "product_option_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -854,9 +910,13 @@ "foreignKeys": { "product_option_product_id_foreign": { "constraintName": "product_option_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_option", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" @@ -938,7 +998,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_option_value_deleted_at", "primary": false, @@ -946,7 +1008,9 @@ }, { "keyName": "product_option_value_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -956,9 +1020,13 @@ "foreignKeys": { "product_option_value_option_id_foreign": { "constraintName": "product_option_value_option_id_foreign", - "columnNames": ["option_id"], + "columnNames": [ + "option_id" + ], "localTableName": "public.product_option_value", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_option", "deleteRule": "cascade", "updateRule": "cascade" @@ -973,25 +1041,37 @@ "unsigned": false, "autoincrement": false, "primary": false, - "nullable": false, + "nullable": true, "mappedType": "text" }, - "product_tag_id": { - "name": "product_tag_id", + "image_id": { + "name": "image_id", "type": "text", "unsigned": false, "autoincrement": false, "primary": false, - "nullable": false, + "nullable": true, "mappedType": "text" + }, + "rank": { + "name": "rank", + "type": "integer", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "integer" } }, - "name": "product_tags", + "name": "product_images", "schema": "public", "indexes": [ { - "keyName": "product_tags_pkey", - "columnNames": ["product_id", "product_tag_id"], + "keyName": "product_images_pkey", + "columnNames": [ + "product_id", + "image_id" + ], "composite": true, "primary": true, "unique": true @@ -999,23 +1079,29 @@ ], "checks": [], "foreignKeys": { - "product_tags_product_id_foreign": { - "constraintName": "product_tags_product_id_foreign", - "columnNames": ["product_id"], - "localTableName": "public.product_tags", - "referencedColumnNames": ["id"], + "product_images_product_id_foreign": { + "constraintName": "product_images_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_images", + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", - "deleteRule": "cascade", - "updateRule": "cascade" + "deleteRule": "cascade" }, - "product_tags_product_tag_id_foreign": { - "constraintName": "product_tags_product_tag_id_foreign", - "columnNames": ["product_tag_id"], - "localTableName": "public.product_tags", - "referencedColumnNames": ["id"], - "referencedTableName": "public.product_tag", - "deleteRule": "cascade", - "updateRule": "cascade" + "product_images_image_id_foreign": { + "constraintName": "product_images_image_id_foreign", + "columnNames": [ + "image_id" + ], + "localTableName": "public.product_images", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.image", + "deleteRule": "cascade" } } }, @@ -1030,32 +1116,25 @@ "nullable": false, "mappedType": "text" }, - "image_id": { - "name": "image_id", + "product_tag_id": { + "name": "product_tag_id", "type": "text", "unsigned": false, "autoincrement": false, "primary": false, "nullable": false, "mappedType": "text" - }, - "rank": { - "name": "rank", - "type": "integer", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "0", - "mappedType": "integer" } }, - "name": "product_images", + "name": "product_tags", "schema": "public", "indexes": [ { - "keyName": "product_images_pkey", - "columnNames": ["product_id", "image_id"], + "keyName": "product_tags_pkey", + "columnNames": [ + "product_id", + "product_tag_id" + ], "composite": true, "primary": true, "unique": true @@ -1063,21 +1142,29 @@ ], "checks": [], "foreignKeys": { - "product_images_product_id_foreign": { - "constraintName": "product_images_product_id_foreign", - "columnNames": ["product_id"], - "localTableName": "public.product_images", - "referencedColumnNames": ["id"], + "product_tags_product_id_foreign": { + "constraintName": "product_tags_product_id_foreign", + "columnNames": [ + "product_id" + ], + "localTableName": "public.product_tags", + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" }, - "product_images_image_id_foreign": { - "constraintName": "product_images_image_id_foreign", - "columnNames": ["image_id"], - "localTableName": "public.product_images", - "referencedColumnNames": ["id"], - "referencedTableName": "public.image", + "product_tags_product_tag_id_foreign": { + "constraintName": "product_tags_product_tag_id_foreign", + "columnNames": [ + "product_tag_id" + ], + "localTableName": "public.product_tags", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.product_tag", "deleteRule": "cascade", "updateRule": "cascade" } @@ -1109,7 +1196,10 @@ "indexes": [ { "keyName": "product_category_product_pkey", - "columnNames": ["product_id", "product_category_id"], + "columnNames": [ + "product_id", + "product_category_id" + ], "composite": true, "primary": true, "unique": true @@ -1119,18 +1209,26 @@ "foreignKeys": { "product_category_product_product_id_foreign": { "constraintName": "product_category_product_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_category_product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" }, "product_category_product_product_category_id_foreign": { "constraintName": "product_category_product_product_category_id_foreign", - "columnNames": ["product_category_id"], + "columnNames": [ + "product_category_id" + ], "localTableName": "public.product_category_product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_category", "deleteRule": "cascade", "updateRule": "cascade" @@ -1350,7 +1448,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_variant_deleted_at", "primary": false, @@ -1358,7 +1458,9 @@ }, { "keyName": "product_variant_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1368,9 +1470,13 @@ "foreignKeys": { "product_variant_product_id_foreign": { "constraintName": "product_variant_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_variant", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" @@ -1403,7 +1509,10 @@ "indexes": [ { "keyName": "product_variant_option_pkey", - "columnNames": ["variant_id", "option_value_id"], + "columnNames": [ + "variant_id", + "option_value_id" + ], "composite": true, "primary": true, "unique": true @@ -1413,18 +1522,26 @@ "foreignKeys": { "product_variant_option_variant_id_foreign": { "constraintName": "product_variant_option_variant_id_foreign", - "columnNames": ["variant_id"], + "columnNames": [ + "variant_id" + ], "localTableName": "public.product_variant_option", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_variant", "deleteRule": "cascade", "updateRule": "cascade" }, "product_variant_option_option_value_id_foreign": { "constraintName": "product_variant_option_option_value_id_foreign", - "columnNames": ["option_value_id"], + "columnNames": [ + "option_value_id" + ], "localTableName": "public.product_variant_option", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_option_value", "deleteRule": "cascade", "updateRule": "cascade" diff --git a/packages/modules/product/src/migrations/Migration20241115084742.ts b/packages/modules/product/src/migrations/Migration20241115084742.ts deleted file mode 100644 index 1add0e96f21f1..0000000000000 --- a/packages/modules/product/src/migrations/Migration20241115084742.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20241115084742 extends Migration { - async up(): Promise { - this.addSql('alter table "product_images" add column "rank" integer default 0 not null;') - } - - async down(): Promise { - this.addSql('alter table "product_images" drop column "rank";') - } -} diff --git a/packages/modules/product/src/migrations/Migration20241115140922.ts b/packages/modules/product/src/migrations/Migration20241115140922.ts new file mode 100644 index 0000000000000..21218f8696f12 --- /dev/null +++ b/packages/modules/product/src/migrations/Migration20241115140922.ts @@ -0,0 +1,31 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241115140922 extends Migration { + + async up(): Promise { + this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_product_id_foreign";'); + this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_image_id_foreign";'); + + this.addSql('alter table if exists "product_images" add column if not exists "rank" integer not null;'); + this.addSql('alter table if exists "product_images" alter column "product_id" type text using ("product_id"::text);'); + this.addSql('alter table if exists "product_images" alter column "product_id" drop not null;'); + this.addSql('alter table if exists "product_images" alter column "image_id" type text using ("image_id"::text);'); + this.addSql('alter table if exists "product_images" alter column "image_id" drop not null;'); + this.addSql('alter table if exists "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on delete cascade;'); + this.addSql('alter table if exists "product_images" add constraint "product_images_image_id_foreign" foreign key ("image_id") references "image" ("id") on delete cascade;'); + } + + async down(): Promise { + this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_product_id_foreign";'); + this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_image_id_foreign";'); + + this.addSql('alter table if exists "product_images" alter column "product_id" type text using ("product_id"::text);'); + this.addSql('alter table if exists "product_images" alter column "product_id" set not null;'); + this.addSql('alter table if exists "product_images" alter column "image_id" type text using ("image_id"::text);'); + this.addSql('alter table if exists "product_images" alter column "image_id" set not null;'); + this.addSql('alter table if exists "product_images" drop column if exists "rank";'); + this.addSql('alter table if exists "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;'); + this.addSql('alter table if exists "product_images" add constraint "product_images_image_id_foreign" foreign key ("image_id") references "image" ("id") on update cascade on delete cascade;'); + } + +} diff --git a/packages/modules/product/src/models/product-image-product.ts b/packages/modules/product/src/models/product-image-product.ts index a24c584955946..143c3fd3e5bc9 100644 --- a/packages/modules/product/src/models/product-image-product.ts +++ b/packages/modules/product/src/models/product-image-product.ts @@ -1,35 +1,17 @@ -import { Cascade, Entity, ManyToOne, PrimaryKey, PrimaryKeyProp, Property, Rel } from "@mikro-orm/core" +import { Cascade, Entity, ManyToOne, PrimaryKeyType, Property, Rel } from "@mikro-orm/core" import Product from "./product" import ProductImage from "./product-image" @Entity({ tableName: "product_images" }) export class ProductImageProduct { - @PrimaryKey({ columnType: "text" }) - product_id: string + @ManyToOne(() => Product, { primary: true, cascade: [Cascade.REMOVE] }) + product!: Rel - @PrimaryKey({ columnType: "text" }) - image_id: string + @ManyToOne(() => ProductImage, { primary: true, cascade: [Cascade.REMOVE] }) + image!: Rel - @ManyToOne({ - primary: true, - entity: () => Product, - fieldName: "product_id", - index: "IDX_product_image_product_product_id", - cascade: [Cascade.REMOVE], - }) - product: Rel + @Property({ columnType: "integer", default: 0, nullable: false }) + rank!: number - @ManyToOne({ - primary: true, - entity: () => ProductImage, - fieldName: "image_id", - index: "IDX_product_image_product_image_id", - cascade: [Cascade.REMOVE], - }) - image: Rel - - @Property({ columnType: "integer", default: 0 }) - rank: number - - [PrimaryKeyProp]?: ["product", "image"] -} + [PrimaryKeyType]?: [string, string] +} \ No newline at end of file diff --git a/packages/modules/product/src/models/product-image.ts b/packages/modules/product/src/models/product-image.ts index 886e95d84b035..118f3275f50df 100644 --- a/packages/modules/product/src/models/product-image.ts +++ b/packages/modules/product/src/models/product-image.ts @@ -63,7 +63,7 @@ class ProductImage { @ManyToMany({ entity: () => Product, pivotEntity: () => ProductImageProduct, - mappedBy: "images" + mappedBy: "images", }) products = new Collection>(this) @@ -78,4 +78,4 @@ class ProductImage { } } -export default ProductImage +export default ProductImage \ No newline at end of file diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index 75b98526b2cc9..54820dc5742c3 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -168,11 +168,10 @@ class Product { }) tags = new Collection(this) - @ManyToMany({ + @ManyToMany(() => ProductImage, "products", { + owner: true, entity: () => ProductImage, pivotEntity: () => ProductImageProduct, - fixedOrder: true, - fixedOrderColumn: "rank", }) images = new Collection>(this) @@ -223,4 +222,4 @@ class Product { } } -export default Product +export default Product \ No newline at end of file From c714d53be72b793c3934928f877e2290f8d96399 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:54:01 +0100 Subject: [PATCH 06/17] dnd --- .../media-grid-view/media-grid-view.tsx | 144 +++++++++++------- .../edit-product-media-form.tsx | 11 ++ .../product-module-service/products.spec.ts | 2 +- .../migrations/.snapshot-medusa-product.json | 61 -------- .../src/migrations/Migration20241115140922.ts | 31 ---- packages/modules/product/src/models/index.ts | 8 +- .../src/models/product-image-product.ts | 17 --- .../product/src/models/product-image.ts | 10 +- .../modules/product/src/models/product.ts | 9 +- .../src/services/product-module-service.ts | 41 ++++- 10 files changed, 151 insertions(+), 183 deletions(-) delete mode 100644 packages/modules/product/src/migrations/Migration20241115140922.ts delete mode 100644 packages/modules/product/src/models/product-image-product.ts diff --git a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx index f139c54bef396..4abeddd15dede 100644 --- a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx +++ b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx @@ -1,8 +1,11 @@ import { closestCenter, + defaultDropAnimationSideEffects, DndContext, DragEndEvent, + DragOverlay, DragStartEvent, + DropAnimation, KeyboardSensor, PointerSensor, UniqueIdentifier, @@ -17,9 +20,8 @@ import { useSortable, } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" -import { CheckMini, Spinner, ThumbnailBadge } from "@medusajs/icons" -import { clx, Tooltip } from "@medusajs/ui" -import { AnimatePresence, motion } from "framer-motion" +import { ThumbnailBadge } from "@medusajs/icons" +import { Checkbox, clx, Tooltip } from "@medusajs/ui" import { useCallback, useState } from "react" import { useTranslation } from "react-i18next" @@ -32,14 +34,24 @@ interface MediaView { interface MediaGridProps { media: MediaView[] - setMedia: (media: MediaView[]) => void + onSwapPositions: (callback: (items: MediaView[]) => MediaView[]) => void selection: Record onCheckedChange: (id: string) => (value: boolean) => void } +const dropAnimationConfig: DropAnimation = { + sideEffects: defaultDropAnimationSideEffects({ + styles: { + active: { + opacity: "0.4", + }, + }, + }), +} + export const MediaGrid = ({ media, - setMedia, + onSwapPositions, selection, onCheckedChange, }: MediaGridProps) => { @@ -60,10 +72,10 @@ export const MediaGrid = ({ setActiveId(null) const { active, over } = event - if (active.id !== over.id) { - setItems((items) => { - const oldIndex = items.indexOf(active.id) - const newIndex = items.indexOf(over.id) + if (active.id !== over?.id) { + onSwapPositions((items) => { + const oldIndex = items.findIndex((item) => item.field_id === active.id) + const newIndex = items.findIndex((item) => item.field_id === over?.id) return arrayMove(items, oldIndex, newIndex) }) @@ -79,7 +91,10 @@ export const MediaGrid = ({ >
- + m.field_id)} + strategy={rectSortingStrategy} + > {media.map((m) => { return ( + + {activeId ? ( + m.field_id === activeId)!} + checked={ + !!selection[media.find((m) => m.field_id === activeId)!.id!] + } + /> + ) : null} +
@@ -108,13 +133,15 @@ const MediaGridItem = ({ checked, onCheckedChange, }: MediaGridItemProps) => { - const [isLoading, setIsLoading] = useState(true) - const { t } = useTranslation() - const handleToggle = useCallback(() => { - onCheckedChange(!checked) - }, [checked, onCheckedChange]) + const handleToggle = useCallback( + (value: boolean) => { + console.log("value", value) + onCheckedChange(value) + }, + [onCheckedChange] + ) const { attributes, @@ -135,8 +162,6 @@ const MediaGridItem = ({ className="shadow-elevation-card-rest hover:shadow-elevation-card-hover focus-visible:shadow-borders-focus bg-ui-bg-subtle-hover group relative aspect-square h-auto max-w-full overflow-hidden rounded-lg outline-none" style={style} ref={setNodeRef} - {...attributes} - {...listeners} > {media.isThumbnail && (
@@ -146,50 +171,57 @@ const MediaGridItem = ({
)}
+
-
-
- {checked && ( -
- -
- )} -
+ { + e.stopPropagation() + }} + checked={checked} + onCheckedChange={handleToggle} + /> +
+ +
+ ) +} + +export const MediaGridItemOverlay = ({ + media, + checked, +}: { + media: MediaView + checked: boolean +}) => { + return ( +
+ {media.isThumbnail && ( +
+
+ )} +
+
- - {isLoading && ( - - - - )} - setIsLoading(false)} alt="" className="size-full object-cover object-center" /> diff --git a/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx b/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx index 224147a171819..7bcf7c21217d6 100644 --- a/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx +++ b/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx @@ -139,6 +139,16 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { setSelection({}) } + const onSwapPositions = ( + callback: (items: typeof fields) => typeof fields + ) => { + const newFields = callback(fields) + + newFields.forEach((field, index) => { + update(index, field) + }) + } + const selectionCount = Object.keys(selection).length return ( @@ -162,6 +172,7 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { media={fields} onCheckedChange={handleCheckedChange} selection={selection} + onSwapPositions={onSwapPositions} />
diff --git a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts index fbfecddd4c799..cc4e778474af7 100644 --- a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts +++ b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts @@ -18,11 +18,11 @@ import { ProductType, } from "@models" -import { UpdateProductInput } from "@types" import { MockEventBusService, moduleIntegrationTestRunner, } from "@medusajs/test-utils" +import { UpdateProductInput } from "@types" import { buildProductAndRelationsData, createCollections, diff --git a/packages/modules/product/src/migrations/.snapshot-medusa-product.json b/packages/modules/product/src/migrations/.snapshot-medusa-product.json index 981232a98345a..37ce6ea6eefc2 100644 --- a/packages/modules/product/src/migrations/.snapshot-medusa-product.json +++ b/packages/modules/product/src/migrations/.snapshot-medusa-product.json @@ -974,67 +974,6 @@ } } }, - { - "columns": { - "product_id": { - "name": "product_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "image_id": { - "name": "image_id", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "text" - }, - "rank": { - "name": "rank", - "type": "integer", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "integer" - } - }, - "name": "product_images", - "schema": "public", - "indexes": [ - { - "keyName": "product_images_pkey", - "columnNames": ["product_id", "image_id"], - "composite": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "product_images_product_id_foreign": { - "constraintName": "product_images_product_id_foreign", - "columnNames": ["product_id"], - "localTableName": "public.product_images", - "referencedColumnNames": ["id"], - "referencedTableName": "public.product", - "deleteRule": "cascade" - }, - "product_images_image_id_foreign": { - "constraintName": "product_images_image_id_foreign", - "columnNames": ["image_id"], - "localTableName": "public.product_images", - "referencedColumnNames": ["id"], - "referencedTableName": "public.image", - "deleteRule": "cascade" - } - } - }, { "columns": { "product_id": { diff --git a/packages/modules/product/src/migrations/Migration20241115140922.ts b/packages/modules/product/src/migrations/Migration20241115140922.ts deleted file mode 100644 index 21218f8696f12..0000000000000 --- a/packages/modules/product/src/migrations/Migration20241115140922.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Migration } from '@mikro-orm/migrations'; - -export class Migration20241115140922 extends Migration { - - async up(): Promise { - this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_product_id_foreign";'); - this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_image_id_foreign";'); - - this.addSql('alter table if exists "product_images" add column if not exists "rank" integer not null;'); - this.addSql('alter table if exists "product_images" alter column "product_id" type text using ("product_id"::text);'); - this.addSql('alter table if exists "product_images" alter column "product_id" drop not null;'); - this.addSql('alter table if exists "product_images" alter column "image_id" type text using ("image_id"::text);'); - this.addSql('alter table if exists "product_images" alter column "image_id" drop not null;'); - this.addSql('alter table if exists "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on delete cascade;'); - this.addSql('alter table if exists "product_images" add constraint "product_images_image_id_foreign" foreign key ("image_id") references "image" ("id") on delete cascade;'); - } - - async down(): Promise { - this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_product_id_foreign";'); - this.addSql('alter table if exists "product_images" drop constraint if exists "product_images_image_id_foreign";'); - - this.addSql('alter table if exists "product_images" alter column "product_id" type text using ("product_id"::text);'); - this.addSql('alter table if exists "product_images" alter column "product_id" set not null;'); - this.addSql('alter table if exists "product_images" alter column "image_id" type text using ("image_id"::text);'); - this.addSql('alter table if exists "product_images" alter column "image_id" set not null;'); - this.addSql('alter table if exists "product_images" drop column if exists "rank";'); - this.addSql('alter table if exists "product_images" add constraint "product_images_product_id_foreign" foreign key ("product_id") references "product" ("id") on update cascade on delete cascade;'); - this.addSql('alter table if exists "product_images" add constraint "product_images_image_id_foreign" foreign key ("image_id") references "image" ("id") on update cascade on delete cascade;'); - } - -} diff --git a/packages/modules/product/src/models/index.ts b/packages/modules/product/src/models/index.ts index f3f6a304fabe7..1e38f5cc3ff88 100644 --- a/packages/modules/product/src/models/index.ts +++ b/packages/modules/product/src/models/index.ts @@ -1,9 +1,11 @@ export { default as Product } from "./product" export { default as ProductCategory } from "./product-category" export { default as ProductCollection } from "./product-collection" +export { default as Image } from "./product-image" +export { default as ProductImageProduct } from "./product-image-product" +export { default as ProductOption } from "./product-option" +export { default as ProductOptionValue } from "./product-option-value" export { default as ProductTag } from "./product-tag" export { default as ProductType } from "./product-type" export { default as ProductVariant } from "./product-variant" -export { default as ProductOption } from "./product-option" -export { default as ProductOptionValue } from "./product-option-value" -export { default as Image } from "./product-image" + diff --git a/packages/modules/product/src/models/product-image-product.ts b/packages/modules/product/src/models/product-image-product.ts deleted file mode 100644 index 143c3fd3e5bc9..0000000000000 --- a/packages/modules/product/src/models/product-image-product.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Cascade, Entity, ManyToOne, PrimaryKeyType, Property, Rel } from "@mikro-orm/core" -import Product from "./product" -import ProductImage from "./product-image" - -@Entity({ tableName: "product_images" }) -export class ProductImageProduct { - @ManyToOne(() => Product, { primary: true, cascade: [Cascade.REMOVE] }) - product!: Rel - - @ManyToOne(() => ProductImage, { primary: true, cascade: [Cascade.REMOVE] }) - image!: Rel - - @Property({ columnType: "integer", default: 0, nullable: false }) - rank!: number - - [PrimaryKeyType]?: [string, string] -} \ No newline at end of file diff --git a/packages/modules/product/src/models/product-image.ts b/packages/modules/product/src/models/product-image.ts index 118f3275f50df..35cfe3c611f2e 100644 --- a/packages/modules/product/src/models/product-image.ts +++ b/packages/modules/product/src/models/product-image.ts @@ -8,7 +8,6 @@ import { OnInit, PrimaryKey, Property, - Rel, } from "@mikro-orm/core" import { @@ -17,7 +16,6 @@ import { generateEntityId, } from "@medusajs/framework/utils" import Product from "./product" -import { ProductImageProduct } from "./product-image-product" const imageUrlIndexName = "IDX_product_image_url" const imageUrlIndexStatement = createPsqlIndexStatementHelper({ @@ -60,12 +58,8 @@ class ProductImage { @Property({ columnType: "timestamptz", nullable: true }) deleted_at?: Date - @ManyToMany({ - entity: () => Product, - pivotEntity: () => ProductImageProduct, - mappedBy: "images", - }) - products = new Collection>(this) + @ManyToMany(() => Product, (product) => product.images) + products = new Collection(this) @OnInit() onInit() { diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index 54820dc5742c3..2674df97ab967 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -11,7 +11,6 @@ import { OnInit, PrimaryKey, Property, - Rel, } from "@mikro-orm/core" import { @@ -25,7 +24,6 @@ import { import ProductCategory from "./product-category" import ProductCollection from "./product-collection" import ProductImage from "./product-image" -import { ProductImageProduct } from "./product-image-product" import ProductOption from "./product-option" import ProductTag from "./product-tag" import ProductType from "./product-type" @@ -170,10 +168,11 @@ class Product { @ManyToMany(() => ProductImage, "products", { owner: true, - entity: () => ProductImage, - pivotEntity: () => ProductImageProduct, + pivotTable: "product_images", + joinColumn: "product_id", + inverseJoinColumn: "image_id", }) - images = new Collection>(this) + images = new Collection(this) @ManyToMany(() => ProductCategory, "products", { owner: true, diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 628887674ffd7..dfb416b591547 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1,6 +1,7 @@ import { Context, DAL, + FindConfig, IEventBusModuleService, InternalModuleDeclaration, ModuleJoinerConfig, @@ -8,10 +9,11 @@ import { ProductTypes, } from "@medusajs/framework/types" import { - Image as ProductImage, Product, ProductCategory, ProductCollection, + Image as ProductImage, + ProductImageProduct, ProductOption, ProductOptionValue, ProductTag, @@ -58,6 +60,7 @@ type InjectedDependencies = { productCategoryService: ProductCategoryService productCollectionService: ModulesSdkTypes.IMedusaInternalService productImageService: ModulesSdkTypes.IMedusaInternalService + productImageProductService: ModulesSdkTypes.IMedusaInternalService productTypeService: ModulesSdkTypes.IMedusaInternalService productOptionService: ModulesSdkTypes.IMedusaInternalService productOptionValueService: ModulesSdkTypes.IMedusaInternalService @@ -90,6 +93,9 @@ export default class ProductModuleService ProductVariant: { dto: ProductTypes.ProductVariantDTO } + ProductImageProduct: { + dto: ProductTypes.ProductImageProductDTO + } }>({ Product, ProductCategory, @@ -99,6 +105,7 @@ export default class ProductModuleService ProductTag, ProductType, ProductVariant, + ProductImageProduct, }) implements ProductTypes.IProductModuleService { @@ -109,6 +116,7 @@ export default class ProductModuleService protected readonly productTagService_: ModulesSdkTypes.IMedusaInternalService protected readonly productCollectionService_: ModulesSdkTypes.IMedusaInternalService protected readonly productImageService_: ModulesSdkTypes.IMedusaInternalService + protected readonly productImageProductService_: ModulesSdkTypes.IMedusaInternalService protected readonly productTypeService_: ModulesSdkTypes.IMedusaInternalService protected readonly productOptionService_: ModulesSdkTypes.IMedusaInternalService protected readonly productOptionValueService_: ModulesSdkTypes.IMedusaInternalService @@ -123,6 +131,7 @@ export default class ProductModuleService productCategoryService, productCollectionService, productImageService, + productImageProductService, productTypeService, productOptionService, productOptionValueService, @@ -141,6 +150,7 @@ export default class ProductModuleService this.productCategoryService_ = productCategoryService this.productCollectionService_ = productCollectionService this.productImageService_ = productImageService + this.productImageProductService_ = productImageProductService this.productTypeService_ = productTypeService this.productOptionService_ = productOptionService this.productOptionValueService_ = productOptionValueService @@ -151,6 +161,35 @@ export default class ProductModuleService return joinerConfig } + // @ts-expect-error + async retrieveProduct( + productId: string, + config?: FindConfig, + sharedContext?: Context + ): Promise { + const product = await this.productService_.retrieve( + productId, + config, + sharedContext + ) + + const productImageProducts = await this.productImageProductService_.list( + { product_id: productId }, + {}, + sharedContext + ) + + if (productImageProducts.length) { + throw new Error( + `product ${productId} has images with ranks ${productImageProducts + .map((p) => `${p.rank}`) + .join(", ")}` + ) + } + + return await this.baseRepository_.serialize(product) + } + // @ts-ignore createProductVariants( data: ProductTypes.CreateProductVariantDTO[], From 33e8ac25106c14fc7311f5139685a3710ea83958 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:56:04 +0100 Subject: [PATCH 07/17] undo changes --- packages/modules/product/src/models/index.ts | 1 - .../src/services/product-module-service.ts | 43 +------------------ 2 files changed, 2 insertions(+), 42 deletions(-) diff --git a/packages/modules/product/src/models/index.ts b/packages/modules/product/src/models/index.ts index 1e38f5cc3ff88..5774eaa5affa8 100644 --- a/packages/modules/product/src/models/index.ts +++ b/packages/modules/product/src/models/index.ts @@ -2,7 +2,6 @@ export { default as Product } from "./product" export { default as ProductCategory } from "./product-category" export { default as ProductCollection } from "./product-collection" export { default as Image } from "./product-image" -export { default as ProductImageProduct } from "./product-image-product" export { default as ProductOption } from "./product-option" export { default as ProductOptionValue } from "./product-option-value" export { default as ProductTag } from "./product-tag" diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index dfb416b591547..f2c45cde11896 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1,24 +1,22 @@ import { Context, DAL, - FindConfig, IEventBusModuleService, InternalModuleDeclaration, ModuleJoinerConfig, ModulesSdkTypes, - ProductTypes, + ProductTypes } from "@medusajs/framework/types" import { Product, ProductCategory, ProductCollection, Image as ProductImage, - ProductImageProduct, ProductOption, ProductOptionValue, ProductTag, ProductType, - ProductVariant, + ProductVariant } from "@models" import { ProductCategoryService } from "@services" @@ -60,7 +58,6 @@ type InjectedDependencies = { productCategoryService: ProductCategoryService productCollectionService: ModulesSdkTypes.IMedusaInternalService productImageService: ModulesSdkTypes.IMedusaInternalService - productImageProductService: ModulesSdkTypes.IMedusaInternalService productTypeService: ModulesSdkTypes.IMedusaInternalService productOptionService: ModulesSdkTypes.IMedusaInternalService productOptionValueService: ModulesSdkTypes.IMedusaInternalService @@ -93,9 +90,6 @@ export default class ProductModuleService ProductVariant: { dto: ProductTypes.ProductVariantDTO } - ProductImageProduct: { - dto: ProductTypes.ProductImageProductDTO - } }>({ Product, ProductCategory, @@ -105,7 +99,6 @@ export default class ProductModuleService ProductTag, ProductType, ProductVariant, - ProductImageProduct, }) implements ProductTypes.IProductModuleService { @@ -116,7 +109,6 @@ export default class ProductModuleService protected readonly productTagService_: ModulesSdkTypes.IMedusaInternalService protected readonly productCollectionService_: ModulesSdkTypes.IMedusaInternalService protected readonly productImageService_: ModulesSdkTypes.IMedusaInternalService - protected readonly productImageProductService_: ModulesSdkTypes.IMedusaInternalService protected readonly productTypeService_: ModulesSdkTypes.IMedusaInternalService protected readonly productOptionService_: ModulesSdkTypes.IMedusaInternalService protected readonly productOptionValueService_: ModulesSdkTypes.IMedusaInternalService @@ -131,7 +123,6 @@ export default class ProductModuleService productCategoryService, productCollectionService, productImageService, - productImageProductService, productTypeService, productOptionService, productOptionValueService, @@ -150,7 +141,6 @@ export default class ProductModuleService this.productCategoryService_ = productCategoryService this.productCollectionService_ = productCollectionService this.productImageService_ = productImageService - this.productImageProductService_ = productImageProductService this.productTypeService_ = productTypeService this.productOptionService_ = productOptionService this.productOptionValueService_ = productOptionValueService @@ -161,35 +151,6 @@ export default class ProductModuleService return joinerConfig } - // @ts-expect-error - async retrieveProduct( - productId: string, - config?: FindConfig, - sharedContext?: Context - ): Promise { - const product = await this.productService_.retrieve( - productId, - config, - sharedContext - ) - - const productImageProducts = await this.productImageProductService_.list( - { product_id: productId }, - {}, - sharedContext - ) - - if (productImageProducts.length) { - throw new Error( - `product ${productId} has images with ranks ${productImageProducts - .map((p) => `${p.rank}`) - .join(", ")}` - ) - } - - return await this.baseRepository_.serialize(product) - } - // @ts-ignore createProductVariants( data: ProductTypes.CreateProductVariantDTO[], From ee6fa0fb593829cf0b2904dc187c63a5915819fa Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:57:53 +0100 Subject: [PATCH 08/17] undo changes --- .../product/src/services/product-module-service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index f2c45cde11896..d4fe39eaa21ba 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -5,18 +5,18 @@ import { InternalModuleDeclaration, ModuleJoinerConfig, ModulesSdkTypes, - ProductTypes + ProductTypes, } from "@medusajs/framework/types" import { + Image as ProductImage, Product, ProductCategory, ProductCollection, - Image as ProductImage, ProductOption, ProductOptionValue, ProductTag, ProductType, - ProductVariant + ProductVariant, } from "@models" import { ProductCategoryService } from "@services" @@ -1888,4 +1888,4 @@ export default class ProductModuleService } } } -} +} \ No newline at end of file From 46a161a15fa150515427b868bdea097faa97e474 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:58:39 +0100 Subject: [PATCH 09/17] undo changes --- packages/modules/product/src/models/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/modules/product/src/models/index.ts b/packages/modules/product/src/models/index.ts index 5774eaa5affa8..a35e9b68a8e47 100644 --- a/packages/modules/product/src/models/index.ts +++ b/packages/modules/product/src/models/index.ts @@ -1,10 +1,9 @@ export { default as Product } from "./product" export { default as ProductCategory } from "./product-category" export { default as ProductCollection } from "./product-collection" -export { default as Image } from "./product-image" -export { default as ProductOption } from "./product-option" -export { default as ProductOptionValue } from "./product-option-value" export { default as ProductTag } from "./product-tag" export { default as ProductType } from "./product-type" export { default as ProductVariant } from "./product-variant" - +export { default as ProductOption } from "./product-option" +export { default as ProductOptionValue } from "./product-option-value" +export { default as Image } from "./product-image" \ No newline at end of file From 8974df316e5e09d07c3be0b2a83c9bb972d0fd86 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:59:55 +0100 Subject: [PATCH 10/17] undo changes --- .../__tests__/product-module-service/products.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts index cc4e778474af7..004e0bb46076b 100644 --- a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts +++ b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts @@ -18,11 +18,11 @@ import { ProductType, } from "@models" +import { UpdateProductInput } from "@types" import { MockEventBusService, moduleIntegrationTestRunner, } from "@medusajs/test-utils" -import { UpdateProductInput } from "@types" import { buildProductAndRelationsData, createCollections, @@ -1238,4 +1238,4 @@ moduleIntegrationTestRunner({ }) }) }, -}) +}) \ No newline at end of file From 7de679930997ebc5dbf5148b7185bff88b6e7271 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:17:44 +0100 Subject: [PATCH 11/17] fix firefox issue --- .../route-modal-form/route-modal-form.tsx | 4 +- .../src/i18n/translations/$schema.json | 14 +- .../dashboard/src/i18n/translations/de.json | 5 +- .../dashboard/src/i18n/translations/en.json | 5 +- .../dashboard/src/i18n/translations/pl.json | 7 +- .../dashboard/src/i18n/translations/tr.json | 5 +- .../components/media-grid-view/index.ts | 1 - .../media-grid-view/media-grid-view.tsx | 230 -------------- .../upload-media-form-item.tsx | 58 ++-- .../product-create-details-media-section.tsx | 242 +++++++++++--- .../product-create-form.tsx | 300 +++++++++--------- .../product-create/product-create.tsx | 11 +- .../routes/products/product-create/utils.ts | 15 +- .../edit-product-media-form.tsx | 254 +++++++++++++-- .../product-media-gallery.tsx | 27 +- .../products/product-media/product-media.tsx | 8 + 16 files changed, 681 insertions(+), 505 deletions(-) delete mode 100644 packages/admin/dashboard/src/routes/products/common/components/media-grid-view/index.ts delete mode 100644 packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx diff --git a/packages/admin/dashboard/src/components/modals/route-modal-form/route-modal-form.tsx b/packages/admin/dashboard/src/components/modals/route-modal-form/route-modal-form.tsx index 2135f6c696e60..69ececbcdb482 100644 --- a/packages/admin/dashboard/src/components/modals/route-modal-form/route-modal-form.tsx +++ b/packages/admin/dashboard/src/components/modals/route-modal-form/route-modal-form.tsx @@ -7,13 +7,13 @@ import { Form } from "../../common/form" type RouteModalFormProps = PropsWithChildren<{ form: UseFormReturn - blockSearch?: boolean + blockSearchParams?: boolean onClose?: (isSubmitSuccessful: boolean) => void }> export const RouteModalForm = ({ form, - blockSearch = false, + blockSearchParams: blockSearch = false, children, onClose, }: RouteModalFormProps) => { diff --git a/packages/admin/dashboard/src/i18n/translations/$schema.json b/packages/admin/dashboard/src/i18n/translations/$schema.json index 582afa68b38a1..aa91a999b2988 100644 --- a/packages/admin/dashboard/src/i18n/translations/$schema.json +++ b/packages/admin/dashboard/src/i18n/translations/$schema.json @@ -1578,6 +1578,12 @@ "create": { "type": "object", "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, "header": { "type": "string" }, @@ -1742,6 +1748,8 @@ } }, "required": [ + "title", + "description", "header", "tabs", "errors", @@ -1990,6 +1998,9 @@ "action" ], "additionalProperties": false + }, + "successToast": { + "type": "string" } }, "required": [ @@ -2008,7 +2019,8 @@ "galleryLabel", "downloadImageLabel", "deleteImageLabel", - "emptyState" + "emptyState", + "successToast" ], "additionalProperties": false }, diff --git a/packages/admin/dashboard/src/i18n/translations/de.json b/packages/admin/dashboard/src/i18n/translations/de.json index f433dfc43537d..09b6f9c8e78bf 100644 --- a/packages/admin/dashboard/src/i18n/translations/de.json +++ b/packages/admin/dashboard/src/i18n/translations/de.json @@ -386,6 +386,8 @@ "successToast": "Produkz {{title}} angepasst." }, "create": { + "title": "Produkt erstellen", + "description": "Erstellen Sie ein neues Produkt.", "header": "Allgemein", "tabs": { "details": "Details", @@ -490,7 +492,8 @@ "header": "Noch keine Medien", "description": "Fügen Sie dem Produkt Medien hinzu, um es in Ihrem Schaufenster zu präsentieren.", "action": "Medien hinzufügen" - } + }, + "successToast": "Medien wurden erfolgreich aktualisiert." }, "discountableHint": "Wenn diese Option deaktiviert ist, werden auf dieses Produkt keine Rabatte gewährt.", "noSalesChannels": "In keinem Vertriebskanal verfügbar", diff --git a/packages/admin/dashboard/src/i18n/translations/en.json b/packages/admin/dashboard/src/i18n/translations/en.json index ae8604bb43718..60af19619567d 100644 --- a/packages/admin/dashboard/src/i18n/translations/en.json +++ b/packages/admin/dashboard/src/i18n/translations/en.json @@ -386,6 +386,8 @@ "successToast": "Product {{title}} was successfully updated." }, "create": { + "title": "Create Product", + "description": "Create a new product.", "header": "General", "tabs": { "details": "Details", @@ -490,7 +492,8 @@ "header": "No media yet", "description": "Add media to the product to showcase it in your storefront.", "action": "Add media" - } + }, + "successToast": "Media was successfully updated." }, "discountableHint": "When unchecked, discounts will not be applied to this product.", "noSalesChannels": "Not available in any sales channels", diff --git a/packages/admin/dashboard/src/i18n/translations/pl.json b/packages/admin/dashboard/src/i18n/translations/pl.json index 3f807b3f58c42..69e4adcab258e 100644 --- a/packages/admin/dashboard/src/i18n/translations/pl.json +++ b/packages/admin/dashboard/src/i18n/translations/pl.json @@ -386,6 +386,8 @@ "successToast": "Produkt {{title}} został pomyślnie zaktualizowany." }, "create": { + "title": "Utwórz produkt", + "description": "Utwórz nowy produkt.", "header": "Ogólne", "tabs": { "details": "Szczegóły", @@ -490,7 +492,8 @@ "header": "Brak mediów", "description": "Dodaj media do produktu, aby zaprezentować go w swoim sklepie.", "action": "Dodaj media" - } + }, + "successToast": "Media zostały pomyślnie zaktualizowane." }, "discountableHint": "Jeśli odznaczone, rabaty nie będą stosowane do tego produktu.", "noSalesChannels": "Niedostępny w żadnych kanałach sprzedaży", @@ -2752,4 +2755,4 @@ "seconds_one": "Drugi", "seconds_other": "Towary drugiej jakości" } -} \ No newline at end of file +} diff --git a/packages/admin/dashboard/src/i18n/translations/tr.json b/packages/admin/dashboard/src/i18n/translations/tr.json index 9812581eb797a..d52e3dce14a0e 100644 --- a/packages/admin/dashboard/src/i18n/translations/tr.json +++ b/packages/admin/dashboard/src/i18n/translations/tr.json @@ -386,6 +386,8 @@ "successToast": "Ürün {{title}} başarıyla güncellendi." }, "create": { + "title": "Ürün Oluştur", + "description": "Yeni bir ürün oluşturun.", "header": "Genel", "tabs": { "details": "Detaylar", @@ -490,7 +492,8 @@ "header": "Henüz medya yok", "description": "Ürünü mağazanızda sergilemek için medya ekleyin.", "action": "Medya ekle" - } + }, + "successToast": "Medya başarıyla güncellendi." }, "discountableHint": "İşaretlenmediğinde, bu ürüne indirim uygulanmayacaktır.", "noSalesChannels": "Hiçbir satış kanalında mevcut değil", diff --git a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/index.ts b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/index.ts deleted file mode 100644 index 4b124fbbb8a58..0000000000000 --- a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./media-grid-view" diff --git a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx b/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx deleted file mode 100644 index 4abeddd15dede..0000000000000 --- a/packages/admin/dashboard/src/routes/products/common/components/media-grid-view/media-grid-view.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import { - closestCenter, - defaultDropAnimationSideEffects, - DndContext, - DragEndEvent, - DragOverlay, - DragStartEvent, - DropAnimation, - KeyboardSensor, - PointerSensor, - UniqueIdentifier, - useSensor, - useSensors, -} from "@dnd-kit/core" -import { - arrayMove, - rectSortingStrategy, - SortableContext, - sortableKeyboardCoordinates, - useSortable, -} from "@dnd-kit/sortable" -import { CSS } from "@dnd-kit/utilities" -import { ThumbnailBadge } from "@medusajs/icons" -import { Checkbox, clx, Tooltip } from "@medusajs/ui" -import { useCallback, useState } from "react" -import { useTranslation } from "react-i18next" - -interface MediaView { - id?: string - field_id: string - url: string - isThumbnail: boolean -} - -interface MediaGridProps { - media: MediaView[] - onSwapPositions: (callback: (items: MediaView[]) => MediaView[]) => void - selection: Record - onCheckedChange: (id: string) => (value: boolean) => void -} - -const dropAnimationConfig: DropAnimation = { - sideEffects: defaultDropAnimationSideEffects({ - styles: { - active: { - opacity: "0.4", - }, - }, - }), -} - -export const MediaGrid = ({ - media, - onSwapPositions, - selection, - onCheckedChange, -}: MediaGridProps) => { - const [activeId, setActiveId] = useState(null) - - const sensors = useSensors( - useSensor(PointerSensor), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }) - ) - - const handleDragStart = (event: DragStartEvent) => { - setActiveId(event.active.id) - } - - const handleDragEnd = (event: DragEndEvent) => { - setActiveId(null) - const { active, over } = event - - if (active.id !== over?.id) { - onSwapPositions((items) => { - const oldIndex = items.findIndex((item) => item.field_id === active.id) - const newIndex = items.findIndex((item) => item.field_id === over?.id) - - return arrayMove(items, oldIndex, newIndex) - }) - } - } - - return ( - -
-
- m.field_id)} - strategy={rectSortingStrategy} - > - {media.map((m) => { - return ( - - ) - })} - - - {activeId ? ( - m.field_id === activeId)!} - checked={ - !!selection[media.find((m) => m.field_id === activeId)!.id!] - } - /> - ) : null} - -
-
-
- ) -} - -interface MediaGridItemProps { - media: MediaView - checked: boolean - onCheckedChange: (value: boolean) => void -} - -const MediaGridItem = ({ - media, - checked, - onCheckedChange, -}: MediaGridItemProps) => { - const { t } = useTranslation() - - const handleToggle = useCallback( - (value: boolean) => { - console.log("value", value) - onCheckedChange(value) - }, - [onCheckedChange] - ) - - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ id: media.field_id }) - - const style = { - transform: CSS.Transform.toString(transform), - transition, - } - - return ( -
- {media.isThumbnail && ( -
- - - -
- )} -
-
- { - e.stopPropagation() - }} - checked={checked} - onCheckedChange={handleToggle} - /> -
- -
- ) -} - -export const MediaGridItemOverlay = ({ - media, - checked, -}: { - media: MediaView - checked: boolean -}) => { - return ( -
- {media.isThumbnail && ( -
- -
- )} -
- -
- -
- ) -} diff --git a/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx b/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx index 883ef812e2e58..2d23cb8f74471 100644 --- a/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx +++ b/packages/admin/dashboard/src/routes/products/common/components/upload-media-form-item/upload-media-form-item.tsx @@ -1,3 +1,4 @@ +import { useCallback } from "react" import { UseFormReturn } from "react-hook-form" import { useTranslation } from "react-i18next" import { z } from "zod" @@ -45,25 +46,40 @@ export const UploadMediaFormItem = ({ }) => { const { t } = useTranslation() - const hasInvalidFiles = (fileList: FileType[]) => { - const invalidFile = fileList.find( - (f) => !SUPPORTED_FORMATS.includes(f.file.type) - ) + const hasInvalidFiles = useCallback( + (fileList: FileType[]) => { + const invalidFile = fileList.find( + (f) => !SUPPORTED_FORMATS.includes(f.file.type) + ) - if (invalidFile) { - form.setError("media", { - type: "invalid_file", - message: t("products.media.invalidFileType", { - name: invalidFile.file.name, - types: SUPPORTED_FORMATS_FILE_EXTENSIONS.join(", "), - }), - }) + if (invalidFile) { + form.setError("media", { + type: "invalid_file", + message: t("products.media.invalidFileType", { + name: invalidFile.file.name, + types: SUPPORTED_FORMATS_FILE_EXTENSIONS.join(", "), + }), + }) - return true - } + return true + } + + return false + }, + [form, t] + ) - return false - } + const onUploaded = useCallback( + (files: FileType[]) => { + form.clearErrors("media") + if (hasInvalidFiles(files)) { + return + } + + files.forEach((f) => append({ ...f, isThumbnail: false })) + }, + [form, append, hasInvalidFiles] + ) return ( { - form.clearErrors("media") - if (hasInvalidFiles(files)) { - return - } - - // TODO: For now all files that get uploaded are not thumbnails, revisit this logic - files.forEach((f) => append({ ...f, isThumbnail: false })) - }} + onUploaded={onUploaded} /> diff --git a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx index 7272d893bdad2..d001328df3cf5 100644 --- a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx +++ b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx @@ -1,7 +1,33 @@ -import { StackPerspective, ThumbnailBadge, Trash, XMark } from "@medusajs/icons" +import { + defaultDropAnimationSideEffects, + DndContext, + DragEndEvent, + DragOverlay, + DragStartEvent, + DropAnimation, + KeyboardSensor, + PointerSensor, + UniqueIdentifier, + useSensor, + useSensors, +} from "@dnd-kit/core" +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + useSortable, +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" +import { + DotsSix, + StackPerspective, + ThumbnailBadge, + Trash, + XMark, +} from "@medusajs/icons" import { IconButton, Text } from "@medusajs/ui" -import { useEffect, useState } from "react" -import { UseFormReturn, useFieldArray } from "react-hook-form" +import { useState } from "react" +import { useFieldArray, UseFormReturn } from "react-hook-form" import { useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../../../components/common/action-menu" import { UploadMediaFormItem } from "../../../../../common/components/upload-media-form-item" @@ -11,6 +37,16 @@ type ProductCreateMediaSectionProps = { form: UseFormReturn } +const dropAnimationConfig: DropAnimation = { + sideEffects: defaultDropAnimationSideEffects({ + styles: { + active: { + opacity: "0.4", + }, + }, + }), +} + export const ProductCreateMediaSection = ({ form, }: ProductCreateMediaSectionProps) => { @@ -20,6 +56,38 @@ export const ProductCreateMediaSection = ({ keyName: "field_id", }) + const [activeId, setActiveId] = useState(null) + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ) + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(event.active.id) + } + + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(null) + const { active, over } = event + + if (active.id !== over?.id) { + const oldIndex = fields.findIndex((item) => item.field_id === active.id) + const newIndex = fields.findIndex((item) => item.field_id === over?.id) + + form.setValue("media", arrayMove(fields, oldIndex, newIndex), { + shouldDirty: true, + shouldTouch: true, + }) + } + } + + const handleDragCancel = () => { + setActiveId(null) + } + const getOnDelete = (index: number) => { return () => { remove(index) @@ -52,20 +120,36 @@ export const ProductCreateMediaSection = ({ return (
-
    - {fields.map((field, index) => { - const { onDelete, onMakeThumbnail } = getItemHandlers(index) - - return ( - + + {activeId ? ( + m.field_id === activeId)!} /> - ) - })} -
+ ) : null} + +
    + field.field_id)}> + {fields.map((field, index) => { + const { onDelete, onMakeThumbnail } = getItemHandlers(index) + + return ( + + ) + })} + +
+
) } @@ -87,25 +171,59 @@ type MediaItemProps = { const MediaItem = ({ field, onDelete, onMakeThumbnail }: MediaItemProps) => { const { t } = useTranslation() + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: field.field_id }) + + const style = { + opacity: isDragging ? 0.4 : undefined, + transform: CSS.Translate.toString(transform), + transition, + } + if (!field.file) { return null } return ( -
  • -
    -
    - -
    -
    - - {field.file.name} - -
    - {field.isThumbnail && } - - {formatFileSize(field.file.size)} +
  • +
    + + + +
    +
    + +
    +
    + + {field.file.name} +
    + {field.isThumbnail && } + + {formatFileSize(field.file.size)} + +
    @@ -145,28 +263,60 @@ const MediaItem = ({ field, onDelete, onMakeThumbnail }: MediaItemProps) => { ) } -const ThumbnailPreview = ({ file }: { file?: File | null }) => { - const [thumbnailUrl, setThumbnailUrl] = useState(null) - - useEffect(() => { - if (file) { - const objectUrl = URL.createObjectURL(file) - setThumbnailUrl(objectUrl) - - return () => URL.revokeObjectURL(objectUrl) - } - }, [file]) +const MediaGridItemOverlay = ({ field }: { field: MediaField }) => { + return ( +
  • +
    + + + +
    +
    + +
    +
    + + {field.file?.name} + +
    + {field.isThumbnail && } + + {formatFileSize(field.file?.size ?? 0)} + +
    +
    +
    +
    +
    + + {}} + > + + +
    +
  • + ) +} - if (!thumbnailUrl) { +const ThumbnailPreview = ({ url }: { url?: string | null }) => { + if (!url) { return null } return ( - + ) } diff --git a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx index 566ea2fa22673..f8bdcdc76c890 100644 --- a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx +++ b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-form/product-create-form.tsx @@ -14,7 +14,6 @@ import { } from "../../../../../extensions" import { useCreateProduct } from "../../../../../hooks/api/products" import { sdk } from "../../../../../lib/client" -import { isFetchError } from "../../../../../lib/is-fetch-error" import { PRODUCT_CREATE_FORM_DEFAULTS, ProductCreateSchema, @@ -80,13 +79,10 @@ export const ProductCreateForm = ({ return {} } - return regions.reduce( - (acc, reg) => { - acc[reg.id] = reg.currency_code - return acc - }, - {} as Record - ) + return regions.reduce((acc, reg) => { + acc[reg.id] = reg.currency_code + return acc + }, {} as Record) }, [regions]) /** @@ -140,32 +136,34 @@ export const ProductCreateForm = ({ uploadedMedia = (await Promise.all(fileReqs)).flat() } - - const { product } = await mutateAsync( - normalizeProductFormValues({ - ...payload, - media: uploadedMedia, - status: (isDraftSubmission ? "draft" : "published") as any, - regionsCurrencyMap, - }) - ) - - toast.success( - t("products.create.successToast", { - title: product.title, - }) - ) - - handleSuccess(`../${product.id}`) } catch (error) { - if (isFetchError(error) && error.status === 400) { + if (error instanceof Error) { toast.error(error.message) - } else { - toast.error(t("general.error"), { - description: error.message, - }) } } + + await mutateAsync( + normalizeProductFormValues({ + ...payload, + media: uploadedMedia, + status: (isDraftSubmission ? "draft" : "published") as any, + regionsCurrencyMap, + }), + { + onSuccess: (data) => { + toast.success( + t("products.create.successToast", { + title: data.product.title, + }) + ) + + handleSuccess(`../${data.product.id}`) + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) }) const onNext = async (currentTab: Tab) => { @@ -210,143 +208,141 @@ export const ProductCreateForm = ({ } setTabState({ ...currentState }) - }, [tab]) + }, [tab, tabState]) return ( - - - { - // We want to continue to the next tab on enter instead of saving as draft immediately - if (e.key === "Enter") { - e.preventDefault() - - if (e.metaKey || e.ctrlKey) { - if (tab !== Tab.VARIANTS) { - e.preventDefault() - e.stopPropagation() - onNext(tab) - - return - } - - handleSubmit() - } - } - }} - onSubmit={handleSubmit} - className="flex h-full flex-col" - > - { - const valid = await form.trigger() + + { + // We want to continue to the next tab on enter instead of saving as draft immediately + if (e.key === "Enter") { + e.preventDefault() + + if (e.metaKey || e.ctrlKey) { + if (tab !== Tab.VARIANTS) { + e.preventDefault() + e.stopPropagation() + onNext(tab) - if (!valid) { return } - setTab(tab as Tab) - }} - className="flex h-full flex-col overflow-hidden" - > - -
    - - - {t("products.create.tabs.details")} - - - {t("products.create.tabs.organize")} - + handleSubmit() + } + } + }} + onSubmit={handleSubmit} + className="flex h-full flex-col" + > + { + const valid = await form.trigger() + + if (!valid) { + return + } + + setTab(tab as Tab) + }} + className="flex h-full flex-col overflow-hidden" + > + +
    + + + {t("products.create.tabs.details")} + + + {t("products.create.tabs.organize")} + + + {t("products.create.tabs.variants")} + + {showInventoryTab && ( - {t("products.create.tabs.variants")} + {t("products.create.tabs.inventory")} - {showInventoryTab && ( - - {t("products.create.tabs.inventory")} - - )} - -
    -
    - - - - - - - + )} +
    +
    +
    + + + + + + + + + + + {showInventoryTab && ( - + - {showInventoryTab && ( - - - - )} - -
    - -
    - - - - - -
    -
    -
    -
    -
    + + + +
    + + + ) } diff --git a/packages/admin/dashboard/src/routes/products/product-create/product-create.tsx b/packages/admin/dashboard/src/routes/products/product-create/product-create.tsx index c9c872cfa9740..fd65e86e13480 100644 --- a/packages/admin/dashboard/src/routes/products/product-create/product-create.tsx +++ b/packages/admin/dashboard/src/routes/products/product-create/product-create.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next" import { RouteFocusModal } from "../../../components/modals" import { useRegions } from "../../../hooks/api" import { usePricePreferences } from "../../../hooks/api/price-preferences" @@ -6,13 +7,15 @@ import { useStore } from "../../../hooks/api/store" import { ProductCreateForm } from "./components/product-create-form/product-create-form" export const ProductCreate = () => { + const { t } = useTranslation() + const { store, isPending: isStorePending, isError: isStoreError, error: storeError, } = useStore({ - fields: "default_sales_channel", + fields: "+default_sales_channel", }) const { @@ -68,6 +71,12 @@ export const ProductCreate = () => { return ( + + {t("products.create.title")} + + + {t("products.create.description")} + {ready && ( } -) => { +): HttpTypes.AdminCreateProduct => { const thumbnail = values.media?.find((media) => media.isThumbnail)?.url const images = values.media ?.filter((media) => !media.isThumbnail) @@ -51,7 +51,7 @@ export const normalizeProductFormValues = ( export const normalizeVariants = ( variants: ProductCreateSchemaType["variants"], regionsCurrencyMap: Record -) => { +): HttpTypes.AdminCreateProductVariant[] => { return variants.map((variant) => ({ title: variant.custom_title || Object.values(variant.options || {}).join(" / "), @@ -61,7 +61,9 @@ export const normalizeVariants = ( allow_backorder: !!variant.allow_backorder, inventory_items: variant .inventory!.map((i) => { - const quantity = castNumber(i.required_quantity) + const quantity = i.required_quantity + ? castNumber(i.required_quantity) + : null if (!i.inventory_item_id || !quantity) { return false @@ -72,7 +74,12 @@ export const normalizeVariants = ( required_quantity: quantity, } }) - .filter(Boolean), + .filter( + ( + item + ): item is { required_quantity: number; inventory_item_id: string } => + item !== false + ), prices: Object.entries(variant.prices || {}) .map(([key, value]: any) => { if (value === "" || value === undefined) { diff --git a/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx b/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx index 7bcf7c21217d6..4667564448258 100644 --- a/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx +++ b/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx @@ -1,12 +1,34 @@ +import { + defaultDropAnimationSideEffects, + DndContext, + DragEndEvent, + DragOverlay, + DragStartEvent, + DropAnimation, + KeyboardSensor, + PointerSensor, + UniqueIdentifier, + useSensor, + useSensors, +} from "@dnd-kit/core" +import { + arrayMove, + rectSortingStrategy, + SortableContext, + sortableKeyboardCoordinates, + useSortable, +} from "@dnd-kit/sortable" +import { CSS } from "@dnd-kit/utilities" import { zodResolver } from "@hookform/resolvers/zod" -import { Button, CommandBar } from "@medusajs/ui" +import { ThumbnailBadge } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { Button, Checkbox, clx, CommandBar, toast, Tooltip } from "@medusajs/ui" import { Fragment, useCallback, useState } from "react" import { useFieldArray, useForm } from "react-hook-form" import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" import { z } from "zod" -import { HttpTypes } from "@medusajs/types" -import { Link } from "react-router-dom" import { RouteFocusModal, useRouteModal, @@ -14,7 +36,6 @@ import { import { KeyboundForm } from "../../../../../components/utilities/keybound-form" import { useUpdateProduct } from "../../../../../hooks/api/products" import { sdk } from "../../../../../lib/client" -import { MediaGrid } from "../../../common/components/media-grid-view" import { UploadMediaFormItem } from "../../../common/components/upload-media-form-item" import { EditProductMediaSchema, @@ -46,6 +67,38 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { keyName: "field_id", }) + const [activeId, setActiveId] = useState(null) + + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ) + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(event.active.id) + } + + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(null) + const { active, over } = event + + if (active.id !== over?.id) { + const oldIndex = fields.findIndex((item) => item.field_id === active.id) + const newIndex = fields.findIndex((item) => item.field_id === over?.id) + + form.setValue("media", arrayMove(fields, oldIndex, newIndex), { + shouldDirty: true, + shouldTouch: true, + }) + } + } + + const handleDragCancel = () => { + setActiveId(null) + } + const { mutateAsync, isPending } = useUpdateProduct(product.id!) const handleSubmit = form.handleSubmit(async ({ media }) => { @@ -80,13 +133,16 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { await mutateAsync( { images: withUpdatedUrls.map((file) => ({ url: file.url })), - // Set thumbnail to empty string if no thumbnail is selected, as the API does not accept null - thumbnail: thumbnail || "", + thumbnail: thumbnail, }, { onSuccess: () => { + toast.success(t("products.media.successToast")) handleSuccess() }, + onError: (error) => { + toast.error(error.message) + }, } ) }) @@ -139,20 +195,10 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { setSelection({}) } - const onSwapPositions = ( - callback: (items: typeof fields) => typeof fields - ) => { - const newFields = callback(fields) - - newFields.forEach((field, index) => { - update(index, field) - }) - } - const selectionCount = Object.keys(selection).length return ( - + {
    - + +
    +
    + m.field_id)} + strategy={rectSortingStrategy} + > + {fields.map((m) => { + return ( + + ) + })} + + + {activeId ? ( + m.field_id === activeId)!} + checked={ + !!selection[ + fields.find((m) => m.field_id === activeId)!.id! + ] + } + /> + ) : null} + +
    +
    +
    @@ -222,8 +300,8 @@ export const EditProductMediaForm = ({ product }: ProductMediaViewProps) => { } const getDefaultValues = ( - images: HttpTypes.AdminProductImage[] | undefined, - thumbnail: string | undefined + images: HttpTypes.AdminProductImage[] | null | undefined, + thumbnail: string | null | undefined ) => { const media: Media[] = images?.map((image) => ({ @@ -246,3 +324,129 @@ const getDefaultValues = ( return media } + +interface MediaView { + id?: string + field_id: string + url: string + isThumbnail: boolean +} + +const dropAnimationConfig: DropAnimation = { + sideEffects: defaultDropAnimationSideEffects({ + styles: { + active: { + opacity: "0.4", + }, + }, + }), +} + +interface MediaGridItemProps { + media: MediaView + checked: boolean + onCheckedChange: (value: boolean) => void +} + +const MediaGridItem = ({ + media, + checked, + onCheckedChange, +}: MediaGridItemProps) => { + const { t } = useTranslation() + + const handleToggle = useCallback( + (value: boolean) => { + onCheckedChange(value) + }, + [onCheckedChange] + ) + + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: media.field_id }) + + const style = { + opacity: isDragging ? 0.4 : undefined, + transform: CSS.Transform.toString(transform), + transition, + } + + return ( +
    + {media.isThumbnail && ( +
    + + + +
    + )} +
    +
    + { + e.stopPropagation() + }} + checked={checked} + onCheckedChange={handleToggle} + /> +
    + +
    + ) +} + +export const MediaGridItemOverlay = ({ + media, + checked, +}: { + media: MediaView + checked: boolean +}) => { + return ( +
    + {media.isThumbnail && ( +
    + +
    + )} +
    + +
    + +
    + ) +} diff --git a/packages/admin/dashboard/src/routes/products/product-media/components/product-media-gallery/product-media-gallery.tsx b/packages/admin/dashboard/src/routes/products/product-media/components/product-media-gallery/product-media-gallery.tsx index 3bdb10d84620b..1f410642e09ae 100644 --- a/packages/admin/dashboard/src/routes/products/product-media/components/product-media-gallery/product-media-gallery.tsx +++ b/packages/admin/dashboard/src/routes/products/product-media/components/product-media-gallery/product-media-gallery.tsx @@ -24,39 +24,39 @@ export const ProductMediaGallery = ({ product }: ProductMediaGalleryProps) => { const { t } = useTranslation() const prompt = usePrompt() - const { mutateAsync, isLoading } = useUpdateProduct(product.id) + const { mutateAsync, isPending } = useUpdateProduct(product.id) const media = getMedia(product.images, product.thumbnail) const next = useCallback(() => { - if (isLoading) { + if (isPending) { return } setCurr((prev) => (prev + 1) % media.length) - }, [media, isLoading]) + }, [media, isPending]) const prev = useCallback(() => { - if (isLoading) { + if (isPending) { return } setCurr((prev) => (prev - 1 + media.length) % media.length) - }, [media, isLoading]) + }, [media, isPending]) const goTo = useCallback( (index: number) => { - if (isLoading) { + if (isPending) { return } setCurr(index) }, - [isLoading] + [isPending] ) const handleDownloadCurrent = () => { - if (isLoading) { + if (isPending) { return } @@ -87,9 +87,10 @@ export const ProductMediaGallery = ({ product }: ProductMediaGalleryProps) => { return } - const mediaToKeep = product.images - .filter((i) => i.id !== current.id) - .map((i) => ({ url: i.url })) + const mediaToKeep = + product.images + ?.filter((i) => i.id !== current.id) + .map((i) => ({ url: i.url })) || [] if (curr === media.length - 1) { setCurr((prev) => prev - 1) @@ -195,7 +196,7 @@ const Canvas = ({ media, curr }: { media: Media[]; curr: number }) => { return (
    -
    +
    {media[curr].isThumbnail && (
    @@ -206,7 +207,7 @@ const Canvas = ({ media, curr }: { media: Media[]; curr: number }) => {
    diff --git a/packages/admin/dashboard/src/routes/products/product-media/product-media.tsx b/packages/admin/dashboard/src/routes/products/product-media/product-media.tsx index 03a2334cb581f..d5f05866e2ff0 100644 --- a/packages/admin/dashboard/src/routes/products/product-media/product-media.tsx +++ b/packages/admin/dashboard/src/routes/products/product-media/product-media.tsx @@ -1,9 +1,11 @@ +import { useTranslation } from "react-i18next" import { useParams } from "react-router-dom" import { RouteFocusModal } from "../../../components/modals" import { useProduct } from "../../../hooks/api/products" import { ProductMediaView } from "./components/product-media-view" export const ProductMedia = () => { + const { t } = useTranslation() const { id } = useParams() const { product, isLoading, isError, error } = useProduct(id!) @@ -16,6 +18,12 @@ export const ProductMedia = () => { return ( + + {t("products.media.label")} + + + {t("products.media.editHint")} + {ready && } ) From ce1514a0c56061dc487ee1984a509eab9569d5fa Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:21:53 +0100 Subject: [PATCH 12/17] lint --- packages/modules/product/src/models/index.ts | 6 +++--- packages/modules/product/src/models/product-image.ts | 2 +- packages/modules/product/src/models/product.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/modules/product/src/models/index.ts b/packages/modules/product/src/models/index.ts index a35e9b68a8e47..f7acfaa74e145 100644 --- a/packages/modules/product/src/models/index.ts +++ b/packages/modules/product/src/models/index.ts @@ -1,9 +1,9 @@ export { default as Product } from "./product" export { default as ProductCategory } from "./product-category" export { default as ProductCollection } from "./product-collection" +export { default as Image } from "./product-image" +export { default as ProductOption } from "./product-option" +export { default as ProductOptionValue } from "./product-option-value" export { default as ProductTag } from "./product-tag" export { default as ProductType } from "./product-type" export { default as ProductVariant } from "./product-variant" -export { default as ProductOption } from "./product-option" -export { default as ProductOptionValue } from "./product-option-value" -export { default as Image } from "./product-image" \ No newline at end of file diff --git a/packages/modules/product/src/models/product-image.ts b/packages/modules/product/src/models/product-image.ts index 35cfe3c611f2e..5c0baf2445b85 100644 --- a/packages/modules/product/src/models/product-image.ts +++ b/packages/modules/product/src/models/product-image.ts @@ -72,4 +72,4 @@ class ProductImage { } } -export default ProductImage \ No newline at end of file +export default ProductImage diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index 2674df97ab967..c2f89a39751af 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -221,4 +221,4 @@ class Product { } } -export default Product \ No newline at end of file +export default Product From 9d67ddc50c3b987c8d17300d7ce024049fe6e046 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:22:39 +0100 Subject: [PATCH 13/17] lint --- .../modules/product/src/services/product-module-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index d4fe39eaa21ba..0745db382c9a3 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -8,10 +8,10 @@ import { ProductTypes, } from "@medusajs/framework/types" import { - Image as ProductImage, Product, ProductCategory, ProductCollection, + Image as ProductImage, ProductOption, ProductOptionValue, ProductTag, @@ -1888,4 +1888,4 @@ export default class ProductModuleService } } } -} \ No newline at end of file +} From c3c858f60578ee39eb528555201c11d323575e84 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:23:15 +0100 Subject: [PATCH 14/17] lint --- .../__tests__/product-module-service/products.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts index 004e0bb46076b..cc4e778474af7 100644 --- a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts +++ b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts @@ -18,11 +18,11 @@ import { ProductType, } from "@models" -import { UpdateProductInput } from "@types" import { MockEventBusService, moduleIntegrationTestRunner, } from "@medusajs/test-utils" +import { UpdateProductInput } from "@types" import { buildProductAndRelationsData, createCollections, @@ -1238,4 +1238,4 @@ moduleIntegrationTestRunner({ }) }) }, -}) \ No newline at end of file +}) From 4c8af959bfd934804459751817298504f7befa1f Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:24:24 +0100 Subject: [PATCH 15/17] add changeset --- .changeset/flat-mugs-try.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/flat-mugs-try.md diff --git a/.changeset/flat-mugs-try.md b/.changeset/flat-mugs-try.md new file mode 100644 index 0000000000000..3d51fdafefdff --- /dev/null +++ b/.changeset/flat-mugs-try.md @@ -0,0 +1,5 @@ +--- +"@medusajs/dashboard": patch +--- + +feat(dashboard): Allow re-ordering product images From 08e3466948bd6fb66172d412bd28801a39d6eb5b Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:25:52 +0100 Subject: [PATCH 16/17] undo changes to product module --- .../product-module-service/products.spec.ts | 2 +- .../migrations/.snapshot-medusa-product.json | 215 +++++++++++++----- packages/modules/product/src/models/index.ts | 6 +- .../src/services/product-module-service.ts | 2 +- 4 files changed, 168 insertions(+), 57 deletions(-) diff --git a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts index cc4e778474af7..fbfecddd4c799 100644 --- a/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts +++ b/packages/modules/product/integration-tests/__tests__/product-module-service/products.spec.ts @@ -18,11 +18,11 @@ import { ProductType, } from "@models" +import { UpdateProductInput } from "@types" import { MockEventBusService, moduleIntegrationTestRunner, } from "@medusajs/test-utils" -import { UpdateProductInput } from "@types" import { buildProductAndRelationsData, createCollections, diff --git a/packages/modules/product/src/migrations/.snapshot-medusa-product.json b/packages/modules/product/src/migrations/.snapshot-medusa-product.json index 37ce6ea6eefc2..93d23dfb7769f 100644 --- a/packages/modules/product/src/migrations/.snapshot-medusa-product.json +++ b/packages/modules/product/src/migrations/.snapshot-medusa-product.json @@ -1,5 +1,7 @@ { - "namespaces": ["public"], + "namespaces": [ + "public" + ], "name": "public", "tables": [ { @@ -135,7 +137,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_category_deleted_at", "primary": false, @@ -143,7 +147,9 @@ }, { "keyName": "product_category_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -153,9 +159,13 @@ "foreignKeys": { "product_category_parent_category_id_foreign": { "constraintName": "product_category_parent_category_id_foreign", - "columnNames": ["parent_category_id"], + "columnNames": [ + "parent_category_id" + ], "localTableName": "public.product_category", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_category", "deleteRule": "cascade", "updateRule": "cascade" @@ -237,7 +247,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_collection_deleted_at", "primary": false, @@ -245,7 +257,9 @@ }, { "keyName": "product_collection_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -320,7 +334,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_image_deleted_at", "primary": false, @@ -328,7 +344,9 @@ }, { "keyName": "image_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -403,7 +421,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_tag_deleted_at", "primary": false, @@ -411,7 +431,9 @@ }, { "keyName": "product_tag_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -486,7 +508,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_type_deleted_at", "primary": false, @@ -494,7 +518,9 @@ }, { "keyName": "product_type_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -567,7 +593,12 @@ "autoincrement": false, "primary": false, "nullable": false, - "enumItems": ["draft", "proposed", "published", "rejected"], + "enumItems": [ + "draft", + "proposed", + "published", + "rejected" + ], "mappedType": "enum" }, "thumbnail": { @@ -734,7 +765,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_deleted_at", "primary": false, @@ -742,7 +775,9 @@ }, { "keyName": "product_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -752,18 +787,26 @@ "foreignKeys": { "product_collection_id_foreign": { "constraintName": "product_collection_id_foreign", - "columnNames": ["collection_id"], + "columnNames": [ + "collection_id" + ], "localTableName": "public.product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_collection", "deleteRule": "set null", "updateRule": "cascade" }, "product_type_id_foreign": { "constraintName": "product_type_id_foreign", - "columnNames": ["type_id"], + "columnNames": [ + "type_id" + ], "localTableName": "public.product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_type", "deleteRule": "set null", "updateRule": "cascade" @@ -845,7 +888,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_option_deleted_at", "primary": false, @@ -853,7 +898,9 @@ }, { "keyName": "product_option_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -863,9 +910,13 @@ "foreignKeys": { "product_option_product_id_foreign": { "constraintName": "product_option_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_option", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" @@ -947,7 +998,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_option_value_deleted_at", "primary": false, @@ -955,7 +1008,9 @@ }, { "keyName": "product_option_value_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -965,9 +1020,13 @@ "foreignKeys": { "product_option_value_option_id_foreign": { "constraintName": "product_option_value_option_id_foreign", - "columnNames": ["option_id"], + "columnNames": [ + "option_id" + ], "localTableName": "public.product_option_value", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_option", "deleteRule": "cascade", "updateRule": "cascade" @@ -1000,7 +1059,10 @@ "indexes": [ { "keyName": "product_tags_pkey", - "columnNames": ["product_id", "product_tag_id"], + "columnNames": [ + "product_id", + "product_tag_id" + ], "composite": true, "primary": true, "unique": true @@ -1010,18 +1072,26 @@ "foreignKeys": { "product_tags_product_id_foreign": { "constraintName": "product_tags_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_tags", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" }, "product_tags_product_tag_id_foreign": { "constraintName": "product_tags_product_tag_id_foreign", - "columnNames": ["product_tag_id"], + "columnNames": [ + "product_tag_id" + ], "localTableName": "public.product_tags", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_tag", "deleteRule": "cascade", "updateRule": "cascade" @@ -1054,7 +1124,10 @@ "indexes": [ { "keyName": "product_images_pkey", - "columnNames": ["product_id", "image_id"], + "columnNames": [ + "product_id", + "image_id" + ], "composite": true, "primary": true, "unique": true @@ -1064,18 +1137,26 @@ "foreignKeys": { "product_images_product_id_foreign": { "constraintName": "product_images_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_images", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" }, "product_images_image_id_foreign": { "constraintName": "product_images_image_id_foreign", - "columnNames": ["image_id"], + "columnNames": [ + "image_id" + ], "localTableName": "public.product_images", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.image", "deleteRule": "cascade", "updateRule": "cascade" @@ -1108,7 +1189,10 @@ "indexes": [ { "keyName": "product_category_product_pkey", - "columnNames": ["product_id", "product_category_id"], + "columnNames": [ + "product_id", + "product_category_id" + ], "composite": true, "primary": true, "unique": true @@ -1118,18 +1202,26 @@ "foreignKeys": { "product_category_product_product_id_foreign": { "constraintName": "product_category_product_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_category_product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" }, "product_category_product_product_category_id_foreign": { "constraintName": "product_category_product_product_category_id_foreign", - "columnNames": ["product_category_id"], + "columnNames": [ + "product_category_id" + ], "localTableName": "public.product_category_product", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_category", "deleteRule": "cascade", "updateRule": "cascade" @@ -1349,7 +1441,9 @@ "schema": "public", "indexes": [ { - "columnNames": ["deleted_at"], + "columnNames": [ + "deleted_at" + ], "composite": false, "keyName": "IDX_product_variant_deleted_at", "primary": false, @@ -1357,7 +1451,9 @@ }, { "keyName": "product_variant_pkey", - "columnNames": ["id"], + "columnNames": [ + "id" + ], "composite": false, "primary": true, "unique": true @@ -1367,9 +1463,13 @@ "foreignKeys": { "product_variant_product_id_foreign": { "constraintName": "product_variant_product_id_foreign", - "columnNames": ["product_id"], + "columnNames": [ + "product_id" + ], "localTableName": "public.product_variant", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product", "deleteRule": "cascade", "updateRule": "cascade" @@ -1402,7 +1502,10 @@ "indexes": [ { "keyName": "product_variant_option_pkey", - "columnNames": ["variant_id", "option_value_id"], + "columnNames": [ + "variant_id", + "option_value_id" + ], "composite": true, "primary": true, "unique": true @@ -1412,18 +1515,26 @@ "foreignKeys": { "product_variant_option_variant_id_foreign": { "constraintName": "product_variant_option_variant_id_foreign", - "columnNames": ["variant_id"], + "columnNames": [ + "variant_id" + ], "localTableName": "public.product_variant_option", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_variant", "deleteRule": "cascade", "updateRule": "cascade" }, "product_variant_option_option_value_id_foreign": { "constraintName": "product_variant_option_option_value_id_foreign", - "columnNames": ["option_value_id"], + "columnNames": [ + "option_value_id" + ], "localTableName": "public.product_variant_option", - "referencedColumnNames": ["id"], + "referencedColumnNames": [ + "id" + ], "referencedTableName": "public.product_option_value", "deleteRule": "cascade", "updateRule": "cascade" diff --git a/packages/modules/product/src/models/index.ts b/packages/modules/product/src/models/index.ts index f7acfaa74e145..f3f6a304fabe7 100644 --- a/packages/modules/product/src/models/index.ts +++ b/packages/modules/product/src/models/index.ts @@ -1,9 +1,9 @@ export { default as Product } from "./product" export { default as ProductCategory } from "./product-category" export { default as ProductCollection } from "./product-collection" -export { default as Image } from "./product-image" -export { default as ProductOption } from "./product-option" -export { default as ProductOptionValue } from "./product-option-value" export { default as ProductTag } from "./product-tag" export { default as ProductType } from "./product-type" export { default as ProductVariant } from "./product-variant" +export { default as ProductOption } from "./product-option" +export { default as ProductOptionValue } from "./product-option-value" +export { default as Image } from "./product-image" diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 0745db382c9a3..628887674ffd7 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -8,10 +8,10 @@ import { ProductTypes, } from "@medusajs/framework/types" import { + Image as ProductImage, Product, ProductCategory, ProductCollection, - Image as ProductImage, ProductOption, ProductOptionValue, ProductTag, From 25c25099943a650be933945af61f19e9081f59de Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:58:05 +0100 Subject: [PATCH 17/17] set activator node --- .../product-create-details-media-section.tsx | 3 +++ .../edit-product-media-form/edit-product-media-form.tsx | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx index d001328df3cf5..6cb5246586f3d 100644 --- a/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx +++ b/packages/admin/dashboard/src/routes/products/product-create/components/product-create-details-form/components/product-create-details-media-section/product-create-details-media-section.tsx @@ -175,6 +175,7 @@ const MediaItem = ({ field, onDelete, onMakeThumbnail }: MediaItemProps) => { attributes, listeners, setNodeRef, + setActivatorNodeRef, transform, transition, isDragging, @@ -199,9 +200,11 @@ const MediaItem = ({ field, onDelete, onMakeThumbnail }: MediaItemProps) => {
    diff --git a/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx b/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx index 4667564448258..11ab0f748746a 100644 --- a/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx +++ b/packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx @@ -366,6 +366,7 @@ const MediaGridItem = ({ attributes, listeners, setNodeRef, + setActivatorNodeRef, transform, transition, isDragging, @@ -379,7 +380,9 @@ const MediaGridItem = ({ return (
    @@ -394,6 +397,7 @@ const MediaGridItem = ({ className={clx("absolute inset-0 cursor-grab touch-none outline-none", { "cursor-grabbing": isDragging, })} + ref={setActivatorNodeRef} {...attributes} {...listeners} />