mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Add more formatters (#5771)
* Added general data formatters * Added usage of new date formatter * Added usage of new date formatter * Added usage of currency formatter * style cleanup * Moved to use real user and server settings * fixed type * Added in formatters again * cleaned up unsued imports
This commit is contained in:
parent
ea249c1dc5
commit
2d6a8a4bcc
@ -3,6 +3,7 @@
|
||||
*/
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { formatCurrency, renderDate } from '../../defaults/formatters';
|
||||
import { ProgressBar } from '../items/ProgressBar';
|
||||
import { ModelType } from '../render/ModelType';
|
||||
import { RenderOwner } from '../render/User';
|
||||
@ -77,8 +78,9 @@ export function TargetDateColumn(): TableColumn {
|
||||
return {
|
||||
accessor: 'target_date',
|
||||
title: t`Target Date`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
// TODO: custom renderer which alerts user if target date is overdue
|
||||
render: (record: any) => renderDate(record.target_date)
|
||||
};
|
||||
}
|
||||
|
||||
@ -86,7 +88,8 @@ export function CreationDateColumn(): TableColumn {
|
||||
return {
|
||||
accessor: 'creation_date',
|
||||
title: t`Creation Date`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
render: (record: any) => renderDate(record.creation_date)
|
||||
};
|
||||
}
|
||||
|
||||
@ -94,7 +97,8 @@ export function ShipmentDateColumn(): TableColumn {
|
||||
return {
|
||||
accessor: 'shipment_date',
|
||||
title: t`Shipment Date`,
|
||||
sortable: true
|
||||
sortable: true,
|
||||
render: (record: any) => renderDate(record.shipment_date)
|
||||
};
|
||||
}
|
||||
|
||||
@ -116,12 +120,10 @@ export function CurrencyColumn({
|
||||
title: title ?? t`Currency`,
|
||||
sortable: sortable ?? true,
|
||||
render: (record: any) => {
|
||||
let value = record[accessor];
|
||||
let currency_key = currency_accessor ?? `${accessor}_currency`;
|
||||
currency = currency ?? record[currency_key];
|
||||
|
||||
// TODO: A better render which correctly formats money values
|
||||
return `${value} ${currency}`;
|
||||
return formatCurrency(record[accessor], {
|
||||
currency: currency ?? record[currency_key]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { renderDate } from '../../../defaults/formatters';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { ThumbnailHoverCard } from '../../images/Thumbnail';
|
||||
@ -78,7 +79,8 @@ function buildOrderTableColumns(): TableColumn[] {
|
||||
{
|
||||
accessor: 'completion_date',
|
||||
sortable: true,
|
||||
title: t`Completed`
|
||||
title: t`Completed`,
|
||||
render: (record: any) => renderDate(record.completion_date)
|
||||
},
|
||||
{
|
||||
accessor: 'issued_by',
|
||||
|
@ -3,6 +3,7 @@ import { Group, Text } from '@mantine/core';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { formatCurrency, renderDate } from '../../../defaults/formatters';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
@ -167,13 +168,34 @@ function stockItemTableColumns(): TableColumn[] {
|
||||
// TODO: Note, if not "In stock" we don't want to display the actual location here
|
||||
return record?.location_detail?.pathstring ?? record.location ?? '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
// TODO: stocktake column
|
||||
// TODO: expiry date
|
||||
// TODO: last updated
|
||||
{
|
||||
accessor: 'expiry_date',
|
||||
sortable: true,
|
||||
title: t`Expiry Date`,
|
||||
switchable: true,
|
||||
render: (record: any) => renderDate(record.expiry_date)
|
||||
},
|
||||
{
|
||||
accessor: 'updated',
|
||||
sortable: true,
|
||||
title: t`Last Updated`,
|
||||
switchable: true,
|
||||
render: (record: any) => renderDate(record.updated)
|
||||
},
|
||||
// TODO: purchase order
|
||||
// TODO: Supplier part
|
||||
// TODO: purchase price
|
||||
{
|
||||
accessor: 'purchase_price',
|
||||
sortable: true,
|
||||
title: t`Purchase Price`,
|
||||
switchable: true,
|
||||
render: (record: any) =>
|
||||
formatCurrency(record.purchase_price, {
|
||||
currency: record.purchase_price_currency
|
||||
})
|
||||
}
|
||||
// TODO: stock value
|
||||
// TODO: packaging
|
||||
// TODO: notes
|
||||
|
86
src/frontend/src/defaults/formatters.tsx
Normal file
86
src/frontend/src/defaults/formatters.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import {
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../states/SettingsState';
|
||||
|
||||
interface formatCurrencyOptionsType {
|
||||
digits?: number;
|
||||
minDigits?: number;
|
||||
currency?: string;
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* format currency (money) value based on current settings
|
||||
*
|
||||
* Options:
|
||||
* - currency: Currency code (uses default value if none provided)
|
||||
* - locale: Locale specified (uses default value if none provided)
|
||||
* - digits: Maximum number of significant digits (default = 10)
|
||||
*/
|
||||
export function formatCurrency(
|
||||
value: number,
|
||||
options: formatCurrencyOptionsType = {}
|
||||
) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const global_settings = useGlobalSettingsState.getState().lookup;
|
||||
|
||||
let maxDigits = options.digits || global_settings.PRICING_DECIMAL_PLACES || 6;
|
||||
maxDigits = Number(maxDigits);
|
||||
let minDigits =
|
||||
options.minDigits || global_settings.PRICING_DECIMAL_PLACES_MIN || 0;
|
||||
minDigits = Number(minDigits);
|
||||
|
||||
// Extract default currency information
|
||||
let currency =
|
||||
options.currency || global_settings.INVENTREE_DEFAULT_CURRENCY || 'USD';
|
||||
|
||||
// Extract locale information
|
||||
let locale = options.locale || navigator.language || 'en-US';
|
||||
|
||||
let formatter = new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
maximumFractionDigits: maxDigits,
|
||||
minimumFractionDigits: minDigits
|
||||
});
|
||||
|
||||
return formatter.format(value);
|
||||
}
|
||||
|
||||
interface renderDateOptionsType {
|
||||
showTime?: boolean;
|
||||
}
|
||||
|
||||
/*
|
||||
* Render the provided date in the user-specified format.
|
||||
*
|
||||
* The provided "date" variable is a string, nominally ISO format e.g. 2022-02-22
|
||||
* The user-configured setting DATE_DISPLAY_FORMAT determines how the date should be displayed.
|
||||
*/
|
||||
export function renderDate(date: string, options: renderDateOptionsType = {}) {
|
||||
if (!date) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const user_settings = useUserSettingsState.getState().lookup;
|
||||
let fmt = user_settings.DATE_DISPLAY_FORMAT || 'YYYY-MM-DD';
|
||||
|
||||
if (options.showTime) {
|
||||
fmt += ' HH:mm';
|
||||
}
|
||||
|
||||
const m = dayjs(date);
|
||||
|
||||
if (m.isValid()) {
|
||||
return m.format(fmt);
|
||||
} else {
|
||||
// Invalid input string, simply return provided value
|
||||
return date;
|
||||
}
|
||||
}
|
@ -5,10 +5,11 @@ import { create } from 'zustand';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiPaths, apiUrl } from './ApiState';
|
||||
import { Setting } from './states';
|
||||
import { Setting, SettingsLookup } from './states';
|
||||
|
||||
export interface SettingsStateProps {
|
||||
settings: Setting[];
|
||||
lookup: SettingsLookup;
|
||||
fetchSettings: () => void;
|
||||
endpoint: ApiPaths;
|
||||
}
|
||||
@ -19,12 +20,16 @@ export interface SettingsStateProps {
|
||||
export const useGlobalSettingsState = create<SettingsStateProps>(
|
||||
(set, get) => ({
|
||||
settings: [],
|
||||
lookup: {},
|
||||
endpoint: ApiPaths.settings_global_list,
|
||||
fetchSettings: async () => {
|
||||
await api
|
||||
.get(apiUrl(ApiPaths.settings_global_list))
|
||||
.then((response) => {
|
||||
set({ settings: response.data });
|
||||
set({
|
||||
settings: response.data,
|
||||
lookup: generate_lookup(response.data)
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching global settings:', error);
|
||||
@ -38,15 +43,30 @@ export const useGlobalSettingsState = create<SettingsStateProps>(
|
||||
*/
|
||||
export const useUserSettingsState = create<SettingsStateProps>((set, get) => ({
|
||||
settings: [],
|
||||
lookup: {},
|
||||
endpoint: ApiPaths.settings_user_list,
|
||||
fetchSettings: async () => {
|
||||
await api
|
||||
.get(apiUrl(ApiPaths.settings_user_list))
|
||||
.then((response) => {
|
||||
set({ settings: response.data });
|
||||
set({
|
||||
settings: response.data,
|
||||
lookup: generate_lookup(response.data)
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching user settings:', error);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
/*
|
||||
return a lookup dictionary for the value of the provided Setting list
|
||||
*/
|
||||
function generate_lookup(data: Setting[]) {
|
||||
let lookup_dir: SettingsLookup = {};
|
||||
for (let setting of data) {
|
||||
lookup_dir[setting.key] = setting.value;
|
||||
}
|
||||
return lookup_dir;
|
||||
}
|
||||
|
@ -87,3 +87,6 @@ export type ErrorResponse = {
|
||||
statusText: string;
|
||||
message?: string;
|
||||
};
|
||||
export type SettingsLookup = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user