1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-29 18:52:22 +01:00

(feat) Save product categories' positions

This commit is contained in:
vincent 2022-09-16 17:01:56 +02:00 committed by Sylvain
parent b5c924a8e0
commit 9742558cfa
5 changed files with 85 additions and 50 deletions

View File

@ -40,7 +40,7 @@ class API::ProductCategoriesController < API::ApiController
if @product_category.insert_at(params[:position])
render :show
else
render json: @product_category.errors.full_messages, status: :unprocessable_entity
render json: @product_category.errors, status: :unprocessable_entity
end
end

View File

@ -1,11 +1,9 @@
// TODO: Remove next eslint-disable
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react';
import { ProductCategory } from '../../../models/product-category';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { ManageProductCategory } from './manage-product-category';
import { ArrowElbowDownRight, ArrowElbowLeftUp, CaretDown, DotsSixVertical } from 'phosphor-react';
import { ArrowElbowDownRight, ArrowLeft, CaretDown, DotsSixVertical } from 'phosphor-react';
interface ProductCategoriesItemProps {
productCategories: Array<ProductCategory>,
@ -42,7 +40,7 @@ export const ProductCategoriesItem: React.FC<ProductCategoriesItemProps> = ({ pr
{((isDragging && offset) || status === 'child') &&
<div className='offset'>
{(offset === 'down') && <ArrowElbowDownRight size={32} weight="light" />}
{(offset === 'up') && <ArrowElbowLeftUp size={32} weight="light" />}
{(offset === 'up') && <ArrowLeft size={32} weight="light" />}
</div>
}
<div className='wrap'>

View File

@ -1,6 +1,4 @@
// TODO: Remove next eslint-disable
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { useImmer } from 'use-immer';
import { ProductCategory } from '../../../models/product-category';
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, closestCenter, DragMoveEvent } from '@dnd-kit/core';
@ -10,7 +8,7 @@ import { ProductCategoriesItem } from './product-categories-item';
interface ProductCategoriesTreeProps {
productCategories: Array<ProductCategory>,
onDnd: (list: Array<ProductCategory>) => void,
onDnd: (list: Array<ProductCategory>, activeCategory: ProductCategory, position: number) => void,
onSuccess: (message: string) => void,
onError: (message: string) => void,
}
@ -67,6 +65,10 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
setActiveData(draft => {
return { ...draft, offset: 'down' };
});
} else if (Math.ceil(delta.x) < -32 && getStatus(over.id) === 'child') {
setActiveData(draft => {
return { ...draft, offset: 'up' };
});
} else {
setActiveData(draft => {
return { ...draft, offset: null };
@ -98,47 +100,60 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
let newOrder = [...categoriesList];
const currentIdsOrder = over?.data.current.sortable.items;
let newIndex = over.data.current.sortable.index;
let droppedItem = getCategory(active.id);
const activeStatus = getStatus(active.id);
const overStatus = getStatus(over.id);
let newPosition = getCategory(over.id).position;
// [A] Single |> [B] Single
if (getStatus(active.id) === 'single' && getStatus(over.id) === 'single') {
// [A]:Single dropped over [B]:Single
if (activeStatus === 'single' && overStatus === 'single') {
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => {
let category = getCategory(sortedId);
if (activeData.offset === 'down' && sortedId === active.id && activeData.index < newIndex && active.id !== over.id) {
category = { ...category, parent_id: Number(over.id) };
droppedItem = category;
} else if (activeData.offset === 'down' && sortedId === active.id && (activeData.index > newIndex || active.id === over.id)) {
category = { ...category, parent_id: getPreviousAdopter(over.id) };
droppedItem = category;
}
return category;
});
}
// [A] Child |> [B] Single
if ((getStatus(active.id) === 'child') && getStatus(over.id) === 'single') {
// [A]:Child dropped over [B]:Single
if ((activeStatus === 'child') && overStatus === 'single') {
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => {
let category = getCategory(sortedId);
if (activeData.offset === 'down' && sortedId === active.id && activeData.index < newIndex) {
category = { ...category, parent_id: Number(over.id) };
droppedItem = category;
newPosition = 0;
} else if (activeData.offset === 'down' && sortedId === active.id && activeData.index > newIndex) {
category = { ...category, parent_id: getPreviousAdopter(over.id) };
droppedItem = category;
newPosition = getChildren(getPreviousAdopter(over.id))?.length || 0;
} else if (sortedId === active.id) {
category = { ...category, parent_id: null };
droppedItem = category;
}
return category;
});
}
// [A] Single || Child |>
if (getStatus(active.id) === 'single' || getStatus(active.id) === 'child') {
// [B] Parent
if (getStatus(over.id) === 'parent') {
// [A]:Single || [A]:Child dropped over
if (activeStatus === 'single' || activeStatus === 'child') {
// [B]:Parent
if (overStatus === 'parent') {
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
if (activeData.index < newIndex) {
newOrder = newIdsOrder.map(sortedId => {
let category = getCategory(sortedId);
if (sortedId === active.id) {
category = { ...category, parent_id: Number(over.id) };
droppedItem = category;
newPosition = 0;
}
return category;
});
@ -147,38 +162,54 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
let category = getCategory(sortedId);
if (sortedId === active.id && !activeData.offset) {
category = { ...category, parent_id: null };
droppedItem = category;
} else if (sortedId === active.id && activeData.offset === 'down') {
category = { ...category, parent_id: getPreviousAdopter(over.id) };
droppedItem = category;
newPosition = getChildren(getPreviousAdopter(over.id))?.length || 0;
}
return category;
});
}
}
// [B] Child
if (getStatus(over.id) === 'child') {
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => {
let category = getCategory(sortedId);
if (sortedId === active.id) {
category = { ...category, parent_id: getCategory(over.id).parent_id };
}
return category;
});
// [B]:Child
if (overStatus === 'child') {
if (activeData.offset === 'up') {
const lastChildIndex = newOrder.findIndex(c => c.id === getChildren(getCategory(over.id).parent_id).pop().id);
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, lastChildIndex);
newOrder = newIdsOrder.map(sortedId => {
let category = getCategory(sortedId);
if (sortedId === active.id) {
category = { ...category, parent_id: null };
droppedItem = category;
}
return category;
});
} else {
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => {
let category = getCategory(sortedId);
if (sortedId === active.id) {
category = { ...category, parent_id: getCategory(over.id).parent_id };
droppedItem = category;
}
return category;
});
}
}
}
// [A] Parent |>…
if (getStatus(active.id) === 'parent') {
// [B] Single
if (getStatus(over.id) === 'single') {
// [A]:Parent dropped over
if (activeStatus === 'parent') {
// [B]:Single
if (overStatus === 'single') {
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
}
// [B] Parent
if (getStatus(over.id) === 'parent') {
// [B]:Parent
if (overStatus === 'parent') {
if (activeData.index < newIndex) {
const lastOverChildIndex = newOrder.findIndex(c => c.id === getChildren(over.id).pop().id);
newIndex = lastOverChildIndex;
newIndex = newOrder.findIndex(c => c.id === getChildren(over.id).pop().id);
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
} else {
@ -186,17 +217,15 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
}
}
// [B] Child
if (getStatus(over.id) === 'child') {
// [B]:Child
if (overStatus === 'child') {
if (activeData.index < newIndex) {
const parent = newOrder.find(c => c.id === getCategory(over.id).parent_id);
const lastSiblingIndex = newOrder.findIndex(c => c.id === getChildren(parent.id).pop().id);
newIndex = lastSiblingIndex;
newIndex = newOrder.findIndex(c => c.id === getChildren(parent.id).pop().id);
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
} else {
const parentIndex = currentIdsOrder.indexOf(getCategory(over.id).parent_id);
newIndex = parentIndex;
newIndex = currentIdsOrder.indexOf(getCategory(over.id).parent_id);
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
}
@ -204,8 +233,9 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
// insert children back
newOrder = showChildren(active.id, newOrder, newIndex);
}
setActiveData(initActiveData);
onDnd(newOrder);
onDnd(newOrder, droppedItem, newPosition);
};
/**
@ -283,7 +313,7 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
};
/**
* Toggle parent category by hidding/showing its children
* Toggle parent category by hiding/showing its children
*/
const handleCollapse = (id) => {
const i = collapsed.findIndex(el => el === id);

View File

@ -46,8 +46,16 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
/**
* Update state after drop
*/
const handleDnd = (data: ProductCategory[]) => {
setProductCategories(data);
const handleDnd = (list: ProductCategory[], activeCategory: ProductCategory, position: number) => {
setProductCategories(list);
ProductCategoryAPI
.update(activeCategory)
.then(c => {
ProductCategoryAPI
.updatePosition(c, position)
.catch(error => onError(error));
})
.catch(error => onError(error));
};
/**
@ -60,10 +68,9 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
};
/**
* Save list's new order
* tmp: check list
*/
const handleSave = () => {
// TODO: index to position -> send to API
console.log('save order:', productCategories);
};
@ -75,7 +82,7 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
<ManageProductCategory action='create'
productCategories={productCategories}
onSuccess={handleSuccess} onError={onError} />
<FabButton className='main-action-btn' onClick={handleSave}>Save</FabButton>
<FabButton className='main-action-btn' onClick={handleSave}>[log]</FabButton>
</div>
</header>
<FabAlert level="warning">

View File

@ -49,7 +49,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
const [currentPage, setCurrentPage] = useState<number>(1);
useEffect(() => {
ProductAPI.index({ page: 1, is_active: true }).then(data => {
ProductAPI.index({ page: 1, is_active: filterVisible }).then(data => {
setPageCount(data.total_pages);
setProducts(data.products);
}).catch(() => {
@ -151,7 +151,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
*/
const handlePagination = (page: number) => {
if (page !== currentPage) {
ProductAPI.index({ page, is_active: true }).then(data => {
ProductAPI.index({ page, is_active: filterVisible }).then(data => {
setCurrentPage(page);
setProducts(data.products);
setPageCount(data.total_pages);