katoikia-app/web-ui/web-react/src/templates/TableDemo.js

757 lines
21 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { classNames } from 'primereact/utils';
import { FilterMatchMode, FilterOperator } from 'primereact/api';
import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
import { Dropdown } from 'primereact/dropdown';
import { InputNumber } from 'primereact/inputnumber';
import { Button } from 'primereact/button';
import { ProgressBar } from 'primereact/progressbar';
import { Calendar } from 'primereact/calendar';
import { MultiSelect } from 'primereact/multiselect';
import { Slider } from 'primereact/slider';
import { TriStateCheckbox } from 'primereact/tristatecheckbox';
import { ToggleButton } from 'primereact/togglebutton';
import { Rating } from 'primereact/rating';
import { CustomerService } from '../service/CustomerService';
import { ProductService } from '../service/ProductService';
const TableDemo = () => {
const [customers1, setCustomers1] = useState(null);
const [customers2, setCustomers2] = useState([]);
const [customers3, setCustomers3] = useState([]);
const [filters1, setFilters1] = useState(null);
const [loading1, setLoading1] = useState(true);
const [loading2, setLoading2] = useState(true);
const [idFrozen, setIdFrozen] = useState(false);
const [products, setProducts] = useState([]);
const [expandedRows, setExpandedRows] = useState(null);
const representatives = [
{ name: 'Amy Elsner', image: 'amyelsner.png' },
{ name: 'Anna Fali', image: 'annafali.png' },
{ name: 'Asiya Javayant', image: 'asiyajavayant.png' },
{ name: 'Bernardo Dominic', image: 'bernardodominic.png' },
{ name: 'Elwin Sharvill', image: 'elwinsharvill.png' },
{ name: 'Ioni Bowcher', image: 'ionibowcher.png' },
{ name: 'Ivan Magalhaes', image: 'ivanmagalhaes.png' },
{ name: 'Onyama Limba', image: 'onyamalimba.png' },
{ name: 'Stephen Shaw', image: 'stephenshaw.png' },
{ name: 'XuXue Feng', image: 'xuxuefeng.png' },
];
const statuses = [
'unqualified',
'qualified',
'new',
'negotiation',
'renewal',
'proposal',
];
const customerService = new CustomerService();
const productService = new ProductService();
useEffect(() => {
setLoading2(true);
customerService.getCustomersLarge().then((data) => {
setCustomers1(getCustomers(data));
setLoading1(false);
});
customerService.getCustomersLarge().then((data) => {
setCustomers2(getCustomers(data));
setLoading2(false);
});
customerService.getCustomersMedium().then((data) => setCustomers3(data));
productService
.getProductsWithOrdersSmall()
.then((data) => setProducts(data));
initFilters1();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const balanceTemplate = (rowData) => {
return <span className="text-bold">{formatCurrency(rowData.balance)}</span>;
};
const getCustomers = (data) => {
return [...(data || [])].map((d) => {
d.date = new Date(d.date);
return d;
});
};
const formatDate = (value) => {
return value.toLocaleDateString('en-US', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
});
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
});
};
const initFilters1 = () => {
setFilters1({
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }],
},
'country.name': {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.STARTS_WITH }],
},
representative: { value: null, matchMode: FilterMatchMode.IN },
date: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.DATE_IS }],
},
balance: {
operator: FilterOperator.AND,
constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }],
},
status: {
operator: FilterOperator.OR,
constraints: [{ value: null, matchMode: FilterMatchMode.EQUALS }],
},
activity: { value: null, matchMode: FilterMatchMode.BETWEEN },
verified: { value: null, matchMode: FilterMatchMode.EQUALS },
});
};
const countryBodyTemplate = (rowData) => {
return (
<React.Fragment>
<img
alt="flag"
src="assets/demo/images/flags/flag_placeholder.png"
className={`flag flag-${rowData.country.code}`}
width={30}
/>
<span
style={{ marginLeft: '.5em', verticalAlign: 'middle' }}
className="image-text"
>
{rowData.country.name}
</span>
</React.Fragment>
);
};
const filterClearTemplate = (options) => {
return (
<Button
type="button"
icon="pi pi-times"
onClick={options.filterClearCallback}
className="p-button-secondary"
></Button>
);
};
const filterApplyTemplate = (options) => {
return (
<Button
type="button"
icon="pi pi-check"
onClick={options.filterApplyCallback}
className="p-button-success"
></Button>
);
};
const representativeBodyTemplate = (rowData) => {
const representative = rowData.representative;
return (
<React.Fragment>
<img
alt={representative.name}
src={`images/avatar/${representative.image}`}
onError={(e) =>
(e.target.src =
'https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png')
}
width={32}
style={{ verticalAlign: 'middle' }}
/>
<span
style={{ marginLeft: '.5em', verticalAlign: 'middle' }}
className="image-text"
>
{representative.name}
</span>
</React.Fragment>
);
};
const representativeFilterTemplate = (options) => {
return (
<>
<div className="mb-3 text-bold">Agent Picker</div>
<MultiSelect
value={options.value}
options={representatives}
itemTemplate={representativesItemTemplate}
onChange={(e) => options.filterCallback(e.value)}
optionLabel="name"
placeholder="Any"
className="p-column-filter"
/>
</>
);
};
const representativesItemTemplate = (option) => {
return (
<div className="p-multiselect-representative-option">
<img
alt={option.name}
src={`assets/demo/images/avatar/${option.image}`}
width={32}
style={{ verticalAlign: 'middle' }}
/>
<span
style={{ marginLeft: '.5em', verticalAlign: 'middle' }}
className="image-text"
>
{option.name}
</span>
</div>
);
};
const dateBodyTemplate = (rowData) => {
return formatDate(rowData.date);
};
const dateFilterTemplate = (options) => {
return (
<Calendar
value={options.value}
onChange={(e) => options.filterCallback(e.value, options.index)}
dateFormat="mm/dd/yy"
placeholder="mm/dd/yyyy"
mask="99/99/9999"
/>
);
};
const balanceBodyTemplate = (rowData) => {
return formatCurrency(rowData.balance);
};
const balanceFilterTemplate = (options) => {
return (
<InputNumber
value={options.value}
onChange={(e) => options.filterCallback(e.value, options.index)}
mode="currency"
currency="USD"
locale="en-US"
/>
);
};
const statusBodyTemplate = (rowData) => {
return (
<span className={`customer-badge status-${rowData.status}`}>
{rowData.status}
</span>
);
};
const statusFilterTemplate = (options) => {
return (
<Dropdown
value={options.value}
options={statuses}
onChange={(e) => options.filterCallback(e.value, options.index)}
itemTemplate={statusItemTemplate}
placeholder="Select a Status"
className="p-column-filter"
showClear
/>
);
};
const statusItemTemplate = (option) => {
return <span className={`customer-badge status-${option}`}>{option}</span>;
};
const activityBodyTemplate = (rowData) => {
return (
<ProgressBar
value={rowData.activity}
showValue={false}
style={{ height: '.5rem' }}
></ProgressBar>
);
};
const activityFilterTemplate = (options) => {
return (
<React.Fragment>
<Slider
value={options.value}
onChange={(e) => options.filterCallback(e.value)}
range
className="m-3"
></Slider>
<div className="flex align-items-center justify-content-between px-2">
<span>{options.value ? options.value[0] : 0}</span>
<span>{options.value ? options.value[1] : 100}</span>
</div>
</React.Fragment>
);
};
const verifiedBodyTemplate = (rowData) => {
return (
<i
className={classNames('pi', {
'text-green-500 pi-check-circle': rowData.verified,
'text-pink-500 pi-times-circle': !rowData.verified,
})}
></i>
);
};
const verifiedFilterTemplate = (options) => {
return (
<TriStateCheckbox
value={options.value}
onChange={(e) => options.filterCallback(e.value)}
/>
);
};
const expandAll = () => {
let _expandedRows = {};
products.forEach((p) => (_expandedRows[`${p.id}`] = true));
setExpandedRows(_expandedRows);
};
const collapseAll = () => {
setExpandedRows(null);
};
const amountBodyTemplate = (rowData) => {
return formatCurrency(rowData.amount);
};
const statusOrderBodyTemplate = (rowData) => {
return (
<span className={`order-badge order-${rowData.status.toLowerCase()}`}>
{rowData.status}
</span>
);
};
const searchBodyTemplate = () => {
return <Button icon="pi pi-search" />;
};
const imageBodyTemplate = (rowData) => {
return (
<img
src={`images/product/${rowData.image}`}
onError={(e) =>
(e.target.src =
'https://www.primefaces.org/wp-content/uploads/2020/05/placeholder.png')
}
alt={rowData.image}
className="shadow-2"
width={100}
/>
);
};
const priceBodyTemplate = (rowData) => {
return formatCurrency(rowData.price);
};
const ratingBodyTemplate = (rowData) => {
return <Rating value={rowData.rating} readOnly cancel={false} />;
};
const statusBodyTemplate2 = (rowData) => {
return (
<span
className={`product-badge status-${rowData.inventoryStatus.toLowerCase()}`}
>
{rowData.inventoryStatus}
</span>
);
};
const rowExpansionTemplate = (data) => {
return (
<div className="orders-subtable">
<h5>Orders for {data.name}</h5>
<DataTable value={data.orders} responsiveLayout="scroll">
<Column field="id" header="Id" sortable></Column>
<Column field="customer" header="Customer" sortable></Column>
<Column field="date" header="Date" sortable></Column>
<Column
field="amount"
header="Amount"
body={amountBodyTemplate}
sortable
></Column>
<Column
field="status"
header="Status"
body={statusOrderBodyTemplate}
sortable
></Column>
<Column
headerStyle={{ width: '4rem' }}
body={searchBodyTemplate}
></Column>
</DataTable>
</div>
);
};
const header = (
<div className="table-header-container">
<Button
icon="pi pi-plus"
label="Expand All"
onClick={expandAll}
className="mr-2 mb-2"
/>
<Button
icon="pi pi-minus"
label="Collapse All"
onClick={collapseAll}
className="mb-2"
/>
</div>
);
const headerTemplate = (data) => {
return (
<React.Fragment>
<img
alt={data.representative.name}
src={`images/avatar/${data.representative.image}`}
width="32"
style={{ verticalAlign: 'middle' }}
/>
<span className="image-text font-bold">{data.representative.name}</span>
</React.Fragment>
);
};
const footerTemplate = (data) => {
return (
<React.Fragment>
<td
colSpan="4"
style={{ textAlign: 'right' }}
className="text-bold pr-6"
>
Total Customers
</td>
<td>{calculateCustomerTotal(data.representative.name)}</td>
</React.Fragment>
);
};
const calculateCustomerTotal = (name) => {
let total = 0;
if (customers3) {
for (let customer of customers3) {
if (customer.representative.name === name) {
total++;
}
}
}
return total;
};
return (
<div className="grid table-demo">
<div className="col-12">
<div className="card">
<h5>Filter Menu</h5>
<DataTable
value={customers1}
paginator
className="p-datatable-gridlines"
showGridlines
rows={10}
dataKey="id"
filters={filters1}
filterDisplay="menu"
loading={loading1}
responsiveLayout="scroll"
emptyMessage="No customers found."
>
<Column
field="name"
header="Name"
filter
filterPlaceholder="Search by name"
style={{ minWidth: '12rem' }}
/>
<Column
header="Country"
filterField="country.name"
style={{ minWidth: '12rem' }}
body={countryBodyTemplate}
filter
filterPlaceholder="Search by country"
filterClear={filterClearTemplate}
filterApply={filterApplyTemplate}
/>
<Column
header="Agent"
filterField="representative"
showFilterMatchModes={false}
filterMenuStyle={{ width: '14rem' }}
style={{ minWidth: '14rem' }}
body={representativeBodyTemplate}
filter
filterElement={representativeFilterTemplate}
/>
<Column
header="Date"
filterField="date"
dataType="date"
style={{ minWidth: '10rem' }}
body={dateBodyTemplate}
filter
filterElement={dateFilterTemplate}
/>
<Column
header="Balance"
filterField="balance"
dataType="numeric"
style={{ minWidth: '10rem' }}
body={balanceBodyTemplate}
filter
filterElement={balanceFilterTemplate}
/>
<Column
field="status"
header="Status"
filterMenuStyle={{ width: '14rem' }}
style={{ minWidth: '12rem' }}
body={statusBodyTemplate}
filter
filterElement={statusFilterTemplate}
/>
<Column
field="activity"
header="Activity"
showFilterMatchModes={false}
style={{ minWidth: '12rem' }}
body={activityBodyTemplate}
filter
filterElement={activityFilterTemplate}
/>
<Column
field="verified"
header="Verified"
dataType="boolean"
bodyClassName="text-center"
style={{ minWidth: '8rem' }}
body={verifiedBodyTemplate}
filter
filterElement={verifiedFilterTemplate}
/>
</DataTable>
</div>
</div>
<div className="col-12">
<div className="card">
<h5>Frozen Columns</h5>
<ToggleButton
checked={idFrozen}
onChange={(e) => setIdFrozen(e.value)}
onIcon="pi pi-lock"
offIcon="pi pi-lock-open"
onLabel="Unfreeze Id"
offLabel="Freeze Id"
style={{ width: '10rem' }}
/>
<DataTable
value={customers2}
scrollable
scrollHeight="400px"
loading={loading2}
scrollDirection="both"
className="mt-3"
>
<Column
field="name"
header="Name"
style={{ flexGrow: 1, flexBasis: '160px' }}
frozen
></Column>
<Column
field="id"
header="Id"
style={{ flexGrow: 1, flexBasis: '100px' }}
frozen={idFrozen}
alignFrozen="left"
></Column>
<Column
field="name"
header="Name"
style={{ flexGrow: 1, flexBasis: '200px' }}
></Column>
<Column
field="country.name"
header="Country"
style={{ flexGrow: 1, flexBasis: '200px' }}
body={countryBodyTemplate}
></Column>
<Column
field="date"
header="Date"
style={{ flexGrow: 1, flexBasis: '200px' }}
body={dateBodyTemplate}
></Column>
<Column
field="company"
header="Company"
style={{ flexGrow: 1, flexBasis: '200px' }}
></Column>
<Column
field="status"
header="Status"
style={{ flexGrow: 1, flexBasis: '200px' }}
body={statusBodyTemplate}
></Column>
<Column
field="activity"
header="Activity"
style={{ flexGrow: 1, flexBasis: '200px' }}
></Column>
<Column
field="representative.name"
header="Representative"
style={{ flexGrow: 1, flexBasis: '200px' }}
body={representativeBodyTemplate}
></Column>
<Column
field="balance"
header="Balance"
body={balanceTemplate}
frozen
style={{ flexGrow: 1, flexBasis: '120px' }}
alignFrozen="right"
></Column>
</DataTable>
</div>
</div>
<div className="col-12">
<div className="card">
<h5>Row Expand</h5>
<DataTable
value={products}
expandedRows={expandedRows}
onRowToggle={(e) => setExpandedRows(e.data)}
responsiveLayout="scroll"
rowExpansionTemplate={rowExpansionTemplate}
dataKey="id"
header={header}
>
<Column expander style={{ width: '3em' }} />
<Column field="name" header="Name" sortable />
<Column header="Image" body={imageBodyTemplate} />
<Column
field="price"
header="Price"
sortable
body={priceBodyTemplate}
/>
<Column field="category" header="Category" sortable />
<Column
field="rating"
header="Reviews"
sortable
body={ratingBodyTemplate}
/>
<Column
field="inventoryStatus"
header="Status"
sortable
body={statusBodyTemplate2}
/>
</DataTable>
</div>
</div>
<div className="col-12">
<div className="card">
<h5>Subheader Grouping</h5>
<DataTable
value={customers3}
rowGroupMode="subheader"
groupRowsBy="representative.name"
sortMode="single"
sortField="representative.name"
sortOrder={1}
scrollable
scrollHeight="400px"
rowGroupHeaderTemplate={headerTemplate}
rowGroupFooterTemplate={footerTemplate}
responsiveLayout="scroll"
>
<Column
field="name"
header="Name"
style={{ minWidth: '200px' }}
></Column>
<Column
field="country"
header="Country"
body={countryBodyTemplate}
style={{ minWidth: '200px' }}
></Column>
<Column
field="company"
header="Company"
style={{ minWidth: '200px' }}
></Column>
<Column
field="status"
header="Status"
body={statusBodyTemplate}
style={{ minWidth: '200px' }}
></Column>
<Column
field="date"
header="Date"
style={{ minWidth: '200px' }}
></Column>
</DataTable>
</div>
</div>
</div>
);
};
const comparisonFn = function (prevProps, nextProps) {
return prevProps.location.pathname === nextProps.location.pathname;
};
export default React.memo(TableDemo, comparisonFn);