mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-06 01:08:21 +01:00
(feat) Save product categories' positions
This commit is contained in:
parent
b5c924a8e0
commit
9742558cfa
@ -40,7 +40,7 @@ class API::ProductCategoriesController < API::ApiController
|
|||||||
if @product_category.insert_at(params[:position])
|
if @product_category.insert_at(params[:position])
|
||||||
render :show
|
render :show
|
||||||
else
|
else
|
||||||
render json: @product_category.errors.full_messages, status: :unprocessable_entity
|
render json: @product_category.errors, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// TODO: Remove next eslint-disable
|
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ProductCategory } from '../../../models/product-category';
|
import { ProductCategory } from '../../../models/product-category';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { ManageProductCategory } from './manage-product-category';
|
import { ManageProductCategory } from './manage-product-category';
|
||||||
import { ArrowElbowDownRight, ArrowElbowLeftUp, CaretDown, DotsSixVertical } from 'phosphor-react';
|
import { ArrowElbowDownRight, ArrowLeft, CaretDown, DotsSixVertical } from 'phosphor-react';
|
||||||
|
|
||||||
interface ProductCategoriesItemProps {
|
interface ProductCategoriesItemProps {
|
||||||
productCategories: Array<ProductCategory>,
|
productCategories: Array<ProductCategory>,
|
||||||
@ -42,7 +40,7 @@ export const ProductCategoriesItem: React.FC<ProductCategoriesItemProps> = ({ pr
|
|||||||
{((isDragging && offset) || status === 'child') &&
|
{((isDragging && offset) || status === 'child') &&
|
||||||
<div className='offset'>
|
<div className='offset'>
|
||||||
{(offset === 'down') && <ArrowElbowDownRight size={32} weight="light" />}
|
{(offset === 'down') && <ArrowElbowDownRight size={32} weight="light" />}
|
||||||
{(offset === 'up') && <ArrowElbowLeftUp size={32} weight="light" />}
|
{(offset === 'up') && <ArrowLeft size={32} weight="light" />}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className='wrap'>
|
<div className='wrap'>
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
// TODO: Remove next eslint-disable
|
import React, { useEffect } from 'react';
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useImmer } from 'use-immer';
|
import { useImmer } from 'use-immer';
|
||||||
import { ProductCategory } from '../../../models/product-category';
|
import { ProductCategory } from '../../../models/product-category';
|
||||||
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, closestCenter, DragMoveEvent } from '@dnd-kit/core';
|
import { DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, closestCenter, DragMoveEvent } from '@dnd-kit/core';
|
||||||
@ -10,7 +8,7 @@ import { ProductCategoriesItem } from './product-categories-item';
|
|||||||
|
|
||||||
interface ProductCategoriesTreeProps {
|
interface ProductCategoriesTreeProps {
|
||||||
productCategories: Array<ProductCategory>,
|
productCategories: Array<ProductCategory>,
|
||||||
onDnd: (list: Array<ProductCategory>) => void,
|
onDnd: (list: Array<ProductCategory>, activeCategory: ProductCategory, position: number) => void,
|
||||||
onSuccess: (message: string) => void,
|
onSuccess: (message: string) => void,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
}
|
}
|
||||||
@ -67,6 +65,10 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
|
|||||||
setActiveData(draft => {
|
setActiveData(draft => {
|
||||||
return { ...draft, offset: 'down' };
|
return { ...draft, offset: 'down' };
|
||||||
});
|
});
|
||||||
|
} else if (Math.ceil(delta.x) < -32 && getStatus(over.id) === 'child') {
|
||||||
|
setActiveData(draft => {
|
||||||
|
return { ...draft, offset: 'up' };
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setActiveData(draft => {
|
setActiveData(draft => {
|
||||||
return { ...draft, offset: null };
|
return { ...draft, offset: null };
|
||||||
@ -98,47 +100,60 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
|
|||||||
let newOrder = [...categoriesList];
|
let newOrder = [...categoriesList];
|
||||||
const currentIdsOrder = over?.data.current.sortable.items;
|
const currentIdsOrder = over?.data.current.sortable.items;
|
||||||
let newIndex = over.data.current.sortable.index;
|
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
|
// [A]:Single dropped over [B]:Single
|
||||||
if (getStatus(active.id) === 'single' && getStatus(over.id) === 'single') {
|
if (activeStatus === 'single' && overStatus === 'single') {
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
newOrder = newIdsOrder.map(sortedId => {
|
newOrder = newIdsOrder.map(sortedId => {
|
||||||
let category = getCategory(sortedId);
|
let category = getCategory(sortedId);
|
||||||
if (activeData.offset === 'down' && sortedId === active.id && activeData.index < newIndex && active.id !== over.id) {
|
if (activeData.offset === 'down' && sortedId === active.id && activeData.index < newIndex && active.id !== over.id) {
|
||||||
category = { ...category, parent_id: Number(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)) {
|
} else if (activeData.offset === 'down' && sortedId === active.id && (activeData.index > newIndex || active.id === over.id)) {
|
||||||
category = { ...category, parent_id: getPreviousAdopter(over.id) };
|
category = { ...category, parent_id: getPreviousAdopter(over.id) };
|
||||||
|
droppedItem = category;
|
||||||
}
|
}
|
||||||
return category;
|
return category;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// [A] Child |> [B] Single
|
// [A]:Child dropped over [B]:Single
|
||||||
if ((getStatus(active.id) === 'child') && getStatus(over.id) === 'single') {
|
if ((activeStatus === 'child') && overStatus === 'single') {
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
newOrder = newIdsOrder.map(sortedId => {
|
newOrder = newIdsOrder.map(sortedId => {
|
||||||
let category = getCategory(sortedId);
|
let category = getCategory(sortedId);
|
||||||
if (activeData.offset === 'down' && sortedId === active.id && activeData.index < newIndex) {
|
if (activeData.offset === 'down' && sortedId === active.id && activeData.index < newIndex) {
|
||||||
category = { ...category, parent_id: Number(over.id) };
|
category = { ...category, parent_id: Number(over.id) };
|
||||||
|
droppedItem = category;
|
||||||
|
newPosition = 0;
|
||||||
} else if (activeData.offset === 'down' && sortedId === active.id && activeData.index > newIndex) {
|
} else if (activeData.offset === 'down' && sortedId === active.id && activeData.index > newIndex) {
|
||||||
category = { ...category, parent_id: getPreviousAdopter(over.id) };
|
category = { ...category, parent_id: getPreviousAdopter(over.id) };
|
||||||
|
droppedItem = category;
|
||||||
|
newPosition = getChildren(getPreviousAdopter(over.id))?.length || 0;
|
||||||
} else if (sortedId === active.id) {
|
} else if (sortedId === active.id) {
|
||||||
category = { ...category, parent_id: null };
|
category = { ...category, parent_id: null };
|
||||||
|
droppedItem = category;
|
||||||
}
|
}
|
||||||
return category;
|
return category;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// [A] Single || Child |>…
|
// [A]:Single || [A]:Child dropped over…
|
||||||
if (getStatus(active.id) === 'single' || getStatus(active.id) === 'child') {
|
if (activeStatus === 'single' || activeStatus === 'child') {
|
||||||
// [B] Parent
|
// [B]:Parent
|
||||||
if (getStatus(over.id) === 'parent') {
|
if (overStatus === 'parent') {
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
if (activeData.index < newIndex) {
|
if (activeData.index < newIndex) {
|
||||||
newOrder = newIdsOrder.map(sortedId => {
|
newOrder = newIdsOrder.map(sortedId => {
|
||||||
let category = getCategory(sortedId);
|
let category = getCategory(sortedId);
|
||||||
if (sortedId === active.id) {
|
if (sortedId === active.id) {
|
||||||
category = { ...category, parent_id: Number(over.id) };
|
category = { ...category, parent_id: Number(over.id) };
|
||||||
|
droppedItem = category;
|
||||||
|
newPosition = 0;
|
||||||
}
|
}
|
||||||
return category;
|
return category;
|
||||||
});
|
});
|
||||||
@ -147,38 +162,54 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
|
|||||||
let category = getCategory(sortedId);
|
let category = getCategory(sortedId);
|
||||||
if (sortedId === active.id && !activeData.offset) {
|
if (sortedId === active.id && !activeData.offset) {
|
||||||
category = { ...category, parent_id: null };
|
category = { ...category, parent_id: null };
|
||||||
|
droppedItem = category;
|
||||||
} else if (sortedId === active.id && activeData.offset === 'down') {
|
} else if (sortedId === active.id && activeData.offset === 'down') {
|
||||||
category = { ...category, parent_id: getPreviousAdopter(over.id) };
|
category = { ...category, parent_id: getPreviousAdopter(over.id) };
|
||||||
|
droppedItem = category;
|
||||||
|
newPosition = getChildren(getPreviousAdopter(over.id))?.length || 0;
|
||||||
}
|
}
|
||||||
return category;
|
return category;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// [B] Child
|
// [B]:Child
|
||||||
if (getStatus(over.id) === 'child') {
|
if (overStatus === 'child') {
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
if (activeData.offset === 'up') {
|
||||||
newOrder = newIdsOrder.map(sortedId => {
|
const lastChildIndex = newOrder.findIndex(c => c.id === getChildren(getCategory(over.id).parent_id).pop().id);
|
||||||
let category = getCategory(sortedId);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, lastChildIndex);
|
||||||
if (sortedId === active.id) {
|
newOrder = newIdsOrder.map(sortedId => {
|
||||||
category = { ...category, parent_id: getCategory(over.id).parent_id };
|
let category = getCategory(sortedId);
|
||||||
}
|
if (sortedId === active.id) {
|
||||||
return category;
|
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 |>…
|
// [A]:Parent dropped over…
|
||||||
if (getStatus(active.id) === 'parent') {
|
if (activeStatus === 'parent') {
|
||||||
// [B] Single
|
// [B]:Single
|
||||||
if (getStatus(over.id) === 'single') {
|
if (overStatus === 'single') {
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
||||||
}
|
}
|
||||||
// [B] Parent
|
// [B]:Parent
|
||||||
if (getStatus(over.id) === 'parent') {
|
if (overStatus === 'parent') {
|
||||||
if (activeData.index < newIndex) {
|
if (activeData.index < newIndex) {
|
||||||
const lastOverChildIndex = newOrder.findIndex(c => c.id === getChildren(over.id).pop().id);
|
newIndex = newOrder.findIndex(c => c.id === getChildren(over.id).pop().id);
|
||||||
newIndex = lastOverChildIndex;
|
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
||||||
} else {
|
} else {
|
||||||
@ -186,17 +217,15 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
|
|||||||
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// [B] Child
|
// [B]:Child
|
||||||
if (getStatus(over.id) === 'child') {
|
if (overStatus === 'child') {
|
||||||
if (activeData.index < newIndex) {
|
if (activeData.index < newIndex) {
|
||||||
const parent = newOrder.find(c => c.id === getCategory(over.id).parent_id);
|
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 = newOrder.findIndex(c => c.id === getChildren(parent.id).pop().id);
|
||||||
newIndex = lastSiblingIndex;
|
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
||||||
} else {
|
} else {
|
||||||
const parentIndex = currentIdsOrder.indexOf(getCategory(over.id).parent_id);
|
newIndex = currentIdsOrder.indexOf(getCategory(over.id).parent_id);
|
||||||
newIndex = parentIndex;
|
|
||||||
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
const newIdsOrder = arrayMove(currentIdsOrder, activeData.index, newIndex);
|
||||||
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
newOrder = newIdsOrder.map(sortedId => getCategory(sortedId));
|
||||||
}
|
}
|
||||||
@ -204,8 +233,9 @@ export const ProductCategoriesTree: React.FC<ProductCategoriesTreeProps> = ({ pr
|
|||||||
// insert children back
|
// insert children back
|
||||||
newOrder = showChildren(active.id, newOrder, newIndex);
|
newOrder = showChildren(active.id, newOrder, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
setActiveData(initActiveData);
|
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 handleCollapse = (id) => {
|
||||||
const i = collapsed.findIndex(el => el === id);
|
const i = collapsed.findIndex(el => el === id);
|
||||||
|
@ -46,8 +46,16 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
|
|||||||
/**
|
/**
|
||||||
* Update state after drop
|
* Update state after drop
|
||||||
*/
|
*/
|
||||||
const handleDnd = (data: ProductCategory[]) => {
|
const handleDnd = (list: ProductCategory[], activeCategory: ProductCategory, position: number) => {
|
||||||
setProductCategories(data);
|
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 = () => {
|
const handleSave = () => {
|
||||||
// TODO: index to position -> send to API
|
|
||||||
console.log('save order:', productCategories);
|
console.log('save order:', productCategories);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,7 +82,7 @@ const ProductCategories: React.FC<ProductCategoriesProps> = ({ onSuccess, onErro
|
|||||||
<ManageProductCategory action='create'
|
<ManageProductCategory action='create'
|
||||||
productCategories={productCategories}
|
productCategories={productCategories}
|
||||||
onSuccess={handleSuccess} onError={onError} />
|
onSuccess={handleSuccess} onError={onError} />
|
||||||
<FabButton className='main-action-btn' onClick={handleSave}>Save</FabButton>
|
<FabButton className='main-action-btn' onClick={handleSave}>[log]</FabButton>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<FabAlert level="warning">
|
<FabAlert level="warning">
|
||||||
|
@ -49,7 +49,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
|||||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ProductAPI.index({ page: 1, is_active: true }).then(data => {
|
ProductAPI.index({ page: 1, is_active: filterVisible }).then(data => {
|
||||||
setPageCount(data.total_pages);
|
setPageCount(data.total_pages);
|
||||||
setProducts(data.products);
|
setProducts(data.products);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
@ -151,7 +151,7 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
|||||||
*/
|
*/
|
||||||
const handlePagination = (page: number) => {
|
const handlePagination = (page: number) => {
|
||||||
if (page !== currentPage) {
|
if (page !== currentPage) {
|
||||||
ProductAPI.index({ page, is_active: true }).then(data => {
|
ProductAPI.index({ page, is_active: filterVisible }).then(data => {
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
setProducts(data.products);
|
setProducts(data.products);
|
||||||
setPageCount(data.total_pages);
|
setPageCount(data.total_pages);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user