mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-03-21 12:29:03 +01:00
(feat) pagination
This commit is contained in:
parent
d5d8a972cf
commit
6678412cd6
@ -8,6 +8,7 @@ class API::ProductsController < API::ApiController
|
||||
|
||||
def index
|
||||
@products = ProductService.list(params)
|
||||
@pages = ProductService.pages if params[:page].present?
|
||||
end
|
||||
|
||||
def show
|
||||
|
@ -1,12 +1,12 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { serialize } from 'object-to-formdata';
|
||||
import { Product, ProductIndexFilter } from '../models/product';
|
||||
import { Product, ProductIndexFilter, ProductsIndex } from '../models/product';
|
||||
import ApiLib from '../lib/api';
|
||||
|
||||
export default class ProductAPI {
|
||||
static async index (filters?: ProductIndexFilter): Promise<Array<Product>> {
|
||||
const res: AxiosResponse<Array<Product>> = await apiClient.get(`/api/products${ApiLib.filtersToQuery(filters)}`);
|
||||
static async index (filters?: ProductIndexFilter): Promise<ProductsIndex> {
|
||||
const res: AxiosResponse<ProductsIndex> = await apiClient.get(`/api/products${ApiLib.filtersToQuery(filters)}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import { CaretDoubleLeft, CaretLeft, CaretRight, CaretDoubleRight } from 'phosphor-react';
|
||||
|
||||
interface FabPaginationProps {
|
||||
pageCount: number,
|
||||
currentPage: number,
|
||||
selectPage: (page: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a pagination navigation
|
||||
*/
|
||||
export const FabPagination: React.FC<FabPaginationProps> = ({ pageCount, currentPage, selectPage }) => {
|
||||
return (
|
||||
<nav className='fab-pagination'>
|
||||
{currentPage - 2 > 1 &&
|
||||
<button onClick={() => selectPage(1)}><CaretDoubleLeft size={24} /></button>
|
||||
}
|
||||
{currentPage - 1 >= 1 &&
|
||||
<button onClick={() => selectPage(currentPage - 1)}><CaretLeft size={24} /></button>
|
||||
}
|
||||
{currentPage - 2 >= 1 &&
|
||||
<button onClick={() => selectPage(currentPage - 2)}>{currentPage - 2}</button>
|
||||
}
|
||||
{currentPage - 1 >= 1 &&
|
||||
<button onClick={() => selectPage(currentPage - 1)}>{currentPage - 1}</button>
|
||||
}
|
||||
<button className='is-active'>{currentPage}</button>
|
||||
{currentPage + 1 <= pageCount &&
|
||||
<button onClick={() => selectPage(currentPage + 1)}>{currentPage + 1}</button>
|
||||
}
|
||||
{currentPage + 2 <= pageCount &&
|
||||
<button onClick={() => selectPage(currentPage + 2)}>{currentPage + 2}</button>
|
||||
}
|
||||
{currentPage + 1 <= pageCount &&
|
||||
<button onClick={() => selectPage(currentPage + 1)}><CaretRight size={24} /></button>
|
||||
}
|
||||
{currentPage + 2 < pageCount &&
|
||||
<button onClick={() => selectPage(pageCount)}><CaretDoubleRight size={24} /></button>
|
||||
}
|
||||
</nav>
|
||||
);
|
||||
};
|
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
import { OrderItem } from './order-item';
|
||||
import { FabPagination } from '../base/fab-pagination';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -25,6 +26,11 @@ type selectOption = { value: number, label: string };
|
||||
export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ onError }) => {
|
||||
const { t } = useTranslation('public');
|
||||
|
||||
// TODO: delete next eslint disable
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [pageCount, setPageCount] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
|
||||
/**
|
||||
* Creates sorting options to the react-select format
|
||||
*/
|
||||
@ -56,6 +62,9 @@ export const OrdersDashboard: React.FC<OrdersDashboardProps> = ({ onError }) =>
|
||||
<div className="orders-list">
|
||||
<OrderItem />
|
||||
</div>
|
||||
{pageCount > 1 &&
|
||||
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={setCurrentPage} />
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
@ -14,6 +14,7 @@ import MachineAPI from '../../api/machine';
|
||||
import { AccordionItem } from './accordion-item';
|
||||
import { X } from 'phosphor-react';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
import { FabPagination } from '../base/fab-pagination';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -33,8 +34,6 @@ interface ProductsProps {
|
||||
const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [products, setProducts] = useState<Array<Product>>([]);
|
||||
const [filteredProductsList, setFilteredProductList] = useImmer<Array<Product>>([]);
|
||||
const [features, setFeatures] = useImmer<Filters>(initFilters);
|
||||
const [filterVisible, setFilterVisible] = useState<boolean>(false);
|
||||
@ -44,11 +43,13 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
const [machines, setMachines] = useState<checklistOption[]>([]);
|
||||
const [update, setUpdate] = useState(false);
|
||||
const [accordion, setAccordion] = useState({});
|
||||
const [pageCount, setPageCount] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index().then(data => {
|
||||
setProducts(data);
|
||||
setFilteredProductList(data);
|
||||
ProductAPI.index({ page: 1 }).then(data => {
|
||||
setPageCount(data.total_pages);
|
||||
setFilteredProductList(data.products);
|
||||
});
|
||||
|
||||
ProductCategoryAPI.index().then(data => {
|
||||
@ -71,6 +72,14 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
}).catch(onError);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index({ page: currentPage }).then(data => {
|
||||
setFilteredProductList(data.products);
|
||||
setPageCount(data.total_pages);
|
||||
window.document.getElementById('content-main').scrollTo({ top: 100, behavior: 'smooth' });
|
||||
});
|
||||
}, [currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
applyFilters();
|
||||
setClearFilters(false);
|
||||
@ -91,7 +100,7 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
try {
|
||||
await ProductAPI.destroy(productId);
|
||||
const data = await ProductAPI.index();
|
||||
setProducts(data);
|
||||
setFilteredProductList(data.products);
|
||||
onSuccess(t('app.admin.store.products.successfully_deleted'));
|
||||
} catch (e) {
|
||||
onError(t('app.admin.store.products.unable_to_delete') + e);
|
||||
@ -307,6 +316,9 @@ const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{pageCount > 1 &&
|
||||
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={setCurrentPage} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -16,6 +16,7 @@ import { User } from '../../models/user';
|
||||
import { Order } from '../../models/order';
|
||||
import { AccordionItem } from './accordion-item';
|
||||
import { StoreListHeader } from './store-list-header';
|
||||
import { FabPagination } from '../base/fab-pagination';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
@ -45,10 +46,13 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
const [filterVisible, setFilterVisible] = useState<boolean>(false);
|
||||
const [machines, setMachines] = useState<checklistOption[]>([]);
|
||||
const [accordion, setAccordion] = useState({});
|
||||
const [pageCount, setPageCount] = useState<number>(0);
|
||||
const [currentPage, setCurrentPage] = useState<number>(1);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index({ is_active: true }).then(data => {
|
||||
setProducts(data);
|
||||
ProductAPI.index({ page: 1 }).then(data => {
|
||||
setPageCount(data.total_pages);
|
||||
setProducts(data.products);
|
||||
}).catch(() => {
|
||||
onError(t('app.public.store.unexpected_error_occurred'));
|
||||
});
|
||||
@ -71,6 +75,14 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
emitCustomEvent('CartUpdate', cart);
|
||||
}, [cart]);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index({ page: currentPage }).then(data => {
|
||||
setProducts(data.products);
|
||||
setPageCount(data.total_pages);
|
||||
window.document.getElementById('content-main').scrollTo({ top: 100, behavior: 'smooth' });
|
||||
});
|
||||
}, [currentPage]);
|
||||
|
||||
/**
|
||||
* Create categories tree (parent/children)
|
||||
*/
|
||||
@ -237,6 +249,9 @@ const Store: React.FC<StoreProps> = ({ onError, onSuccess, currentUser }) => {
|
||||
<StoreProductItem key={product.id} product={product} cart={cart} onSuccessAddProductToCart={addToCart} />
|
||||
))}
|
||||
</div>
|
||||
{pageCount > 1 &&
|
||||
<FabPagination pageCount={pageCount} currentPage={currentPage} selectPage={setCurrentPage} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,7 +2,8 @@ import { TDateISO } from '../typings/date-iso';
|
||||
import { ApiFilter } from './api';
|
||||
|
||||
export interface ProductIndexFilter extends ApiFilter {
|
||||
is_active: boolean,
|
||||
is_active?: boolean,
|
||||
page?: number
|
||||
}
|
||||
|
||||
export enum StockType {
|
||||
@ -15,6 +16,11 @@ export interface Stock {
|
||||
external: number,
|
||||
}
|
||||
|
||||
export interface ProductsIndex {
|
||||
total_pages?: number,
|
||||
products: Array<Product>
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number,
|
||||
name: string,
|
||||
|
@ -25,6 +25,7 @@
|
||||
@import "modules/base/fab-input";
|
||||
@import "modules/base/fab-modal";
|
||||
@import "modules/base/fab-output-copy";
|
||||
@import "modules/base/fab-pagination";
|
||||
@import "modules/base/fab-panel";
|
||||
@import "modules/base/fab-popover";
|
||||
@import "modules/base/fab-state-label";
|
||||
|
@ -0,0 +1,25 @@
|
||||
.fab-pagination {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: repeat(9, min-content);
|
||||
justify-content: center;
|
||||
gap: 1.6rem;
|
||||
button {
|
||||
min-width: 4rem;
|
||||
height: 4rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-sm);
|
||||
@include text-lg(500);
|
||||
&:hover:not(.is-active) {
|
||||
background-color: var(--gray-soft);
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
background-color: var(--main);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
}
|
@ -2,15 +2,24 @@
|
||||
|
||||
# Provides methods for Product
|
||||
class ProductService
|
||||
PRODUCTS_PER_PAGE = 2
|
||||
|
||||
def self.list(filters)
|
||||
products = Product.includes(:product_images)
|
||||
if filters[:is_active].present?
|
||||
state = filters[:disabled] == 'false' ? [nil, false] : true
|
||||
products = products.where(is_active: state)
|
||||
end
|
||||
if filters[:page].present?
|
||||
products = products.page(filters[:page]).per(PRODUCTS_PER_PAGE)
|
||||
end
|
||||
products
|
||||
end
|
||||
|
||||
def self.pages
|
||||
Product.page(1).per(PRODUCTS_PER_PAGE).total_pages
|
||||
end
|
||||
|
||||
# amount params multiplied by hundred
|
||||
def self.amount_multiplied_by_hundred(amount)
|
||||
if amount.present?
|
||||
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.array! @products do |product|
|
||||
json.total_pages @pages if @pages.present?
|
||||
json.products @products do |product|
|
||||
json.extract! product, :id, :name, :slug, :sku, :is_active, :product_category_id, :quantity_min, :stock, :machine_ids,
|
||||
:low_stock_threshold
|
||||
json.amount product.amount / 100.0 if product.amount.present?
|
||||
|
Loading…
x
Reference in New Issue
Block a user