mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
products page in front
This commit is contained in:
parent
1e3e7854b2
commit
e23e83000d
30
app/frontend/src/javascript/api/product.ts
Normal file
30
app/frontend/src/javascript/api/product.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Product } from '../models/product';
|
||||
|
||||
export default class ProductAPI {
|
||||
static async index (): Promise<Array<Product>> {
|
||||
const res: AxiosResponse<Array<Product>> = await apiClient.get('/api/products');
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async get (id: number): Promise<Product> {
|
||||
const res: AxiosResponse<Product> = await apiClient.get(`/api/products/${id}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async create (product: Product): Promise<Product> {
|
||||
const res: AxiosResponse<Product> = await apiClient.post('/api/products', { product });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async update (product: Product): Promise<Product> {
|
||||
const res: AxiosResponse<Product> = await apiClient.patch(`/api/products/${product.id}`, { product });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async destroy (productId: number): Promise<void> {
|
||||
const res: AxiosResponse<void> = await apiClient.delete(`/api/products/${productId}`);
|
||||
return res?.data;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import React from 'react';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { Product } from '../../models/product';
|
||||
|
||||
interface ProductsListProps {
|
||||
products: Array<Product>,
|
||||
onEdit: (product: Product) => void,
|
||||
onDelete: (productId: number) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows a list of all Products
|
||||
*/
|
||||
export const ProductsList: React.FC<ProductsListProps> = ({ products, onEdit, onDelete }) => {
|
||||
/**
|
||||
* Init the process of editing the given product
|
||||
*/
|
||||
const editProduct = (product: Product): () => void => {
|
||||
return (): void => {
|
||||
onEdit(product);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Init the process of delete the given product
|
||||
*/
|
||||
const deleteProduct = (productId: number): () => void => {
|
||||
return (): void => {
|
||||
onDelete(productId);
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{products.map((product) => (
|
||||
<div key={product.id}>
|
||||
{product.name}
|
||||
<div className="buttons">
|
||||
<FabButton className="edit-btn" onClick={editProduct(product)}>
|
||||
<i className="fa fa-edit" />
|
||||
</FabButton>
|
||||
<FabButton className="delete-btn" onClick={deleteProduct(product.id)}>
|
||||
<i className="fa fa-trash" />
|
||||
</FabButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
77
app/frontend/src/javascript/components/store/products.tsx
Normal file
77
app/frontend/src/javascript/components/store/products.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { Loader } from '../base/loader';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { ProductsList } from './products-list';
|
||||
import { Product } from '../../models/product';
|
||||
import ProductAPI from '../../api/product';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface ProductsProps {
|
||||
onSuccess: (message: string) => void,
|
||||
onError: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* This component shows all Products and filter
|
||||
*/
|
||||
const Products: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [products, setProducts] = useState<Array<Product>>([]);
|
||||
const [product, setProduct] = useState<Product>(null);
|
||||
|
||||
useEffect(() => {
|
||||
ProductAPI.index().then(data => {
|
||||
setProducts(data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Open edit the product modal
|
||||
*/
|
||||
const editProduct = (product: Product) => {
|
||||
setProduct(product);
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a product
|
||||
*/
|
||||
const deleteProduct = async (productId: number): Promise<void> => {
|
||||
try {
|
||||
await ProductAPI.destroy(productId);
|
||||
const data = await ProductAPI.index();
|
||||
setProducts(data);
|
||||
onSuccess(t('app.admin.store.products.successfully_deleted'));
|
||||
} catch (e) {
|
||||
onError(t('app.admin.store.products.unable_to_delete') + e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{t('app.admin.store.products.all_products')}</h2>
|
||||
<FabButton className="save">{t('app.admin.store.products.create_a_product')}</FabButton>
|
||||
<ProductsList
|
||||
products={products}
|
||||
onEdit={editProduct}
|
||||
onDelete={deleteProduct}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProductsWrapper: React.FC<ProductsProps> = ({ onSuccess, onError }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<Products onSuccess={onSuccess} onError={onError} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('products', react2angular(ProductsWrapper, ['onSuccess', 'onError']));
|
@ -4,9 +4,35 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
Application.Controllers.controller('AdminStoreController', ['$scope', 'CSRF', 'growl',
|
||||
function ($scope, CSRF, growl) {
|
||||
Application.Controllers.controller('AdminStoreController', ['$scope', 'CSRF', 'growl', '$state',
|
||||
function ($scope, CSRF, growl, $state) {
|
||||
/* PRIVATE SCOPE */
|
||||
// Map of tab state and index
|
||||
const TABS = {
|
||||
'app.admin.store.settings': 0,
|
||||
'app.admin.store.products': 1,
|
||||
'app.admin.store.categories': 2,
|
||||
'app.admin.store.orders': 3
|
||||
};
|
||||
|
||||
/* PUBLIC SCOPE */
|
||||
// default tab: products
|
||||
$scope.tabs = {
|
||||
active: TABS[$state.current.name]
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered in click tab
|
||||
*/
|
||||
$scope.selectTab = () => {
|
||||
setTimeout(function () {
|
||||
const currentTab = _.keys(TABS)[$scope.tabs.active];
|
||||
if (currentTab !== $state.current.name) {
|
||||
$state.go(currentTab, { location: true, notify: false, reload: false });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered in case of error
|
||||
*/
|
||||
|
@ -84,7 +84,7 @@ Application.Controllers.controller('MainNavController', ['$scope', 'settingsProm
|
||||
authorizedRoles: ['admin', 'manager']
|
||||
},
|
||||
{
|
||||
state: 'app.admin.store',
|
||||
state: 'app.admin.store.products',
|
||||
linkText: 'app.public.common.manage_the_store',
|
||||
linkIcon: 'cart-plus',
|
||||
authorizedRoles: ['admin', 'manager']
|
||||
|
24
app/frontend/src/javascript/models/product.ts
Normal file
24
app/frontend/src/javascript/models/product.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export enum StockType {
|
||||
internal = 'internal',
|
||||
external = 'external'
|
||||
}
|
||||
|
||||
export interface Stock {
|
||||
internal: number,
|
||||
external: number,
|
||||
}
|
||||
|
||||
export interface Product {
|
||||
id: number,
|
||||
name: string,
|
||||
slug: string,
|
||||
sku: string,
|
||||
description: string,
|
||||
is_active: boolean,
|
||||
product_category_id: number,
|
||||
amount: number,
|
||||
quantity_min: number,
|
||||
stock: Stock,
|
||||
low_stock_alert: boolean,
|
||||
low_stock_threshold: number,
|
||||
}
|
@ -1105,6 +1105,7 @@ angular.module('application.router', ['ui.router'])
|
||||
})
|
||||
|
||||
.state('app.admin.store', {
|
||||
abstract: true,
|
||||
url: '/admin/store',
|
||||
views: {
|
||||
'main@': {
|
||||
@ -1114,6 +1115,22 @@ angular.module('application.router', ['ui.router'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('app.admin.store.settings', {
|
||||
url: '/settings'
|
||||
})
|
||||
|
||||
.state('app.admin.store.products', {
|
||||
url: '/products'
|
||||
})
|
||||
|
||||
.state('app.admin.store.categories', {
|
||||
url: '/categories'
|
||||
})
|
||||
|
||||
.state('app.admin.store.orders', {
|
||||
url: '/orders'
|
||||
})
|
||||
|
||||
// OpenAPI Clients
|
||||
.state('app.admin.open_api_clients', {
|
||||
url: '/open_api_clients',
|
||||
|
@ -20,19 +20,19 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true" active="tabs.active">
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.store.settings' | translate }}" index="0">
|
||||
<uib-tab heading="{{ 'app.admin.store.settings' | translate }}" index="0" select="selectTab()">
|
||||
<ng-include src="'/admin/store/settings.html'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.store.all_products' | translate }}" index="1">
|
||||
<uib-tab heading="{{ 'app.admin.store.all_products' | translate }}" index="1" select="selectTab()">
|
||||
<ng-include src="'/admin/store/products.html'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.store.categories_of_store' | translate }}" index="2">
|
||||
<uib-tab heading="{{ 'app.admin.store.categories_of_store' | translate }}" index="2" select="selectTab()">
|
||||
<ng-include src="'/admin/store/categories.html'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.store.the_orders' | translate }}" index="3">
|
||||
<uib-tab heading="{{ 'app.admin.store.the_orders' | translate }}" index="3" select="selectTab()">
|
||||
<ng-include src="'/admin/store/orders.html'"></ng-include>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
@ -1 +1 @@
|
||||
<h2>Products page</h2>
|
||||
<products on-success="onSuccess" on-error="onError"/>
|
||||
|
@ -1925,4 +1925,9 @@ en:
|
||||
success: "The category has been successfully deleted"
|
||||
save: "Save"
|
||||
required: "This field is required"
|
||||
slug_pattern: "Only lowercase alphanumeric groups of characters separated by an hyphen"
|
||||
slug_pattern: "Only lowercase alphanumeric groups of characters separated by an hyphen"
|
||||
products:
|
||||
all_products: "All products"
|
||||
create_a_product: "Create a product"
|
||||
successfully_deleted: "The product has been successfully deleted"
|
||||
unable_to_delete: "Unable to delete the product: "
|
||||
|
@ -1925,4 +1925,9 @@ fr:
|
||||
success: "La catégorie a bien été supprimée"
|
||||
save: "Enregistrer"
|
||||
required: "This field is required"
|
||||
slug_pattern: "Only lowercase alphanumeric groups of characters separated by an hyphen"
|
||||
slug_pattern: "Only lowercase alphanumeric groups of characters separated by an hyphen"
|
||||
products:
|
||||
all_products: "Tous les produits"
|
||||
create_a_product: "Créer un produit"
|
||||
successfully_deleted: "Le produit a bien été supprimé"
|
||||
unable_to_delete: "Impossible de supprimer le produit: "
|
||||
|
Loading…
x
Reference in New Issue
Block a user