Updated drawer filters

bug fixes
pagination constrained to 2000 records
This commit is contained in:
2023-11-06 01:50:58 +05:30
parent 9810798b02
commit 380a9059aa
39 changed files with 771 additions and 227 deletions

37
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "0.0.21",
"license": "ISC",
"dependencies": {
"@armco/analytics": "^0.2.2",
"@armco/analytics": "^0.2.5",
"@armco/armory-react-components": "^0.0.20",
"@popperjs/core": "^2.11.8",
"@reduxjs/toolkit": "^1.8.1",
@@ -17,6 +17,7 @@
"bootstrap": "^5.3.0",
"classnames": "^2.3.2",
"d3": "^7.8.5",
"highcharts": "^11.2.0",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.5",
"moment": "^2.29.4",
@@ -101,9 +102,9 @@
}
},
"node_modules/@armco/analytics": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@armco/analytics/-/analytics-0.2.2.tgz",
"integrity": "sha512-dnzL1TQVx35RoCJGf5LKH1Rf/VqGmQTsoZY0niicdzXhPQk/qA2V8X/q0iIJMZgwQ3CO35437zxKx/QWV3xaVQ==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@armco/analytics/-/analytics-0.2.5.tgz",
"integrity": "sha512-esgKYvXGCTM2TWeFd6a9lkQRZur5Y9ATVElxgWKjMRak0DSFbBE1CGJbsKaSf398gv622ZkVmLgIq9DbIbbesw==",
"dependencies": {
"jet-logger": "^1.3.1",
"jquery": "^3.7.0",
@@ -15549,6 +15550,11 @@
"tslib": "^2.0.3"
}
},
"node_modules/highcharts": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.2.0.tgz",
"integrity": "sha512-9i650YK7ZBA1Mgtr3avMkLVCAI45RQvYnwi+eHsdFSaBGuQN6BHoa4j4lMkSJLv0V4LISTK1z7J7G82Lzd7zwg=="
},
"node_modules/highlight.js": {
"version": "11.8.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
@@ -17175,9 +17181,9 @@
}
},
"node_modules/jquery": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
"integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/js-cookie": {
"version": "3.0.5",
@@ -22545,9 +22551,9 @@
}
},
"@armco/analytics": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@armco/analytics/-/analytics-0.2.2.tgz",
"integrity": "sha512-dnzL1TQVx35RoCJGf5LKH1Rf/VqGmQTsoZY0niicdzXhPQk/qA2V8X/q0iIJMZgwQ3CO35437zxKx/QWV3xaVQ==",
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@armco/analytics/-/analytics-0.2.5.tgz",
"integrity": "sha512-esgKYvXGCTM2TWeFd6a9lkQRZur5Y9ATVElxgWKjMRak0DSFbBE1CGJbsKaSf398gv622ZkVmLgIq9DbIbbesw==",
"requires": {
"jet-logger": "^1.3.1",
"jquery": "^3.7.0",
@@ -33746,6 +33752,11 @@
"tslib": "^2.0.3"
}
},
"highcharts": {
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/highcharts/-/highcharts-11.2.0.tgz",
"integrity": "sha512-9i650YK7ZBA1Mgtr3avMkLVCAI45RQvYnwi+eHsdFSaBGuQN6BHoa4j4lMkSJLv0V4LISTK1z7J7G82Lzd7zwg=="
},
"highlight.js": {
"version": "11.8.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.8.0.tgz",
@@ -34904,9 +34915,9 @@
}
},
"jquery": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.0.tgz",
"integrity": "sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ=="
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"js-cookie": {
"version": "3.0.5",

View File

@@ -28,7 +28,7 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@armco/analytics": "^0.2.2",
"@armco/analytics": "^0.2.5",
"@armco/armory-react-components": "^0.0.20",
"@popperjs/core": "^2.11.8",
"@reduxjs/toolkit": "^1.8.1",
@@ -36,6 +36,7 @@
"bootstrap": "^5.3.0",
"classnames": "^2.3.2",
"d3": "^7.8.5",
"highcharts": "^11.2.0",
"highlight.js": "^11.8.0",
"js-cookie": "^3.0.5",
"moment": "^2.29.4",

View File

@@ -74,6 +74,14 @@ const ConfigRowItem = (props: ConfigRowItemProps): JSX.Element => {
onClick={() => (edited ? setEdited(false) : setEdited(true))}
/>
</span>
<span className={`me-3 ${edited ? "pe-none" : "cursor-pointer"}`}>
<LoadableIcon
icon="md/MdAdd"
width="1.3rem"
color={!configIsSubmittable ? "green" : "rgba(0, 128, 0, 0.3)"}
onClick={() => onDelete && onDelete(config?._id)}
/>
</span>
<span className={edited ? "pe-none" : "cursor-pointer"}>
<LoadableIcon
icon="ri/RiDeleteBin6Line"

View File

@@ -33,7 +33,7 @@ const ConfigurationViewer = (props: ConfigurationViewerProps): JSX.Element => {
const addConfig = (key: string, value: string, _id?: string) => {
const payload: ObjectType = {
key,
value: JSON.parse(value),
value,
namespace: namespace._id,
version: "v1",
}

View File

@@ -1,17 +1,31 @@
import { ReactNode } from "react"
import { useState } from "react"
import { DrawerProps } from "../../types/components.interface"
import "./Drawer.component.scss"
interface DrawerProps {
children?: ReactNode
classes?: string
}
import LoadableIcon from "../atoms/LoadableIcon"
const Drawer = (props: DrawerProps): JSX.Element => {
const { children, classes } = props
const { children, classes, isCollapsible } = props
const [collapsed, setCollapsed] = useState<boolean>()
return (
<aside className={`ar-Drawer${classes ? " " + classes : ""}`}>
{children}
<aside
className={`ar-Drawer${classes ? " " + classes : ""}${
isCollapsible ? " position-relative" : ""
}${collapsed ? " collapsed" : ""}`}
>
{isCollapsible && (
<LoadableIcon
classes="position-absolute top-1 end-1 cursor-pointer"
icon={
collapsed
? "tb/TbLayoutSidebarLeftExpand"
: "tb/TbLayoutSidebarLeftCollapse"
}
size="1.5rem"
onClick={() => setCollapsed(!collapsed)}
/>
)}
{!collapsed && children}
</aside>
)
}

View File

@@ -0,0 +1,3 @@
.ar-FontsList {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import FontsList from "./FontsList"
describe("FontsList", () => {
it("renders without error", () => {
})
})

View File

@@ -0,0 +1,93 @@
import { ChangeEvent, useEffect, useState } from "react"
import { FontsListProps } from "../../types/components.interface"
import { ArrayType } from "../../types/types"
import { ArButtonVariants, ArLoaderTypes, ArSizes } from "../../types/enums"
import { Button, Loader, Search } from ".."
import { Helper } from "../../utils"
import "./FontsList.component.scss"
const FontsList = (props: FontsListProps): JSX.Element => {
const [fonts, setFonts] = useState<ArrayType>()
const [page, setPage] = useState<ArrayType>()
const [loading, setLoading] = useState<boolean>()
// TODO: Fetch Fonts
useEffect(() => {}, [])
return (
<div className="ar-FontsList h-100 w-100">
{!fonts && (
<Loader label="Loading Fonts..." type={ArLoaderTypes.SHAPES} />
)}
<div className="ar-FontsList__header-pagination-search-upload py-2 px-3 mb-2 border">
<div className="row">
<div className="col-4 d-flex align-items-center">
<h6 className="mb-0 h-100 flex-center px-3 border-right">
<Button
variant={ArButtonVariants.LINK}
content="Categories"
size={ArSizes.SMALL}
splitOptions={[
{
label: "All",
},
{
label: "Business",
},
{
label: "Medical",
},
]}
/>
</h6>
</div>
<span className="col-4 d-none d-md-flex flex-v-center justify-content-end">
<Button
classes="h-100 float-end me-3"
content="Create"
size={ArSizes.SMALL}
variant={ArButtonVariants.LINK}
preIcon="io5/IoCreateOutline"
/>
<Button
classes="h-100 float-end me-3"
content="Upload"
size={ArSizes.SMALL}
variant={ArButtonVariants.SUCCESS}
/>
</span>
<div className="col-4 offset-4 offset-md-0 flex-v-center">
<Search
classes="bg-white"
placeholder="Search by name, tags, description"
onChange={Helper.debounce(
(event: ChangeEvent<HTMLInputElement>) => null,
// onSearchChanged(event.target.value),
1000,
)}
data={
fonts
? fonts.map(
(font): SearchItem => ({
label: font.name,
data: font,
}),
)
: []
}
/>
</div>
</div>
</div>
{page && (
<div className="ar-FontsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap flex-grow-1">
{loading && (
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
)}
</div>
)}
</div>
)
}
export default FontsList

View File

@@ -0,0 +1,3 @@
import FontsList from "./FontsList"
export default FontsList

View File

@@ -9,7 +9,6 @@ import {
Tags,
TextInput,
} from ".."
import { ObjectType } from "../../types/types"
import "./IconController.component.scss"
const complement = (hex?: string) => {
@@ -151,15 +150,18 @@ const IconController = (props: IconControllerProps): JSX.Element => {
setUnit(e.value)
}
options={[
// @ts-ignore
{ label: "rem", value: "rem" },
// @ts-ignore
{ label: "px", value: "px" },
// @ts-ignore
{ label: "vh", value: "vh" },
]}
/>
</div>
</div>
</div>
<Tags classes="col-12 h-75" label={name} />
<Tags classes="col-12 h-100 px-0" label={name} />
</div>
</div>
</div>

View File

@@ -4,6 +4,7 @@ import { useAppDispatch, useAppSelector } from "../../hooks"
import { notify, setRightPanelContent } from "../../store"
import {
getFavorites,
getSelectedTag,
removeFavorite,
setFavorites,
} from "../../pages/IconsPage/IconsPage.slice"
@@ -20,20 +21,68 @@ import {
ArButtonVariants,
ArIconTileTypes,
ArLoaderTypes,
ArPageTriggers,
ArPopoverSlots,
ArPopoverTriggers,
ArSizes,
} from "../../types/enums"
import { IconTileProps, IconsListProps } from "../../types/components.interface"
import Helper from "../../utils/helper"
import { FunctionType, ObjectType, SegmentType } from "../../types/types"
import { Helper, Network } from "../../utils"
import API_CONFIG from "../../config/api-config"
import { ENDPOINTS } from "../../config/constants"
import "./IconsList.component.scss"
import { SegmentType } from "../../types/types"
const fetchIconsPage = (
limit: number,
from: number,
filters: ObjectType,
dataSetter: FunctionType,
pageSetter: FunctionType,
setLoading: FunctionType,
) => {
const pageApi =
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
ENDPOINTS.STATIC.ICON.ROOT +
ENDPOINTS.STATIC.ICON.PAGE
const queryParams: ObjectType = { pageSize: limit, from, ...filters }
// if (
// filters &&
// typeof filters === "object" &&
// Object.keys(filters).length > 0
// ) {
// queryParams.filters = filters
// }
Network.get(pageApi, queryParams)
.then((response) => {
if (response && response.status === 200) {
if (response.body && dataSetter) {
dataSetter(response.body)
pageSetter(response.body.slice(0, 100))
}
}
setLoading(false)
})
.catch((error) => {
console.error(error)
setLoading(false)
})
}
const IconsList = (props: IconsListProps): JSX.Element => {
const { icons, onSearchChanged } = props
const { onSearchChanged, searchString } = props
const [icons, setIcons] = useState<Array<IconResponse>>()
const [page, setPage] = useState<Array<IconResponse>>()
const [view, setView] = useState<SegmentType>()
const [loading, setLoading] = useState<boolean>()
const dispatch = useAppDispatch()
const favorites = useAppSelector(getFavorites)
const selectedTag = useAppSelector<string | undefined>(getSelectedTag)
useEffect(() => {
setView({ name: ArIconTileTypes.COMFY })
}, [])
useEffect(() => {
if (favorites) {
@@ -43,10 +92,17 @@ const IconsList = (props: IconsListProps): JSX.Element => {
}
}, [favorites, dispatch])
const slices =
icons &&
icons.length > 0 &&
Helper.generateSlices(icons.length, 100, "index")
useEffect(() => {
const filters: ObjectType = {}
if (selectedTag) {
filters.tags = selectedTag
}
if (searchString) {
filters.search = searchString
}
setLoading(true)
fetchIconsPage(2000, 0, filters, setIcons, setPage, setLoading)
}, [selectedTag, searchString])
const onIconTileClick = (iconProps: IconResponse) => {
dispatch(
@@ -59,7 +115,7 @@ const IconsList = (props: IconsListProps): JSX.Element => {
}
return (
<div className="ar-IconsList h-100 w-100 overflow-auto position-relative">
<div className="ar-IconsList h-100 w-100 overflow-auto position-relative d-flex flex-column">
{!icons && (
<Loader label="Loading Icons..." type={ArLoaderTypes.SHAPES} />
)}
@@ -127,9 +183,11 @@ const IconsList = (props: IconsListProps): JSX.Element => {
<Search
classes="bg-white"
placeholder="Search by name, tags, description"
onChange={(event: ChangeEvent<HTMLInputElement>) =>
onSearchChanged(event.target.value)
}
onChange={Helper.debounce(
(event: ChangeEvent<HTMLInputElement>) =>
onSearchChanged(event.target.value),
1000,
)}
data={
icons
? icons.map(
@@ -144,11 +202,14 @@ const IconsList = (props: IconsListProps): JSX.Element => {
</div>
</div>
</div>
{icons && (
<div className="ar-IconsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap">
{icons.slice(0, 100).map((icon, index) => (
{page && (
<div className="ar-IconsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap flex-grow-1">
{loading && (
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
)}
{page.map((icon, index) => (
<Popover trigger={ArPopoverTriggers.HOVER}>
{view && view.name !== ArIconTileTypes.LIST ? (
{!view || view.name !== ArIconTileTypes.LIST ? (
<span slot={ArPopoverSlots.POPOVER}>{icon.name}</span>
) : null}
<IconTile
@@ -157,10 +218,12 @@ const IconsList = (props: IconsListProps): JSX.Element => {
? (view.name as ArIconTileTypes)
: ArIconTileTypes.COMFY
}
hideBorder={view?.name !== ArIconTileTypes.LIST}
classes={view?.name}
slot={ArPopoverSlots.ANCHOR}
key={index}
icon={icon}
iconSize={view?.name === ArIconTileTypes.LIST ? "1rem" : "2rem"}
onClick={onIconTileClick}
tools={[
{
@@ -226,13 +289,20 @@ const IconsList = (props: IconsListProps): JSX.Element => {
))}
</div>
)}
{slices && (
<Pagination
classes="my-3 flex-center"
data={slices}
maxPillsToShow={5}
/>
)}
{/* {slices && ( */}
<Pagination
classes="my-3 flex-center"
data={icons}
maxPillsToShow={5}
pageSetter={setPage}
// trigger={ArPageTriggers.SCROLL}
count={1}
load={100}
dataFetcher={(load, count) =>
fetchIconsPage(load, count, {}, setIcons, setPage, setLoading)
}
/>
{/* )} */}
</div>
)
}

View File

@@ -4,8 +4,8 @@ import "./LoginProvider.component.scss"
const LoginProvider = (props: LoginProviderProps): JSX.Element => {
return (
<iframe
src="https://iam.notabuck.com"
// src="http://localhost:3001"
// src="https://iam.notabuck.com"
src="http://localhost:3001"
title="IAM"
className="ar-LoginProvider h-100"
/>

View File

@@ -1,8 +1,12 @@
.ar-Main {
.ar-Drawer {
transition: width 0.3s;
width: 15%;
&.collapsed {
width: 3.5rem;
}
& + .ar-Content {
width: 85%;
// width: 85%;
}
}
}

View File

@@ -18,7 +18,11 @@ const Main = (props: MainProps): JSX.Element => {
} = props
return (
<main className="ar-Main d-flex flex-grow-1 w-100">
{drawerContent && <Drawer classes="d-flex h-100">{drawerContent}</Drawer>}
{drawerContent && (
<Drawer classes="d-flex h-100" isCollapsible>
{drawerContent}
</Drawer>
)}
<SidePanel
header={leftPanelHeader || "Header Name"}
placement={ArPlacement.LEFT}

View File

@@ -4,17 +4,19 @@ import { ArVizProps } from "../../../types/components.interface"
import { ArVisualizationTypes } from "../../../types/enums"
import { generateBubbleChart } from "../../../utils/chartGenerators"
import "./ArViz.component.scss"
import BubbleChart from "../BubbleChart"
import { ObjectType } from "../../../types/types"
const dataDummy = [
{ source: "Item 1", x: 100, y: 60, val: 1350, color: "#C9D6DF" },
{ source: "Item 2", x: 30, y: 80, val: 2500, color: "#F7EECF" },
{ source: "Item 3", x: 50, y: 40, val: 5700, color: "#E3E1B2" },
{ source: "Item 4", x: 190, y: 100, val: 30000, color: "#F9CAC8" },
{ source: "Item 5", x: 80, y: 170, val: 47500, color: "#D1C2E0" },
{ source: "Item 1", val: 1350, color: "#C9D6DF" },
{ source: "Item 2", val: 2500, color: "#F7EECF" },
{ source: "Item 3", val: 5700, color: "#E3E1B2" },
{ source: "Item 4", val: 30000, color: "#F9CAC8" },
{ source: "Item 5", val: 47500, color: "#D1C2E0" },
]
const ArViz = (props: ArVizProps): JSX.Element => {
const { type, data, demo } = props
const { clickHandler, type, data, demo } = props
const svgContainerRef = useRef(null)
useEffect(() => {
@@ -34,7 +36,9 @@ const ArViz = (props: ArVizProps): JSX.Element => {
}
}, [svgContainerRef, data])
return (
return type === ArVisualizationTypes.BUBBLE ? (
<BubbleChart data={data as Array<ObjectType>} clickHandler={clickHandler} />
) : (
<div className="ar-ArViz h-100 w-100 overflow-auto p-3">
<svg
id="ar-ArViz__chart-container"

View File

@@ -0,0 +1,3 @@
.ar-BubbleChart {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import BubbleChart from "./BubbleChart"
describe("BubbleChart", () => {
it("renders without error", () => {
})
})

View File

@@ -0,0 +1,100 @@
import { useEffect, useRef } from "react"
import Highcharts from "highcharts"
import HighchartsMore from "highcharts/highcharts-more"
import { BubbleChartProps } from "../../../types/components.interface"
import "./BubbleChart.component.scss"
import { ObjectType } from "../../../types/types"
HighchartsMore(Highcharts)
const BubbleChart = (props: BubbleChartProps): JSX.Element => {
const { clickHandler, data } = props
const chartRef = useRef(null)
useEffect(() => {
if (chartRef.current && data) {
const labels = Object.keys(data)
const values = Object.values(data)
const options = {
chart: {
type: "packedbubble",
},
title: {
text: null,
},
tooltip: {
formatter: function (): string {
return (
"Tag: <b>" +
("point" in this && (this.point as ObjectType).name) +
"</b><br>Count: <b>" +
("y" in this && this.y) +
"</b>"
)
},
},
credits: {
enabled: false,
},
plotOptions: {
packedbubble: {
minSize: "30%",
maxSize: "120%",
zMin: 0,
zMax: 1000,
layoutAlgorithm: {
splitSeries: false,
gravitationalConstant: 0.02,
},
dataLabels: {
enabled: true,
format: "{point.name}",
filter: {
property: "y",
operator: ">",
value: 250,
},
style: {
color: "black",
textOutline: "none",
fontWeight: "normal",
},
},
events: {
click: function (e: any) {
clickHandler && clickHandler(e)
},
},
},
},
series: [
{
cursor: "pointer",
showInLegend: false,
data: labels.map((label, index) => ({
// x: index, // X-axis position
// y: values[index], // Y-axis value
// z: values[index], // Bubble size value
value: values[index],
name: label, // Label for the bubble
})),
},
],
xAxis: {
visible: false,
},
yAxis: {
visible: false,
},
}
// @ts-ignore
Highcharts.chart(chartRef.current, options)
}
}, [data]) // Re-render chart when data prop changes
return <div ref={chartRef} style={{ width: "100%" }}></div>
}
export default BubbleChart

View File

@@ -0,0 +1,3 @@
import BubbleChart from "./BubbleChart"
export default BubbleChart

View File

@@ -19,7 +19,7 @@ const Dropdown = (props: DropdownProps): JSX.Element => {
onSelectionChanged(
{
value: e.target.value,
data: options.find((o) => (o.name || o.label) === e.target.value),
data: options?.find((o) => (o.name || o.label) === e.target.value),
},
context,
)
@@ -37,10 +37,10 @@ const Dropdown = (props: DropdownProps): JSX.Element => {
onChange={onLocalChange}
aria-label={ariaLabel}
>
{options.map((option, index) => (
{options?.map((option, index) => (
<option
key={(id || label) + "dropdown-" + index}
value={option.name || option.label}
value={(option.name || option.label) as string}
>
{option.label}
</option>

View File

@@ -13,6 +13,7 @@ const IconTile = (props: IconTileProps): JSX.Element => {
hideBorder,
hideFooter,
icon,
iconSize,
onClick,
tools,
toolsPlacement,
@@ -65,8 +66,8 @@ const IconTile = (props: IconTileProps): JSX.Element => {
>
<LoadableIcon
// key={icon.name}
size="2rem"
svgB64String={btoa(icon.svg)}
size={iconSize}
svgB64String={btoa(icon.icon)}
color={hovered ? "blue" : "grey"}
/>
{type !== ArIconTileTypes.LIST && iconTools}

View File

@@ -1,19 +1,49 @@
import { ReactNode, useEffect, useState } from "react"
import { Icon, SelectionPill } from "../.."
import { PaginationProps } from "../../../types/components.interface"
import { ArPageTriggers } from "../../../types/enums"
import { Helper } from "../../../utils"
import { ObjectType } from "../../../types/types"
import { PageInfoType } from "../../../types/entity.interface"
import "./Pagination.component.scss"
const Pagination = (props: PaginationProps): JSX.Element => {
const { classes, data, maxPillsToShow } = props
const Pagination = (props: PaginationProps): ReactNode => {
const {
classes,
count,
data,
dataFetcher,
load,
maxPillsToShow,
pageSetter,
pageSize,
trigger,
} = props
const [currentPage, setCurrentPage] = useState<number>()
const [pages, setPages] = useState<Array<PageInfoType>>()
const [pageWindow, setPageWindow] = useState<number>()
let selectorBlockRenders
const navItemSize = "1.2rem"
if (data) {
if (maxPillsToShow && data.length > maxPillsToShow) {
useEffect(() => {
if (data) {
const slices =
data &&
data.length > 0 &&
Helper.generateSlices(data.length, pageSize || 100, "index")
slices && setPages(slices)
setPageWindow(0)
}
}, [data])
if (pages && data && pageWindow !== undefined) {
if (maxPillsToShow && pages.length > maxPillsToShow) {
selectorBlockRenders = []
// TODO: onclick
selectorBlockRenders.push(
<SelectionPill
classes="me-2 border-radius"
disabled={pageWindow === 0}
data={{
label: (
<Icon
@@ -24,12 +54,12 @@ const Pagination = (props: PaginationProps): JSX.Element => {
/>
),
}}
onClick={() => {}}
onClick={() => setPageWindow(pageWindow - maxPillsToShow)}
/>,
)
selectorBlockRenders = selectorBlockRenders.concat(
data
.slice(0, maxPillsToShow)
pages
.slice(pageWindow, pageWindow + maxPillsToShow)
.map((obj, index, arr) => (
<SelectionPill
classes={
@@ -40,13 +70,25 @@ const Pagination = (props: PaginationProps): JSX.Element => {
: ""
}
data={obj}
onClick={() => {}}
selected={currentPage === obj.sliceIndex}
// onClick={() => dataFetcher && dataFetcher(load, count)}
onClick={(pageInfo: ObjectType) => {
const pageData = data.slice(
(pageInfo.data as ObjectType).startIndex as number,
(pageInfo.data as ObjectType).endIndex as number,
)
pageSetter && pageSetter(pageData)
setCurrentPage(
(pageInfo.data as ObjectType).sliceIndex as number,
)
}}
/>
)),
)
selectorBlockRenders.push(
<SelectionPill
classes="ms-2 border-radius"
disabled={pages?.length <= pageWindow}
data={{
label: (
<Icon
@@ -57,18 +99,18 @@ const Pagination = (props: PaginationProps): JSX.Element => {
/>
),
}}
onClick={() => {}}
onClick={() => setPageWindow(pageWindow + maxPillsToShow)}
/>,
)
} else {
// TODO: Add onClickhandler
selectorBlockRenders = data.map((obj) => (
selectorBlockRenders = pages.map((obj) => (
<SelectionPill data={obj} onClick={() => {}} />
))
}
}
return (
return trigger === ArPageTriggers.SCROLL ? null : (
<div className={`ar-Pagination${classes ? " " + classes : ""}`}>
{selectorBlockRenders}
</div>

View File

@@ -24,4 +24,9 @@
&.selected {
background-color: var(--ar-bg-selected);
}
&.disabled {
pointer-events: none;
cursor: auto;
}
}

View File

@@ -1,26 +1,24 @@
import { useState } from "react"
import { useEffect, useState } from "react"
import { SelectionPillProps } from "../../../types/components.interface"
import "./SelectionPill.component.scss"
type PILLSIZES = "large" | "medium" | "small"
interface SelectionPillProps {
classes?: string
context?: string
data: { [key: string]: string | number | JSX.Element }
onClick: Function
size?: PILLSIZES
}
const SelectionPill = (props: SelectionPillProps): JSX.Element => {
const { classes, context, data, onClick, size } = props
const [selected, setSelected] = useState<boolean>()
const { classes, context, data, disabled, onClick, selected, size } = props
const [localSelected, setLocalSelected] = useState<boolean>()
useEffect(() => {
setLocalSelected(selected)
}, [selected])
return (
<span
className={`ar-SelectionPill inline-flex-center border${
classes ? " " + classes : ""
}${size ? " " + size : ""}
${selected ? " selected" : ""}`}
${selected ? " selected" : ""}
${disabled ? " disabled" : ""}`}
onClick={() => {
setSelected(!selected)
setLocalSelected(!localSelected)
onClick && onClick({ value: data.label || "", data })
}}
>

View File

@@ -4,39 +4,47 @@ import {
ArPopoverTriggers,
ArVisualizationTypes,
} from "../../../types/enums"
import { ObjectType } from "../../../types/types"
import ArViz from "../ArViz"
import LoadableIcon from "../LoadableIcon"
import Popover from "../Popover"
import "./Tags.component.scss"
const Tags = (props: TagsProps): JSX.Element => {
const { classes, label } = props
const { clickHandler, classes, hideHeader, label, tags } = props
return (
<div className={`ar-Tags w-100${classes ? " " + classes : ""}`}>
<div className="border h-100">
<div className="ar-Tags__header fw-bold border-bottom py-1 px-2">
<div className="d-inline-block me-2">
{(label ? label : "") + " Tags"}
</div>
<Popover classes="d-inline-block" trigger={ArPopoverTriggers.HOVER}>
<div
slot={ArPopoverSlots.POPOVER}
style={{
width: "6rem",
whiteSpace: "normal",
lineHeight: "1.2rem",
}}
>
Please use up/down errors to indicate if a tag matches the icon.
<div className="h-100">
{!hideHeader && (
<div className="ar-Tags__header fw-bold border-bottom py-1 px-2">
<div className="d-inline-block me-2">
{(label ? label : "") + " Tags"}
</div>
<LoadableIcon
icon="md.MdInfoOutline"
slot={ArPopoverSlots.ANCHOR}
/>
</Popover>
</div>
<Popover classes="d-inline-block" trigger={ArPopoverTriggers.HOVER}>
<div
slot={ArPopoverSlots.POPOVER}
style={{
width: "6rem",
whiteSpace: "normal",
lineHeight: "1.2rem",
}}
>
Please use up/down arrows to indicate if a tag matches the icon.
</div>
<LoadableIcon
icon="md.MdInfoOutline"
slot={ArPopoverSlots.ANCHOR}
/>
</Popover>
</div>
)}
<div className="ar-Tags__container flex-h-center">
<ArViz type={ArVisualizationTypes.BUBBLE} data={[]} />
<ArViz
clickHandler={clickHandler}
type={ArVisualizationTypes.BUBBLE}
data={tags as ObjectType}
/>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
/* PLOP_INJECT_IMPORT */
import BubbleChart from "./atoms/BubbleChart"
import CronTab from "./atoms/CronTab"
import Carousel from "./molecules/Carousel"
import Swiper from "./molecules/Swiper"
@@ -140,9 +141,10 @@ import Alert from "./atoms/Alert"
export {
/* PLOP_INJECT_EXPORT */
CronTab,
Carousel,
Swiper,
BubbleChart,
CronTab,
Carousel,
Swiper,
TaskList,
TaskLoginPrompt,
TaskViewer,

View File

@@ -1,6 +1,14 @@
import { useState } from "react"
import { AlphabetFilter, CategoryFilter, Dropdown, Pillbox } from "../.."
import { selectTag } from "../../../pages/IconsPage/IconsPage.slice"
import { useAppDispatch } from "../../../hooks"
import { AlphabetFilter, CategoryFilter, Dropdown, Pillbox, Tags } from "../.."
import { PillProps } from "../../atoms/Pill/Pill"
import {
BasicFilterConfig,
BasicFilterType,
FilterState,
FiltersProps,
} from "../../../types/filterconfig"
import Helper from "../../../utils/helper"
import "./Filters.component.scss"
@@ -9,14 +17,13 @@ const Filters = (props: FiltersProps): JSX.Element => {
const [filters, setFilters] = useState<FilterState | undefined>(
initialFilters,
)
const dispatch = useAppDispatch()
const useData = filteredData || data
const total = useData && useData.length
const countOptions = Helper.generateSlices(
total || 0,
config.count || 0,
"range",
)
const countOptions =
config.count !== undefined &&
Helper.generateSlices(total || 0, config.count || 0, "range")
const onLocalFilterChange = (
data: BasicFilterType | undefined,
@@ -120,26 +127,33 @@ const Filters = (props: FiltersProps): JSX.Element => {
})
}
})
const clickHandler = (e: any) => {
dispatch(selectTag(e.point.name))
}
return (
<div className="ar-Filters p-3 h-100 overflow-auto">
<div className="ar-Filters p-3 h-100 overflow-auto w-100">
<h5>Filters</h5>
{"count" in config && (
<Dropdown
classes="mb-3"
context="count"
id="icon-range-selector"
label="Select a range"
options={countOptions}
onSelectionChanged={(data: BasicFilterConfig) => {
onLocalFilterChange(data, "count")
}}
/>
<>
<Dropdown
classes="mb-3"
context="count"
id="icon-range-selector"
label="Select a range"
options={countOptions || []}
onSelectionChanged={(data: BasicFilterConfig) => {
onLocalFilterChange(data, "count")
}}
/>
<Pillbox
classes="mb-3"
data={flatFilters}
onChange={onLocalFilterChange}
/>
</>
)}
<Pillbox
classes="mb-3"
data={flatFilters}
onChange={onLocalFilterChange}
/>
{config.alphabet && (
<AlphabetFilter onSelectionChanged={onLocalFilterChange} />
)}
@@ -156,6 +170,13 @@ const Filters = (props: FiltersProps): JSX.Element => {
}
/>
)}
<Tags
clickHandler={clickHandler}
classes="col-12 h-75"
label={"test"}
tags={config.tags}
hideHeader
/>
</div>
)
}

View File

@@ -7,20 +7,26 @@ export const ICON_ROOT = `${
export const SESSION_COOKIE_NAME = "x-access-token"
export const DEFAULT_LOCALE = {
direction: "ltr",
format: moment.localeData().longDateFormat("L"),
separator: " - ",
applyLabel: "Apply",
cancelLabel: "Cancel",
weekLabel: "W",
customRangeLabel: "Custom Range",
daysOfWeek: moment.weekdaysMin(),
monthNames: moment.monthsShort(),
firstDay: moment.localeData().firstDayOfWeek(),
}
// export const DEFAULT_LOCALE = {
// direction: "ltr",
// format: moment.localeData().longDateFormat("L"),
// separator: " - ",
// applyLabel: "Apply",
// cancelLabel: "Cancel",
// weekLabel: "W",
// customRangeLabel: "Custom Range",
// daysOfWeek: moment.weekdaysMin(),
// monthNames: moment.monthsShort(),
// firstDay: moment.localeData().firstDayOfWeek(),
// }
export const ENDPOINTS = {
STATIC: {
ICON: {
ROOT: "/icon",
PAGE: "/page",
},
},
USERS: {
ROOT: "/secure/users",
ADD: "/add",

View File

@@ -1,9 +1,28 @@
import FontsList from "../../components/FontsList"
import Main from "../../components/Main"
import Footer from "../../components/Footer"
import "./FontsPage.page.scss"
interface FontsPageProps {}
const FontsPage = (props: FontsPageProps): JSX.Element => {
return <div className="ar-FontsPage">Be Here Soon</div>
return (
<div className="ar-FontsPage">
<Main
contentClasses="p-2"
mainContent={
<FontsList
icons={[]}
onSearchChanged={() => {}}
// searchString={searchText}
/>
}
// rightPanelContent={rightPanelContent}
// rightPanelHeader="Favorites"
/>
<Footer />
</div>
)
}
export default FontsPage

View File

@@ -4,6 +4,7 @@ import { IconTileProps } from "../../types/components.interface"
export interface IconsPageState {
favorites: Array<IconTileProps>
selectedTag?: string
}
const initialState: IconsPageState = {
@@ -20,11 +21,16 @@ export const iconsPageSlice = createSlice({
removeFavorite: (state, action: PayloadAction<IconTileProps>) => {
state.favorites.splice(state.favorites.indexOf(action.payload))
},
selectTag: (state, action: PayloadAction<string>) => {
state.selectedTag = action.payload
},
},
})
export const { setFavorites, removeFavorite } = iconsPageSlice.actions
export const { selectTag, setFavorites, removeFavorite } =
iconsPageSlice.actions
export const getFavorites = (state: RootState) => state.iconsPage.favorites
export const getSelectedTag = (state: RootState) => state.iconsPage.selectedTag
export default iconsPageSlice.reducer

View File

@@ -6,21 +6,22 @@ import Footer from "../../components/Footer"
import Main from "../../components/Main"
import IconsList from "../../components/IconsList"
import { ObjectType } from "../../types/types"
import { FilterState } from "../../types/filterconfig"
import Network from "../../utils/network"
import Helper from "../../utils/helper"
import "./IconsPage.page.scss"
interface IconsPageProps {}
const fetchData = async (setIcons: Function) => {
const fetchData = async (api: string, cb: Function) => {
const response: { status: number; body: Array<IconResponse> } =
await Network.getStatic("/icon/all", null, null)
setIcons(response.body)
await Network.getStatic(api, null, null)
cb && cb(response.body)
}
const IconsPage = (props: IconsPageProps): JSX.Element => {
const [icons, setIcons] = useState<Array<IconResponse> | undefined>()
// const [icons, setIcons] = useState<Array<IconResponse> | undefined>()
const [searchText, setSearchText] = useState<string | undefined>()
const [tags, setTags] = useState<ObjectType>()
const [filteredIcons, setFilteredIcons] = useState<
Array<IconResponse> | undefined
>()
@@ -34,62 +35,70 @@ const IconsPage = (props: IconsPageProps): JSX.Element => {
getRightPanelContent,
)
useEffect(() => {
!icons && fetchData(setIcons)
}, [icons])
// useEffect(() => {
// !icons && fetchData("/icon/all", setIcons)
// }, [icons])
useEffect(() => {
if (icons) {
let finalFilteredIconList: Array<IconResponse> | undefined = icons
if (searchText) {
finalFilteredIconList = Helper.recrusiveFilter(
finalFilteredIconList,
searchText,
false,
["name"],
)
}
if (filters && finalFilteredIconList) {
const alphabet =
filters.alphabet &&
(filters.alphabet as Array<BasicFilterConfig>).length > 0 &&
(filters.alphabet as Array<BasicFilterConfig>).map(
(al: BasicFilterConfig) => al.value,
)
finalFilteredIconList = Helper.matchArrayFilters(
finalFilteredIconList,
alphabet,
"name",
"starts",
)
const categories = filters.categories && filters.categories
if (categories) {
Object.keys(categories).forEach((category) => {
const categoryValue = categories[category as keyof BasicFilterType]
if ((categoryValue as Array<ObjectType>).length > 0) {
finalFilteredIconList = Helper.matchArrayFilters(
finalFilteredIconList,
categoryValue,
"group",
)
}
})
}
const countFilter =
filters.count && "data" in filters.count && filters.count.data
if (countFilter) {
finalFilteredIconList = finalFilteredIconList?.slice(
"startIndex" in countFilter ? +countFilter.startIndex : 0,
"endIndex" in countFilter ? +countFilter.endIndex : 0,
)
}
}
setFilteredIcons(finalFilteredIconList)
const variant = "gt100"
const parseTags = (tagReponse: ObjectType) => {
setTags(tagReponse[variant] as ObjectType)
}
}, [filters, icons, searchText])
!tags && fetchData(`/icon/tag/all?variants=${variant}`, parseTags)
}, [tags])
// useEffect(() => {
// if (icons) {
// let finalFilteredIconList: Array<IconResponse> | undefined = icons
// if (searchText) {
// finalFilteredIconList = Helper.recrusiveFilter(
// finalFilteredIconList,
// searchText,
// false,
// ["name"],
// )
// }
// if (filters && finalFilteredIconList) {
// const alphabet =
// filters.alphabet &&
// (filters.alphabet as Array<BasicFilterConfig>).length > 0 &&
// (filters.alphabet as Array<BasicFilterConfig>).map(
// (al: BasicFilterConfig) => al.value,
// )
// finalFilteredIconList = Helper.matchArrayFilters(
// finalFilteredIconList,
// alphabet,
// "name",
// "starts",
// )
// const categories = filters.categories && filters.categories
// if (categories) {
// Object.keys(categories).forEach((category) => {
// const categoryValue = categories[category as keyof BasicFilterType]
// if ((categoryValue as Array<ObjectType>).length > 0) {
// finalFilteredIconList = Helper.matchArrayFilters(
// finalFilteredIconList,
// categoryValue,
// "group",
// )
// }
// })
// }
// const countFilter =
// filters.count && "data" in filters.count && filters.count.data
// if (countFilter) {
// finalFilteredIconList = finalFilteredIconList?.slice(
// "startIndex" in countFilter ? +countFilter.startIndex : 0,
// "endIndex" in countFilter ? +countFilter.endIndex : 0,
// )
// }
// }
// setFilteredIcons(finalFilteredIconList)
// }
// }, [filters, icons, searchText])
return (
<div className="ar-IconsPage d-flex flex-column">
@@ -97,15 +106,25 @@ const IconsPage = (props: IconsPageProps): JSX.Element => {
contentClasses="p-2"
drawerContent={
<Filters
config={{ count: 1000, alphabet: true, categories: ["group"] }}
data={icons}
config={{
// count: 1000,
// alphabet: true,
// categories: ["group"],
tags,
}}
// config={{ tags }}
// data={icons}
filteredData={filteredIcons}
initialFilters={filters}
onFilterChange={setFilters}
/>
}
mainContent={
<IconsList icons={filteredIcons} onSearchChanged={setSearchText} />
<IconsList
icons={filteredIcons}
onSearchChanged={setSearchText}
searchString={searchText}
/>
}
rightPanelContent={rightPanelContent}
// rightPanelHeader="Favorites"

View File

@@ -169,4 +169,12 @@ label.required:after {
.bg {
background-color: var(--ar-bg);
}
.top-1 {
top: 1rem;
}
.end-1 {
right: 1rem;
}

View File

@@ -15,6 +15,8 @@ import {
ArButtonVariants,
ArIconTileTypes,
ArLoaderTypes,
ArPageTriggers,
ArPillSizes,
ArPlacement,
ArPopoverPositions,
ArPopoverTriggers,
@@ -26,6 +28,7 @@ import {
FrameContentDefinition,
Locale,
Namespace,
PageInfoType,
Task,
ToolItemConfig,
TreeListData,
@@ -72,6 +75,14 @@ export interface FormInputProps extends BaseProps {
}
/* PLOP_INJECT_INTERFACE */
export interface FontsListProps extends BaseProps {
}
export interface BubbleChartProps extends BaseProps {
clickHandler?: FunctionType
data: Array<ObjectType>
}
export interface CronTabProps extends BaseProps {
onChange: FunctionType
value?: ObjectType | string
@@ -224,6 +235,7 @@ export interface BubbleVizProps extends BaseProps {
}
export interface ArVizProps extends BaseProps {
clickHandler?: FunctionType
type: ArVisualizationTypes
data: ArrayType | ObjectType
}
@@ -242,7 +254,10 @@ export interface IconControllerProps extends BaseProps {
}
export interface TagsProps extends BaseProps {
clickHandler?: FunctionType
hideHeader?: boolean
label?: string
tags?: ObjectType
}
export interface ColorSelectorProps extends BaseProps {
@@ -423,12 +438,14 @@ export type SliderProps = { focussed?: boolean } & TextInputProps
export interface IconsListProps extends BaseProps {
icons?: Array<IconResponse>
onSearchChanged: Function
searchString?: string
}
export interface IconTileProps extends BaseProps {
hideBorder?: boolean
hideFooter?: boolean
icon: IconResponse
iconSize?: string
onClick?: Function
tools?: Array<ToolItemConfig>
toolsPlacement?: string
@@ -448,9 +465,23 @@ export interface ButtonProps extends BaseProps {
splitTrigger?: ArPopoverTriggers
}
export interface DrawerProps {
children?: ReactNode
classes?: string
isCollapsible?: boolean
}
export interface PaginationProps extends BaseProps {
data: Array<{ [key: string]: string | number }>
count: number
isHeadless?: boolean
load: number
trigger?: ArPageTriggers
maxPillsToShow?: number
pageSetter?: FunctionType
pageSize?: number
// data?: Array<{ [key: string]: string | number }>
data?: Array<PageItem>
dataFetcher?: FunctionType
}
export interface HeaderProps {
@@ -529,7 +560,7 @@ export interface DropdownProps extends FormInputProps {
id?: string
label?: string
onSelectionChanged: Function
options: Array<{ [key: string]: string | number }>
options?: Array<PageInfoType>
}
export interface ModalProps {
@@ -639,3 +670,12 @@ export interface DateProps extends FormInputProps {
}
export interface CardProps extends BaseProps {}
export interface SelectionPillProps extends BaseProps {
context?: string
data: PageInfoType
disabled?: boolean
onClick: Function
selected?: boolean
size?: ArPillSizes
}

View File

@@ -121,3 +121,11 @@ export interface CronType {
week: string
month: string
}
export interface PageInfoType {
label: string | JSX.Element
name?: string
sliceIndex?: number
startIndex?: number
endIndex?: number
}

View File

@@ -115,3 +115,14 @@ export enum JobStatus {
SUSPENDED = "suspended",
ARCHIVED = "archived",
}
export enum ArPageTriggers {
SELECTOR = "selector",
SCROLL = "scroll",
}
export enum ArPillSizes {
LARGE = "large",
MEDIUM = "medium",
SMALL = "small",
}

View File

@@ -1,15 +1,18 @@
interface BasicFilterConfig {
import { ObjectType } from "./types"
export interface BasicFilterConfig {
value: string
data?: { [key: string]: string | number }
}
interface FilterConfig {
export interface FilterConfig {
count?: number
alphabet?: boolean
categories?: Array<string>
tags?: ObjectType
}
interface FiltersProps {
export interface FiltersProps {
data?: Array<any>
filteredData?: Array<any>
config: FilterConfig
@@ -17,13 +20,13 @@ interface FiltersProps {
onFilterChange: Function
}
interface FilterState {
export interface FilterState {
count?: BasicFilterType
alphabet?: BasicFilterType
categories?: BasicFilterType
}
type BasicFilterType =
export type BasicFilterType =
| { [key: string]: Array<BasicFilterConfig> }
| BasicFilterConfig
| Array<BasicFilterConfig>

View File

@@ -1,7 +1,10 @@
interface IconResponse {
interface PageItem {}
interface IconResponse extends PageItem {
name: string
group: string
svg: string
icon: string
tags?: Array<string>
description?: string
message?: string

View File

@@ -1,5 +1,6 @@
import { lazy } from "react"
import { FunctionType } from "../types/types"
import { PageInfoType } from "../types/entity.interface"
class Helper {
static populatePagesInRoutes(routes: RouteConfig[]) {
@@ -87,7 +88,7 @@ class Helper {
) {
let i = 0,
j = 0
const slices = []
const slices: Array<PageInfoType> = []
while (i < count) {
let addUp = sliceLength
if (i + sliceLength > count) {

View File

@@ -137,13 +137,17 @@ export default class Network {
}
static stringifyUrl(url: string, queryParams?: any) {
if (!queryParams) {
return url || ""
}
const arrLength = Object.keys(queryParams).length
return url
? queryParams
? Object.keys(queryParams).reduce(
(acc, key) => acc.concat(`${key}=${queryParams[key]}`),
url + "?",
)
: url
? Object.keys(queryParams).reduce(
(acc, key, index) =>
acc.concat(`${key}=${queryParams[key]}`) +
(index < arrLength - 1 ? "&" : ""),
url + "?",
)
: ""
}
}