tunakit
v1.0.3
Published
<img src="./TunaKIt-transparent.png" width="500">
Downloads
373
Readme
TunaKit - UI Component Library for CF
TunaKit is a collection of reusable components that can be used to build web applications.
Installation
npm install tunakit
Peer Dependencies
The following dependencies are required to be installed by the importing application:
- "@ant-design/icons": "^4.8.0"
- "antd": "^5.14.0"
- "axios": "^1.2.1"
- "react": "^18.2.0"
- "react-dom": "^18.2.0"
- "react-router-dom": "^6.4.3"
- "swr": "^2.0.0"
Components
NotAvailable
Usage
import { NotAvailable } from 'tunakit';
const MyComponent = () => {
return (
<NotAvailable />
);
}
Image
HighlightedTag
Props
- status (Required): The text to look for in status map.
- statusMap (Required): An object that maps the status to the corresponding label and color.
Usage
import { HighlightedTag } from 'tunakit';
const MyComponent = () => {
const statusMap: TagColumnData = {
APPROVED: {
value: 'Approved',
label: 'APPROVED',
color: 'green',
},
PENDING: {
value: 'Pending',
label: 'PENDING',
color: 'orange',
},
}
return (
<HighlightedTag status="APPROVED" statusMap={statusMap} />
);
}
Image
PageHeader
Props
- title (Required: string): The title of the page.
- onBack (Optional: () => void): A function called when the back button is clicked.
- rightContent (Optional: ReactNode): The content to be displayed on the right side of the header.
Usage
import { PageHeader } from 'tunakit';
const MyComponent = () => {
return (
<PageHeader title="Factories" onBack={() => navigate(-1)}
rightContent={<Button type={'primary'}>Add Factory</Button>} />
);
}
Image
FacilitySelect
Props
- includeAll (Required: boolean): Allows "All Facilities" option should in the dropdown.
- value (Optional: string): The value of the selected facility.
- onChange (Optional: (value: string) => void): A function called when the selected facility changes.
- initialSearch (Optional: string): The initial search value for the facility search.
- loading (Optional: boolean): A flag to indicate if the facility data is loading.
- userFacilities (Optional: UserFacility[]): The list of facilities.
Usage
import { FacilitySelect } from "tunakit";
const MyComponent = () => {
return (
<FacilitySelect
includeAll={false}
value={"Facility 1"}
onChange={(value) => console.log(value)}
initialSearch={"Facility 1"}
loading={false}
userFacilities={[
{ facility: { id: "1", name: "Facility 1" } },
{ facility: { id: "2", name: "Facility 2" } }
]} />
);
};
Image
FilterComponent
Props
- enabled (Optional: Array<'piNumber' | 'createdAt' | 'createdBy' | 'id' | 'name' | 'status'>): The list of predefined filters to enable. Allowed values: (id, name, piNumber, createdAt, createdBy, status).
- statusOptions (Optional: Array): List of status options to enable the status filter.
- customFilters (Optional: Array): List of filters (cf-filter-component supported format) to enable which aren't provided by default.
- setClearAllFiltersFn (Optional: Dispatch<SetStateAction>): setState method from the useState hook. Allows to clear all the selected filters.
Usage
import { FilterComponent } from 'tunakit';
const MyComponent = () => {
return (
<FilterComponent
enabled={['name', 'piNumber']}
statusOptions={[
{ label: 'Approved', value: 'APPROVED' },
{ label: 'Pending', value: 'PENDING' },
]}
customFilters={[
{
label: 'Custom Filter',
id: 'customFilter',
type: 'text',
placeholder: 'Enter custom filter',
},
]}
/>
);
}
Image
ListViewPage
Props
- title (Required: string): The title of the page.
- showFacilityFilter (Required: boolean): Set to true to show the facility filter.
- filters (Optional: FilterComponentProps): The
FilterComponent
props. - dataFetcher (Required: ApiFn): The function to fetch the data which returns a SF api response and accepts SF search api params.
- order (Required: Array): The order in which the table columns appear. Values must be api response keys. Partially providing keys will ensure order for those keys and the rest will be appended in the order they appear in the api response.
- action (Optional: ReactElement): React Component to be rendered in the action column of the table. The component will
have data of the table row in the props
record
. - excludeColumns (Optional: Set): The list of columns not to be shown in the table.
- includeColumns (Optional: Array): The list of columns to be shown in the table. Only the provided
columns will be displayed in the table. For a user name column, set type to
userInfo
, for a date column, set type todate
and for a tag type column, set type totag
along with atagMap
. To display the row value, just provide the api response key. - customColumns (Optional: ColumnsType): Antd table columns type, will be added as is to the table. Suitable for custom rendering logic.
- header (Optional: ReactElement): React Component to be rendered in the header of the table.
- emptyText (Optional: string): The text to be displayed when there is no data.
- setRefetch (Optional: Dispatch<SetStateAction>): setState method from the useState hook. Allows to refetch the data.
- fixedFilters (Optional: Array): The list of fixed filters to be applied to the search api.
- fetchUserFacilities (Optional: 'context' | 'api'): The method to fetch the user facilities. 'context' fetches the facilities from the context and 'api' fetches the facilities from the api. Default is 'context'.
- includeAllFacilities (Optional: boolean): Set to true to include all facilities in the facility filter. Default is true.
- fetchSize (Optional: number): The number of records to fetch in a single api call. Default is 20.
- onBack (Optional: () => void): A function called when the back button is clicked in page header. Back button is shown only if this prop is provided.
- setTableData (Optional: Dispatch<SetStateAction>): setState method from the useState hook. Allows to set the current visible table data.
Usage
import { ListViewPage } from 'tunakit';
import { useState } from "react";
export const EditButton = ({ record }: any) => {
return <Button type={"primary"} onClick={() => console.log({ record })}>Edit</Button>
}
const MyComponent = () => {
const [refetch, setRefetch] = useState(null);
const [data, setData] = useState(null);
const navigate = useNavigate();
return (
<ListViewPage
title={"Factory Material Trace"}
showFacilityFilter={true}
filters={{
enabled: ['id', 'name'],
statusOptions: [
{ label: 'Approved', value: 'APPROVED' },
{ label: 'Pending', value: 'PENDING' },
],
customFilters: [
{
label: 'Custom Filter',
id: 'customFilter',
type: 'text',
placeholder: 'Enter custom filter',
},
],
}}
dataFetcher={getFactoryMaterialTraceTest}
order={['created_by']}
action={<EditButton />}
excludeColumns={new Set(["id", "deleted_at", "deleted_by"])}
includeColumns={[
"updated_by",
{ key: "created_by", type: "userInfo", title: 'Created By User' },
{ key: "created_at", type: "date" },
{ key: "updated_at", type: "datetime" },
{ key: "facility_id", type: "facility" }, // either of showFacilityFilter or fetchUserFacilities must be provided
{ key: "location_code", type: "text", title: 'Zone Code' },
{
key: "step_code",
type: "tag",
tagMap: {
INWARD: {
value: "INWARD",
label: "INWARD",
color: "blue",
},
GRADING: {
value: "GRADING",
label: "GRADING",
color: "green",
},
ALLOCATION: {
value: "ALLOCATION",
label: "ALLOCATION",
color: "geekblue",
},
PACKAGING: {
value: "PACKAGING",
label: "PACKAGING",
color: "gold",
},
BY_PRODUCT: {
value: "BY_PRODUCT",
label: "BY PRODUCT",
color: "purple",
},
},
}]}
customColumns={[
{
title: 'Custom Column',
dataIndex: 'customColumn',
render: (_, record) => <span>{record.id}</span>,
},
]}
header={<Row justify={'end'}><Button type={'primary'}>Export Data</Button></Row>}
emptyText={"No data found"}
setRefetch={setRefetch}
fixedFilters={[
{ field: 'status', operator: 'eq', value: 'APPROVED' },
]}
fetchUserFacilities={'api'}
includeAllFacilities={true}
fetchSize={10}
onBack={() => navigate(-1)}
setTableData={setData}
/>
);
}
Image
Box
Props
- children (Optional: React.ReactNode): React Element to be render inside the box.
- h (Optional: number): height of the box
- w (Optional: number) width of the box
Usage
import { Box } from "tunakit";
const MyComponent = () => {
return <>
<Box h={10} />
</>;
};
Approval Button Group
Props
- onApprove (Optional: () => void): A function called when the approve button is clicked.
- onReject (Optional: () => void): A function called when the reject button is clicked.
- justify (Optional: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'): The justify property of the button group. Default is 'end'.
Usage
import { ApprovalButtonGroup } from 'tunakit';
const MyComponent = () => {
return (
<ApprovalButtonGroup justify={'end'} onApprove={() => console.log('Approved')}
onReject={() => console.log('Rejected')} />
);
}
Image
DataTable
A subset of ListViewPage component where the table rendering is extracted out.
Props
dataFetcher: ApiFn;
order: Array<string>;
userFacilities?: Array<UserFacility>
action?: ReactElement;
excludeColumns?: Set<string>;
includeColumns?: Array<IncludeColumnType>;
customColumns?: ColumnsType;
emptyText: string;
setRefetch?: Dispatch<SetStateAction<any>>; // setState method of useState
fixedFilters?: Array<SearchApiSearchParamsFilter>;
fetchSize?: number;
- dataFetcher (Required: ApiFn): The function to fetch the data which returns a SF api response and accepts SF search api params.
- order (Required: Array): The order in which the table columns appear. Values must be api response keys. Partially providing keys will ensure order for those keys and the rest will be appended in the order they appear in the api response.
- userFacilities (Optional: Array): Array of facilities user is mapped to. Used to render facility name in the DataTable.
- action (Optional: ReactElement): React Component to be rendered in the action column of the table. The component will
have data of the table row in the props
record
. - excludeColumns (Optional: Set): The list of columns not to be shown in the table.
- includeColumns (Optional: Array): The list of columns to be shown in the table. Only the provided
columns will be displayed in the table. For a user name column, set type to
userInfo
, for a date column, set type todate
and for a tag type column, set type totag
along with atagMap
. To display the row value, just provide the api response key. - customColumns (Optional: ColumnsType): Antd table columns type, will be added as is to the table. Suitable for custom rendering logic.
- emptyText (Optional: string): The text to be displayed when there is no data.
- setRefetch (Optional: Dispatch<SetStateAction>): setState method from the useState hook. Allows to refetch the data.
- fixedFilters (Optional: Array): The list of fixed filters to be applied to the search api.
- fetchSize (Optional: number): The number of records to fetch in a single api call. Default is 20.
Usage
import { DataTable } from 'tunakit';
import { useState } from "react";
export const EditButton = ({ record }: any) => {
return <Button type={"primary"} onClick={() => console.log({ record })}>Edit</Button>
}
const MyComponent = () => {
const [refetch, setRefetch] = useState(null);
const [data, setData] = useState(null);
const navigate = useNavigate();
return (
<DataTable
dataFetcher={getFactoryMaterialTraceTest}
order={['created_by']}
userFacilities={[]} // fetched from api or from context, depends on caller
action={<EditButton />}
excludeColumns={new Set(["id", "deleted_at", "deleted_by"])}
includeColumns={[
"updated_by",
{ key: "created_by", type: "userInfo", title: 'Created By User' },
{ key: "created_at", type: "date" },
{ key: "updated_at", type: "datetime" },
{ key: "facility_id", type: "facility" }, // either of showFacilityFilter or fetchUserFacilities must be provided
{ key: "location_code", type: "text", title: 'Zone Code' },
{
key: "step_code",
type: "tag",
tagMap: {
INWARD: {
value: "INWARD",
label: "INWARD",
color: "blue",
},
GRADING: {
value: "GRADING",
label: "GRADING",
color: "green",
},
ALLOCATION: {
value: "ALLOCATION",
label: "ALLOCATION",
color: "geekblue",
},
PACKAGING: {
value: "PACKAGING",
label: "PACKAGING",
color: "gold",
},
BY_PRODUCT: {
value: "BY_PRODUCT",
label: "BY PRODUCT",
color: "purple",
},
},
}]}
customColumns={[
{
title: 'Custom Column',
dataIndex: 'customColumn',
render: (_, record) => <span>{record.id}</span>,
},
]}
emptyText={"No data found"}
setRefetch={setRefetch}
fixedFilters={[
{ field: 'status', operator: 'eq', value: 'APPROVED' },
]}
fetchSize={10}
/>
);
}
Image
DividerHeader
Props
- title (Required: string): The title of the header.
- titleStyle (Optional: React.CSSProperties): The css style of the title.
- suffix (Optional: string): The suffix of the header.
- suffixStyle (Optional: React.CSSProperties): The css style of the suffix.
- rightContent (Optional: { element: React.ReactNode, leftContentSpan: number, rightContentSpan: number }): The content to be displayed on the right side of the header. element is the react element to render. Use left and right content span to adjust the look of the divider line.
Usage
import { DividerHeader } from "tunakit";
const MyComponent = () => {
return <>
<DividerHeader title={'Remarks'} suffix={' *'} suffixStyle={{ color: 'red' }} rightContent={{
element: <Button type={'primary'}>Submit</Button>,
leftContentSpan: 21,
rightContentSpan: 3
}} />
</>;
};
Image
Without Right Content
With Right Content
UploadDocumentModal
Props
header: string;
isOpen: boolean;
onCancel: () => void;
onComplete: (data: UploadFile<any>) => void;
onError: (data: UploadFile<any>) => void;
action: string;
beforeUpload?: (file: RcFile) => Promise<boolean>;
data?: string;
acceptedFileExtensions?: Array<string>;
maxFiles?: number;
- header (Required: string): The title of the modal.
- isOpen (Required: boolean): The flag to show/hide the modal.
- onCancel (Required: () => void): Callback function called when the modal is closed.
- onComplete (Required: (data: UploadFile) => void): Callback function called when the file upload is successful with the uploaded file data.
- onError (Required: (data: UploadFile) => void): Callback function called when the file upload fails with the file upload error data.
- action (Required: string): The POST api endpoint on which the file must be uploaded.
- beforeUpload (Optional: (file: RcFile) => Promise): A function that is called before the file is uploaded. If the function returns false, the file upload is cancelled. Perform any neccessary validations here.
- data (Optional: string): File upload template which will be downloaded as a file. Passing this prop enables
the
Download Template
button. - acceptedFileExtensions (Optional: Array): The list of file extensions that are allowed to be uploaded.
- maxFiles (Optional: number): The maximum number of files that can be uploaded.
Usage
import { UploadDocumentModal } from "tunakit";
import templateCsv from "./templateCsv.csv";
const MyComponent = () => {
const [isOpen, setIsOpen] = useState(false);
const onError = (data: UploadFile<any>) => {
console.log('Error Uploading file', data);
};
const beforeUpload = async (file: RcFile) => {
console.log('Validating file', file);
return true;
}
const onComplete = (data: UploadFile<any>) => {
console.log('File uploaded successfully', data);
}
return <>
<UploadDocumentModal
header={'Upload Warehouse Zones'}
data={templateCsv}
action={'https://api.service.com/upload'}
isOpen={isOpen}
onError={onError}
onCancel={() => setIsOpen(false)}
beforeUpload={beforeUpload}
onComplete={onComplete} />
</>;
};
Image
RadioButtonGroup
Radio Button Group is a HOC (Higher Order Component) which provides a skeleton base for implementing custom designed
radio buttons. For example a radio button where each button is a designed card. The children radio buttons must
implement currentValue
and setCurrentValue
prop which will have the same type as RadioButtonGroup
value and
setValue
prop respectively.
Props
- value (Optional: string | number): The value of the selected radio button.
- columns (Optional: number): The number of radio buttons to be displayed in a single row. Default is 2.
- setValue (Optional: (value: string | number) => void): Callback function called when the radio button is selection is changed.
Usage
const MyComponent = () => {
const [value, setValue] = useState('option1');
return (
<RadioButtonGroup value={value} setValue={setValue} columns={3}>
<RadioButton value={'option1'}>Option 1</RadioButton>
<RadioButton value={'option2'}>Option 2</RadioButton>
<RadioButton value={'option3'}>Option 3</RadioButton>
</RadioButtonGroup>
);
}
RadioCardButton
A radio button of card type to be used with RadioButtonGroup.
Props
- icon (Required: ReactElement): The icon to be displayed in the radio button.
- title (Required: string): The title of the radio button.
- subtitle (Required: string): The subtitle of the radio button.
- value (Required: string | number): The value of the radio button.
- currentValue (Optional: string | number): The value of the selected radio button in the RadioButtonGroup. This value is passed by the RadioButtonGroup component.
- setCurrentValue (Optional: (value: string | number) => void): Callback function called when this radio button is selected. This function is passed by the RadioButtonGroup component.
- ribbon (Optional: { text: string, color?: string }): The ribbon to be displayed on the radio button.
- disabled (Optional: boolean): Enables of disables the radio button.
Usage
import { useState } from "react";
const MyComponent = () => {
const [value, setValue] = useState('upload');
return <>
<RadioButtonGroup value={value} setValue={setValue}>
<RadioCardButton
icon={<FileAddTwoTone />}
title={"Upload"}
subtitle={"When you have a csv file ready"}
value={"upload"} />
<RadioCardButton
icon={<ScanOutlined style={{ color: "rgb(24, 144, 255)" }} />}
title={"Scan"}
disabled={true}
subtitle={"Scan and let us do the rest"}
ribbon={{ text: "Coming Soon", color: "green" }}
value={"scan"} />
</RadioButtonGroup>
</>
}
Image
Common Interfaces
BaseEntity
All the backend entity types definition in UI must extend this interface type. For the ListViewPage component, the dataFetcher function must return an array of BaseEntity type.
interface BaseEntity {
id: number;
created_by: string;
updated_by: string;
created_at: string;
updated_at: string;
}
BaseResponseStatus
Status type for all backend api response status
key in SF.
interface BaseResponseStatus {
status_code: number;
status_message: string;
status_type: string;
total_count: number;
}
BaseResponseUserInfo
User info type for all backend api response user_info
key in SF.
interface BaseResponseUserInfo {
[key: string]: {
email?: string
name: string
phoneNumber?: string
};
}
BaseResponse
Base response type for all backend api response in SF where T
is the entity type.
interface BaseResponse<T> {
status: BaseResponseStatus;
data: Array<T>;
user_info?: BaseResponseUserInfo;
}
SearchApiSearchParamsFilter
Type for building SF search api filters
param string.
interface SearchApiSearchParamsFilter {
field: string;
operator: string;
value?: string;
}
SearchApiSearchParams
Type with all acceptable parameters for SF search api.
interface SearchApiSearchParams {
fetchSize?: number;
page?: number;
sortBy?: string;
includes?: string;
filters?: Array<SearchApiSearchParamsFilter>;
}
Contexts
UserFacilityContext
Context to store the user facilities. The context provider must be wrapped around the application to provide the user facilities to the components. Ideally the context value must be saved at the render of App component on app initialisation.
Usage
- Wrap the AppContainer inside App component (or whichever is the parent component returned from App)
with
UserFacilitiesProvider
.
import { UserFacilitiesProvider } from 'tunakit';
const App = () => {
return <UserFacilitiesProvider>
<AppContainer {...props} />
</UserFacilitiesProvider>;
};
- Set the value in context on app load (inside AppContainer)
const AppContainer = () => {
const { setUserFacilities } = useUserFacilitiesContext();
const { userFacilities, loading: facilityLoading, error: userFacilitiesError } = useUserFacilities();
useEffect(() => {
if (userFacilities.length) {
setUserFacilities(userFacilities);
}
}, [JSON.stringify(userFacilities)]);
if (facilityLoading) {
return <Spin fullscreen spinning={facilityLoading} tip={'Loading App'} />
}
const ApplicationError = () => {
return <Card><Result
status="error"
title="Error loading WMS."
subTitle={'Please refresh or contact admin.'}
extra={[
<Button type="primary" key="console" onClick={() => window.location.reload()}>
Refresh
</Button>,
]}
/></Card>
}
return <>
// core app rendering logic here, render ApplicationError if userFacilitiesError truthy
</>
}
- Getting UserFacilities value anywhere in the app.
No need to handle loading, since the value access will be sync and in constant time. The value retrieved is reactive, if context changes, part of the page rerenders where userFacilities is accessed.
const MyComponent = () => {
const { userFacilities } = useUserFacilitiesContext();
return <>
{userFacilities.map(facility => <div key={facility.facility.id}>{facility.facility.name}</div>)}
</>
}