diff --git a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx index 17150923..df250111 100644 --- a/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx +++ b/demand-capacity-mgmt-frontend/src/components/capacitygroup/CapacityGroupsList.tsx @@ -21,33 +21,35 @@ */ -import React, { useContext, useMemo, useState, useEffect } from 'react'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; import { Button, Col, Dropdown, Form, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'; -import { FaCopy, FaEllipsisV, FaEye, FaRedo, FaStar } from 'react-icons/fa'; -import { useUser } from '../../contexts/UserContext'; +import { FaCopy, FaEllipsisV, FaEye, FaRedo } from 'react-icons/fa'; +import { LuStar } from 'react-icons/lu'; import { CapacityGroupContext } from '../../contexts/CapacityGroupsContextProvider'; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; +import { useUser } from '../../contexts/UserContext'; import '../../index.css'; +import { CapacityGroupProp } from '../../interfaces/capacitygroup_interfaces'; import { EventType } from '../../interfaces/event_interfaces'; +import { + FavoriteType, + SingleCapacityGroupFavoriteResponse +} from "../../interfaces/favorite_interface"; import { getUserGreeting } from '../../interfaces/user_interface'; import { LoadingMessage } from '../common/LoadingMessages'; import Pagination from '../common/Pagination'; import Search from '../common/Search'; import CapacityGroupsTable from './CapacityGroupsTable'; -import { - FavoriteType, - SingleCapacityGroupFavoriteResponse -} from "../../interfaces/Favorite_interface"; -import {FavoritesContext} from "../../contexts/FavoritesContextProvider"; const CapacityGroupsList: React.FC = () => { const { user } = useUser(); const { capacitygroups, isLoading, fetchCapacityGroupsWithRetry } = useContext(CapacityGroupContext)!; - + const [filteredCapacityGroups, setFilteredCapacityGroups] = useState([]); const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [sortColumn, setSortColumn] = useState(''); - const [sortOrder, setSortOrder] = useState(''); + const [sortOrder, setSortOrder] = useState('asc'); const [capacitygroupsPerPage, setcapacitygroupsPerPage] = useState(20); // Set the default value here const { addFavorite, fetchFavoritesByType, deleteFavorite } = useContext(FavoritesContext)!; const [favoriteCapacityGroups, setFavoriteCapacityGroups] = useState([]); @@ -94,11 +96,13 @@ const CapacityGroupsList: React.FC = () => { }, [capacitygroups]); - const filteredcapacitygroups = useMemo(() => { - let sortedcapacitygroups = [...capacitygroups]; + const isCapacityGroupFavorite = (capacityGroupID: string) => favoriteCapacityGroups.includes(capacityGroupID); + + useMemo(() => { + let filteredcapacitygroups = [...capacitygroups]; if (searchQuery !== '') { - sortedcapacitygroups = sortedcapacitygroups.filter((capacitygroup) => + filteredcapacitygroups = filteredcapacitygroups.filter((capacitygroup) => capacitygroup.internalId.toString().includes(searchQuery.toLowerCase()) || capacitygroup.name.toLowerCase().includes(searchQuery.toLowerCase()) || capacitygroup.customerBPNL.toString().includes(searchQuery.toLowerCase()) || @@ -110,8 +114,21 @@ const CapacityGroupsList: React.FC = () => { ); } + // Separate favorited and unfavorited demands + const favoriteCapacityGroups = filteredcapacitygroups.filter((capacitygroup) => isCapacityGroupFavorite(capacitygroup.internalId)); + const unfavoritedCapacityGroups = filteredcapacitygroups.filter((capacitygroup) => !isCapacityGroupFavorite(capacitygroup.internalId)); + + // Sort favorited demands by changedAt timestamp in descending order + //favoriteCapacityGroups.sort((a, b) => new Date(b.changedAt).getTime() - new Date(a.changedAt).getTime()); + + // Sort unfavorited demands by changedAt timestamp in descending order + //unfavoritedCapacityGroups.sort((a, b) => new Date(b.changedAt).getTime() - new Date(a.changedAt).getTime()); + + // Concatenate favorited and unfavorited demands + const sortedCapacityGroups = [...favoriteCapacityGroups, ...unfavoritedCapacityGroups]; + if (sortColumn !== '') { - sortedcapacitygroups.sort((a, b) => { + sortedCapacityGroups.sort((a, b) => { const aValue = String(a[sortColumn]); const bValue = String(b[sortColumn]); @@ -120,21 +137,21 @@ const CapacityGroupsList: React.FC = () => { if (sortOrder === 'desc') { // Reverse the array if the sort order is descending - sortedcapacitygroups.reverse(); + sortedCapacityGroups.reverse(); } } - return sortedcapacitygroups; + setFilteredCapacityGroups(sortedCapacityGroups); }, [capacitygroups, searchQuery, sortColumn, sortOrder]); const slicedcapacitygroups = useMemo(() => { const indexOfLastCapacityGroup = currentPage * capacitygroupsPerPage; const indexOfFirstCapacityGroup = indexOfLastCapacityGroup - capacitygroupsPerPage; - return filteredcapacitygroups.slice(indexOfFirstCapacityGroup, indexOfLastCapacityGroup); - }, [filteredcapacitygroups, currentPage, capacitygroupsPerPage]); + return filteredCapacityGroups.slice(indexOfFirstCapacityGroup, indexOfLastCapacityGroup); + }, [filteredCapacityGroups, currentPage, capacitygroupsPerPage]); - const totalPagesNum = useMemo(() => Math.ceil(filteredcapacitygroups.length / capacitygroupsPerPage), [ - filteredcapacitygroups, + const totalPagesNum = useMemo(() => Math.ceil(filteredCapacityGroups.length / capacitygroupsPerPage), [ + filteredCapacityGroups, capacitygroupsPerPage, ]); @@ -143,19 +160,26 @@ const CapacityGroupsList: React.FC = () => { slicedcapacitygroups.map((capacitygroup) => ( - + toggleFavorite(capacitygroup.internalId)} size={25} - /> + /> + - + Go to Details} + > + + { Down ) : - capacitygroup.linkStatus === EventType.GENERAL_EVENT ? ( - - General - - ) + capacitygroup.linkStatus === EventType.GENERAL_EVENT ? ( + + General + + ) : ( - N/A - )} + N/A + )} @@ -256,7 +280,7 @@ const CapacityGroupsList: React.FC = () => { pages={totalPagesNum} setCurrentPage={setCurrentPage} currentItems={slicedcapacitygroups} - items={filteredcapacitygroups} + items={filteredCapacityGroups} />
diff --git a/demand-capacity-mgmt-frontend/src/components/demands/DemandList.tsx b/demand-capacity-mgmt-frontend/src/components/demands/DemandList.tsx index 2e978db6..2e1c394d 100644 --- a/demand-capacity-mgmt-frontend/src/components/demands/DemandList.tsx +++ b/demand-capacity-mgmt-frontend/src/components/demands/DemandList.tsx @@ -20,20 +20,21 @@ * ******************************************************************************** */ -import React, {useCallback, useContext, useEffect, useMemo, useState} from 'react'; -import {Button, Col, Dropdown, Form, Row} from 'react-bootstrap'; -import {FaCopy, FaEllipsisV, FaInfoCircle, FaSearch, FaStar, FaTrashAlt} from 'react-icons/fa'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { Button, Col, Dropdown, Form, Row } from 'react-bootstrap'; +import { FaCopy, FaEllipsisV, FaInfoCircle, FaSearch, FaTrashAlt } from 'react-icons/fa'; +import { LuStar } from 'react-icons/lu'; import CapacityGroupsProvider from '../../contexts/CapacityGroupsContextProvider'; -import {DemandContext} from '../../contexts/DemandContextProvider'; -import {FavoritesContext} from "../../contexts/FavoritesContextProvider"; -import {FavoriteType, MaterialDemandFavoriteResponse} from "../../interfaces/Favorite_interface"; +import { DemandContext } from '../../contexts/DemandContextProvider'; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; import UnitsofMeasureContextContextProvider from '../../contexts/UnitsOfMeasureContextProvider'; -import {DemandProp, DemandSeries, DemandSeriesValue} from '../../interfaces/demand_interfaces'; -import {EventType} from '../../interfaces/event_interfaces'; +import { DemandProp, DemandSeries, DemandSeriesValue } from '../../interfaces/demand_interfaces'; +import { EventType } from '../../interfaces/event_interfaces'; +import { FavoriteType, MaterialDemandFavoriteResponse } from "../../interfaces/favorite_interface"; import CapacityGroupAddToExisting from '../capacitygroup/CapacityGroupAddToExisting'; import CapacityGroupWizardModal from '../capacitygroup/CapacityGroupWizardModal'; -import DangerConfirmationModal, {ConfirmationAction} from '../common/DangerConfirmationModal'; -import {LoadingMessage} from '../common/LoadingMessages'; +import DangerConfirmationModal, { ConfirmationAction } from '../common/DangerConfirmationModal'; +import { LoadingMessage } from '../common/LoadingMessages'; import Pagination from '../common/Pagination'; import DemandDetailsModal from './DemandDetailsModal'; import DemandListTable from './DemandListTable'; @@ -72,27 +73,28 @@ const DemandList: React.FC<{ const [selectedDemands, setSelectedDemands] = useState([]); const [currentPage, setCurrentPage] = useState(1);//Its updated from showWizard - const [sortColumn, setSortColumn] = useState('changedAt'); + const [sortColumn, setSortColumn] = useState(null); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [demandsPerPage, setDemandsPerPage] = useState(6); //Only show 5 items by default const [filteredDemands, setFilteredDemands] = useState([]); const fetchFavorites = async () => { - try { - const favorites = await fetchFavoritesByType(FavoriteType.MATERIAL_DEMAND); - console.log(favorites) - if (favorites && favorites.materialDemands) { - setFavoriteDemands(favorites.materialDemands.map((fav: MaterialDemandFavoriteResponse) => fav.id)); - } - } catch (error) { - console.error('Error fetching favorites by type in DemandList:', error); + try { + const favorites = await fetchFavoritesByType(FavoriteType.MATERIAL_DEMAND); + console.log(favorites) + if (favorites && favorites.materialDemands) { + setFavoriteDemands(favorites.materialDemands.map((fav: MaterialDemandFavoriteResponse) => fav.id)); } + } catch (error) { + console.error('Error fetching favorites by type in DemandList:', error); + } }; useEffect(() => { - setShowWizardModal(showWizard || false); - fetchDemandProps(); - fetchFavorites(); + setShowWizardModal(showWizard || false); + fetchDemandProps(); + fetchFavorites(); }, [showWizard]); @@ -169,11 +171,13 @@ const DemandList: React.FC<{ const handleCloseDetails = () => setShowDetailsModal(false); + const isDemandFavorited = (demandId: string) => favoriteDemands.includes(demandId); + useMemo(() => { - let sortedDemands = [...demandprops]; + let filteredDemands = [...demandprops]; if (searchQuery !== '') { - sortedDemands = sortedDemands.filter((demand) => + filteredDemands = filteredDemands.filter((demand) => demand.materialDescriptionCustomer.toLowerCase().includes(searchQuery.toLowerCase()) || demand.id.toString().includes(searchQuery.toLowerCase()) || demand.customer.bpn.toString().toLowerCase().includes(searchQuery.toLowerCase()) || @@ -182,29 +186,37 @@ const DemandList: React.FC<{ ); } + // Separate favorited and unfavorited demands + const favoritedDemands = filteredDemands.filter((demand) => isDemandFavorited(demand.id)); + const unfavoritedDemands = filteredDemands.filter((demand) => !isDemandFavorited(demand.id)); + + // Sort favorited demands by changedAt timestamp in descending order + favoritedDemands.sort((a, b) => new Date(b.changedAt).getTime() - new Date(a.changedAt).getTime()); + + // Sort unfavorited demands by changedAt timestamp in descending order + unfavoritedDemands.sort((a, b) => new Date(b.changedAt).getTime() - new Date(a.changedAt).getTime()); + + // Concatenate favorited and unfavorited demands + const sortedDemands = [...favoritedDemands, ...unfavoritedDemands]; + if (sortColumn) { + // Sort the concatenated array by the specified column sortedDemands.sort((a, b) => { const aValue = a[sortColumn]; const bValue = b[sortColumn]; - if (sortColumn === 'changedAt') { - const timestampA = aValue instanceof Date ? aValue.getTime() : typeof aValue === 'string' ? new Date(aValue).getTime() : 0; - const timestampB = bValue instanceof Date ? bValue.getTime() : typeof bValue === 'string' ? new Date(bValue).getTime() : 0; - - return sortOrder === 'asc' ? timestampB - timestampA : timestampA - timestampB; - } else { - // For other columns, perform string or numeric comparison - if (typeof aValue === 'string' && typeof bValue === 'string') { - // Sort strings alphabetically - return aValue.localeCompare(bValue, undefined, { sensitivity: 'base' }); - } else if (typeof aValue === 'number' && typeof bValue === 'number') { - // Sort numbers numerically - return aValue - bValue; - } - - // If the types are not string or number, return 0 (no sorting) - return 0; + // For other columns, perform string or numeric comparison + if (typeof aValue === 'string' && typeof bValue === 'string') { + // Sort strings alphabetically + return aValue.localeCompare(bValue, undefined, { sensitivity: 'base' }); + } else if (typeof aValue === 'number' && typeof bValue === 'number') { + // Sort numbers numerically + return aValue - bValue; } + + // If the types are not string or number, return 0 (no sorting) + return 0; + }); if (sortOrder === 'desc') { @@ -214,7 +226,7 @@ const DemandList: React.FC<{ } setFilteredDemands(sortedDemands); - }, [demandprops, searchQuery]); + }, [demandprops, searchQuery, sortColumn, sortOrder]); const slicedDemands = useMemo(() => { // Use filteredDemandsByEventTypes instead of filteredDemands for slicing and rendering @@ -247,15 +259,15 @@ const DemandList: React.FC<{ ); const toggleFavorite = async (demandId: string) => { - if (favoriteDemands.includes(demandId)) { - await deleteFavorite(demandId) - setFavoriteDemands(prev => prev.filter(id => id !== demandId)); - } else { - await addFavorite(demandId, FavoriteType.MATERIAL_DEMAND) - setFavoriteDemands(prev => [...prev, demandId]); - } - fetchFavorites(); - fetchDemandProps(); + if (favoriteDemands.includes(demandId)) { + await deleteFavorite(demandId) + setFavoriteDemands(prev => prev.filter(id => id !== demandId)); + } else { + await addFavorite(demandId, FavoriteType.MATERIAL_DEMAND) + setFavoriteDemands(prev => [...prev, demandId]); + } + fetchFavorites(); + fetchDemandProps(); }; @@ -271,14 +283,16 @@ const DemandList: React.FC<{ checked={selectedDemands.includes(demand)} // Check if the demand is in the selectedDemands array /> - - toggleFavorite(demand.id)} - size={25} - /> - + + + toggleFavorite(demand.id)} + size={25} + /> + + @@ -285,16 +296,16 @@ const DemandManagement: React.FC = () => { {demand.linkStatus === EventType.LINKED ? ( - Linked - + Linked + ) : demand.linkStatus === EventType.TODO ? ( - TODO - + TODO + ) : demand.linkStatus === EventType.UN_LINKED ? ( - Unlinked - + Unlinked + ) : ( N/A )} @@ -305,16 +316,16 @@ const DemandManagement: React.FC = () => { - + handleDetails(demand)}> Details + onClick={() => handleDetails(demand)}> Details { navigator.clipboard.writeText(demand.id) - }}> Copy ID + }}> Copy ID handleDeleteButtonClick(demand.id)}> Delete + onClick={() => handleDeleteButtonClick(demand.id)}> Delete @@ -328,22 +339,22 @@ const DemandManagement: React.FC = () => {
- +
{isLoading ? ( // Conditional rendering based on loading state - + ) : ( <>
@@ -402,7 +413,7 @@ const DemandManagement: React.FC = () => { {selectedDemand && setIsEditModalOpen(false)}/>} + onCloseModal={() => setIsEditModalOpen(false)} />} @@ -420,7 +431,7 @@ const DemandManagement: React.FC = () => { New Material Demand - + @@ -441,7 +452,7 @@ const DemandManagement: React.FC = () => { onHide={handleCloseDetails} dialogClassName="custom-modal" fullscreen="xl" - selectedDemand={selectedDemand}/> + selectedDemand={selectedDemand} /> )} diff --git a/demand-capacity-mgmt-frontend/src/components/events/EventsTable.tsx b/demand-capacity-mgmt-frontend/src/components/events/EventsTable.tsx index fbd617f9..b731f1a9 100644 --- a/demand-capacity-mgmt-frontend/src/components/events/EventsTable.tsx +++ b/demand-capacity-mgmt-frontend/src/components/events/EventsTable.tsx @@ -28,18 +28,13 @@ import { FaArrowUp, FaCopy, FaEllipsisV, - FaEnvelope, - FaExclamation, - FaLink, - FaStar, - FaTrashAlt, - FaUnlink, - FaWrench + FaTrashAlt } from 'react-icons/fa'; +import { LuStar } from 'react-icons/lu'; import { EventsContext } from '../../contexts/EventsContextProvider'; import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; -import { EventFavoriteResponse, FavoriteType } from "../../interfaces/Favorite_interface"; -import { EventProp } from '../../interfaces/event_interfaces'; +import { EventProp, eventTypeIcons } from '../../interfaces/event_interfaces'; +import { EventFavoriteResponse, FavoriteType } from "../../interfaces/favorite_interface"; import DangerConfirmationModal, { ConfirmationAction } from '../common/DangerConfirmationModal'; import Pagination from '../common/Pagination'; @@ -86,17 +81,6 @@ const EventsTable: React.FC = ({ events, isArchive }) => { fetchFavorites(); }; - const eventTypeIcons: { [key: string]: React.ReactNode } = { - GENERAL_EVENT: , - TODO: , - ALERT: , - STATUS_IMPROVEMENT: , - STATUS_REDUCTION: , - LINKED: , - UN_LINKED: , - }; - - const handleSort = (field: string) => { if (sortField === field) { setSortOrder(prevOrder => (prevOrder === 'asc' ? 'desc' : 'asc') as 'asc' | 'desc'); @@ -231,12 +215,13 @@ const EventsTable: React.FC = ({ events, isArchive }) => { {currentEvents.map((event, index) => ( - toggleFavorite(event.logID)} - size={25} - /> + + toggleFavorite(event.logID)} + size={25} + /> {new Date(event.timeCreated).toLocaleString()} { - data: T[]; - headings: string[]; - renderRow: (item: T, index: number) => JSX.Element[]; - onButtonClick?: (item: T) => void; -} - -const FavoritesTable = ({ - data, - headings, - renderRow, - onButtonClick - }: FavoritesTableProps) => { - return ( -
- - - - - - {headings.map((heading, index) => ( - - ))} - - - - {data.map((item, index) => ( - - - - {renderRow(item, index).map((cell, cellIndex) => ( - - ))} - - ))} - -
#Action{heading}
{index + 1} - - {cell}
-
- ); -}; - -export default FavoritesTable; - diff --git a/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableCapacityGroup.tsx b/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableCapacityGroup.tsx new file mode 100644 index 00000000..a9fae27b --- /dev/null +++ b/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableCapacityGroup.tsx @@ -0,0 +1,206 @@ +/* + * ****************************************************************************** + * Copyright (c) 2023 BMW AG + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************* + */ + +import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { Button, Col, Form, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'; +import { + FaArrowDown, + FaArrowUp, + FaCopy, + FaEye +} from 'react-icons/fa'; +import { LuStarOff } from "react-icons/lu"; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; +import { SingleCapacityGroupFavoriteResponse } from '../../interfaces/favorite_interface'; +import Pagination from '../common/Pagination'; +interface FavoriteTableMaterialDemandsProps { + favcapacitygroups: SingleCapacityGroupFavoriteResponse[]; +} + +const FavoritesTableCapacityGroup: React.FC = ({ favcapacitygroups }) => { + const [sortField, setSortField] = useState('changedAt'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [currentPage, setCurrentPage] = useState(1); + const [eventsPerPage, setEventsPerPage] = useState(5); + + const { deleteFavorite, fetchFavorites } = useContext(FavoritesContext)!; + + const handleSort = (field: string) => { + if (sortField === field) { + setSortOrder(prevOrder => (prevOrder === 'asc' ? 'desc' : 'asc') as 'asc' | 'desc'); + } else { + setSortField(field); + setSortOrder('desc'); + } + }; + + + const sortedData = useMemo(() => { + const sortedArray = [...favcapacitygroups].sort((a, b) => { + let comparison = 0; + // if (sortField === 'changedAt' && a && b.changedAt) { + // const dateA = new Date(a.changedAt).getTime(); + // const dateB = new Date(b.changedAt).getTime(); + // comparison = dateB - dateA; // Most recent first + // } else + if (sortField !== 'timestamp' && a[sortField as keyof SingleCapacityGroupFavoriteResponse] && b[sortField as keyof SingleCapacityGroupFavoriteResponse]) { + const fieldA = a[sortField as keyof SingleCapacityGroupFavoriteResponse] as string; + const fieldB = b[sortField as keyof SingleCapacityGroupFavoriteResponse] as string; + comparison = fieldA.localeCompare(fieldB); + } + return sortOrder === 'asc' ? comparison : -comparison; + }); + return sortedArray; + }, [favcapacitygroups, sortField, sortOrder]); + + const indexOfLastEvent = currentPage * eventsPerPage; + const indexOfFirstEvent = indexOfLastEvent - eventsPerPage; + const currentEvents = sortedData.slice(indexOfFirstEvent, indexOfLastEvent); + const totalPagesNum = Math.ceil(sortedData.length / eventsPerPage); + + const handleUnfavorite = useCallback( + async (id: string) => { + try { + await deleteFavorite(id) + fetchFavorites(); + } catch (error) { + console.error('Error Unfavoriting:', error); + } + }, + [favcapacitygroups] + ); + + return ( + <> +
+ + + + + + + + + + + + + {favcapacitygroups.map((cg, index) => ( + + + + + + + + + ))} + +
+ Capacity Group Name {sortField === 'timestamp' ? (sortOrder === 'asc' ? : + ) : '-'} + + Customer BPNL {sortField === 'eventId' ? (sortOrder === 'asc' ? : + ) : '-'} + + Supplier BPNL {sortField === 'objectId' ? (sortOrder === 'asc' ? : + ) : '-'} +
+ + handleUnfavorite(cg.capacityGroupId)} + size={25} + /> + + + + Open details} + > + + + + {cg.capacityGroupId}}> + + + {cg.capacityGroupName}{cg.customer}{cg.supplier}
+
+
+
+ +
+
+
+ + + Per Page: + + + setEventsPerPage(Number(e.target.value))} + /> + + +
+
+
+
+
+ + ); +}; + +export default FavoritesTableCapacityGroup; + + + diff --git a/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableEvents.tsx b/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableEvents.tsx new file mode 100644 index 00000000..268317e6 --- /dev/null +++ b/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableEvents.tsx @@ -0,0 +1,208 @@ +/* + * ****************************************************************************** + * Copyright (c) 2023 BMW AG + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************* + */ + +import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { Button, Col, Form, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'; +import { + FaArrowDown, + FaArrowUp, + FaCopy +} from 'react-icons/fa'; +import { LuStarOff } from "react-icons/lu"; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; +import { eventTypeIcons } from '../../interfaces/event_interfaces'; +import { EventFavoriteResponse } from '../../interfaces/favorite_interface'; +import Pagination from '../common/Pagination'; +interface FavoriteTableEventsProps { + events: EventFavoriteResponse[]; +} + +const FavoritesTableEvents: React.FC = ({ events }) => { + const [sortField, setSortField] = useState('changedAt'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [currentPage, setCurrentPage] = useState(1); + const [eventsPerPage, setEventsPerPage] = useState(20); + + const { deleteFavorite, fetchFavorites } = useContext(FavoritesContext)!; + + const handleSort = (field: string) => { + if (sortField === field) { + setSortOrder(prevOrder => (prevOrder === 'asc' ? 'desc' : 'asc') as 'asc' | 'desc'); + } else { + setSortField(field); + setSortOrder('desc'); + } + }; + + + const sortedData = useMemo(() => { + const sortedArray = [...events].sort((a, b) => { + let comparison = 0; + if (sortField === 'changedAt' && a.timeCreated && b.timeCreated) { + const dateA = new Date(a.timeCreated).getTime(); + const dateB = new Date(b.timeCreated).getTime(); + comparison = dateB - dateA; // Most recent first + } else if (sortField !== 'timestamp' && a[sortField as keyof EventFavoriteResponse] && b[sortField as keyof EventFavoriteResponse]) { + const fieldA = a[sortField as keyof EventFavoriteResponse] as string; + const fieldB = b[sortField as keyof EventFavoriteResponse] as string; + comparison = fieldA.localeCompare(fieldB); + } + return sortOrder === 'asc' ? comparison : -comparison; + }); + return sortedArray; + }, [events, sortField, sortOrder]); + + const indexOfLastEvent = currentPage * eventsPerPage; + const indexOfFirstEvent = indexOfLastEvent - eventsPerPage; + const currentEvents = sortedData.slice(indexOfFirstEvent, indexOfLastEvent); + const totalPagesNum = Math.ceil(sortedData.length / eventsPerPage); + + const handleUnfavorite = useCallback( + async (id: string) => { + try { + await deleteFavorite(id) + fetchFavorites(); + } catch (error) { + console.error('Error Unfavoriting:', error); + } + }, + [events] + ); + + const generateOverlay = (event: EventFavoriteResponse): React.ReactElement => { + const eventTypeIcon = eventTypeIcons[event.eventType]; + if (eventTypeIcon) { + return ( + {event.eventType}} + > + {eventTypeIcon} + + ); + } + return {event.eventType}; + }; + + return ( + <> +
+ + + + + + + + + + + + + {events.map((event, index) => ( + + + + + + + + + ))} + +
handleSort('timeCreated')}> + Timestamp {sortField === 'timeCreated' ? (sortOrder === 'asc' ? : + ) : '-'} + + Event ID {sortField === 'id' ? (sortOrder === 'asc' ? : + ) : '-'} + + Type {sortField === 'eventType' ? (sortOrder === 'asc' ? : + ) : '-'} + + Description {sortField === 'eventDescription' ? (sortOrder === 'asc' ? : + ) : '-'} + + User {sortField === 'userAccount' ? (sortOrder === 'asc' ? : + ) : '-'} +
+ + handleUnfavorite(event.logID)} + size={25} + /> + + + {new Date(event.timeCreated).toLocaleString()}{event.id}}> + {generateOverlay(event)}{event.description}{event.userAccount}
+
+
+
+ +
+
+
+ + + Per Page: + + + setEventsPerPage(Number(e.target.value))} + /> + + +
+
+
+
+
+ + ); +}; + +export default FavoritesTableEvents; + + + diff --git a/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableMaterialDemands.tsx b/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableMaterialDemands.tsx new file mode 100644 index 00000000..adffe88d --- /dev/null +++ b/demand-capacity-mgmt-frontend/src/components/favorites/FavoritesTableMaterialDemands.tsx @@ -0,0 +1,187 @@ +/* + * ****************************************************************************** + * Copyright (c) 2023 BMW AG + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************* + */ + +import React, { useCallback, useContext, useMemo, useState } from 'react'; +import { Button, Col, Form, OverlayTrigger, Row, Tooltip } from 'react-bootstrap'; +import { + FaCopy, + FaEye +} from 'react-icons/fa'; +import { LuStarOff } from "react-icons/lu"; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; +import { MaterialDemandFavoriteResponse } from '../../interfaces/favorite_interface'; +import Pagination from '../common/Pagination'; +interface FavoriteTableMaterialDemandsProps { + materialdemands: MaterialDemandFavoriteResponse[]; +} + +const FavoriteTableMaterialDemands: React.FC = ({ materialdemands }) => { + const [sortField, setSortField] = useState('changedAt'); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const [currentPage, setCurrentPage] = useState(1); + const [eventsPerPage, setEventsPerPage] = useState(20); + + const { deleteFavorite, fetchFavorites } = useContext(FavoritesContext)!; + + const handleSort = useCallback((field: string) => { + setSortField(field); + setSortOrder(prevOrder => (prevOrder === 'asc' ? 'desc' : 'asc') as 'asc' | 'desc'); + }, []); + + + const sortedData = useMemo(() => { + const sortedArray = [...materialdemands].sort((a, b) => { + let comparison = 0; + if (sortField === 'changedAt' && a.changedAt && b.changedAt) { + const dateA = new Date(a.changedAt).getTime(); + const dateB = new Date(b.changedAt).getTime(); + comparison = dateB - dateA; // Most recent first + } + // if (sortField !== 'changedAt' && a[sortField as keyof MaterialDemandFavoriteResponse] && b[sortField as keyof MaterialDemandFavoriteResponse]) { + // const fieldA = a[sortField as keyof MaterialDemandFavoriteResponse]; + // const fieldB = b[sortField as keyof MaterialDemandFavoriteResponse]; + // comparison = fieldA.localeCompare(fieldB); + // } + return sortOrder === 'asc' ? comparison : -comparison; + }); + return sortedArray; + }, [materialdemands, sortField, sortOrder]); + + const indexOfLastEvent = currentPage * eventsPerPage; + const indexOfFirstEvent = indexOfLastEvent - eventsPerPage; + const currentEvents = sortedData.slice(indexOfFirstEvent, indexOfLastEvent); + const totalPagesNum = Math.ceil(sortedData.length / eventsPerPage); + + const handleUnfavorite = useCallback( + async (id: string) => { + try { + await deleteFavorite(id) + fetchFavorites(); + } catch (error) { + console.error('Error Unfavoriting:', error); + } + }, + [materialdemands] + ); + + return ( + <> +
+ + + + + + + + + + + + + {materialdemands.map((md, index) => ( + + + + + + + + + + ))} + +
Material No. Customer Material No. SupplierDescription
+ + handleUnfavorite(md.id)} + size={25} + /> + + + + Go to Demand}> + + + {md.id}}> + + {md.materialNumberCustomer}{md.materialNumberSupplier}{md.materialDescriptionCustomer}
+
+
+
+ +
+
+
+ + + Per Page: + + + setEventsPerPage(Number(e.target.value))} + /> + + +
+
+
+
+
+ + ); +}; + +export default FavoriteTableMaterialDemands; + + + diff --git a/demand-capacity-mgmt-frontend/src/components/pages/EventsPage.tsx b/demand-capacity-mgmt-frontend/src/components/pages/EventsPage.tsx index aa7efd24..87bf6700 100644 --- a/demand-capacity-mgmt-frontend/src/components/pages/EventsPage.tsx +++ b/demand-capacity-mgmt-frontend/src/components/pages/EventsPage.tsx @@ -1,24 +1,43 @@ +/* + * ******************************************************************************* + * Copyright (c) 2023 BMW AG + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************** + */ import { useContext, useEffect, useState } from "react"; import { Button, Tab, Tabs } from "react-bootstrap"; import { FaFilter, FaRedo } from "react-icons/fa"; import { FcTimeline } from "react-icons/fc"; +import { LuStar } from "react-icons/lu"; import Creatable from 'react-select/creatable'; import { EventsContext } from "../../contexts/EventsContextProvider"; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; import { EventProp } from "../../interfaces/event_interfaces"; import DangerConfirmationModal, { ConfirmationAction } from "../common/DangerConfirmationModal"; import { LoadingMessage } from "../common/LoadingMessages"; import EventsTable from "../events/EventsTable"; -import {FavoritesContext} from "../../contexts/FavoritesContextProvider"; -import { - EventFavoriteResponse, - FavoriteType, - SingleCapacityGroupFavoriteResponse -} from "../../interfaces/Favorite_interface"; function EventsPage() { const [activeTab, setActiveTab] = useState("Events"); const [userInput, setUserInput] = useState(''); const { events, archiveEvents, fetchEvents, fetchFilteredEvents, deleteAllEvents } = useContext(EventsContext)!; + const { fetchFavoritesByType } = useContext(FavoritesContext)!; + const [options, setOptions] = useState([]); // State to store options for Creatable component const [showConfirmationModal, setShowConfirmationModal] = useState(false); const [loading, setLoading] = useState(false); const [filteredEvents, setFilteredEvents] = useState(events); @@ -26,9 +45,27 @@ function EventsPage() { const handleUserInputChange = (newValue: string | undefined) => { setUserInput(newValue || ''); - setFilteredEvents(newValue === undefined ? events : []); - }; + // Filter events based on userInput (id, capacityGroupId, or objectId) + const filteredEvents: EventProp[] = events.filter((event) => { + if (newValue) { + const lowerCaseValue = newValue.toLowerCase(); + + // Check if the input is a number + const isNumber = !isNaN(Number(lowerCaseValue)); + + return ( + (isNumber && event.id.toString().includes(lowerCaseValue)) || + (!isNumber && + ((event.capacityGroupId && event.capacityGroupId.toString().includes(lowerCaseValue)) || + (event.id && event.id.toString().includes(lowerCaseValue)))) + ); + } + return true; // Show all events if userInput is empty + }); + + setFilteredEvents(filteredEvents); + }; const handleRefreshClick = () => { fetchEvents(); // Call your fetchEvents function to refresh the data @@ -47,6 +84,7 @@ function EventsPage() { setShowConfirmationModal(false); }; + useEffect(() => { const fetchData = async () => { try { @@ -62,15 +100,54 @@ function EventsPage() { filteredEvents = events; // Show all events if userInput is empty } setFilteredEvents(filteredEvents); + + console.log(filteredEvents) + } catch (error) { console.error('Error fetching events:', error); } finally { setLoading(false); } + + try { + setLoading(true); + let eventOptions: any[] = []; + const response = await fetchFavoritesByType('EVENT'); + eventOptions = response?.events?.map((event: any) => { + // Create a short timestamp (you can adjust the format as per your requirement) + const options: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'numeric', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }; + const shortTimestamp = new Date(event.timeCreated).toLocaleString(undefined, options); + + // Combine short timestamp, eventType, and customer in the label + const label = ( +
+ {shortTimestamp} | {event.eventType} | {event.description} +
+ ); + + return { + value: event.id, + label: label, + }; + }) || []; + setOptions(eventOptions); // Update options state with event descriptions + } catch (error) { + console.error('Error fetching filtered events:', error); + } finally { + setLoading(false); + } + }; fetchData(); - }, [userInput, events, fetchFilteredEvents]); + }, [fetchFilteredEvents]); + return ( @@ -105,6 +182,7 @@ function EventsPage() { Filter
} + options={options} // Handle user input changes and call the filtering function onChange={(selectedOption) => handleUserInputChange(selectedOption?.value)} /> diff --git a/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx b/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx index 7633cbb0..433a1ddd 100644 --- a/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx +++ b/demand-capacity-mgmt-frontend/src/components/pages/FavoritesPage.tsx @@ -1,93 +1,81 @@ -import React, {useContext} from "react"; +/* + * ******************************************************************************* + * Copyright (c) 2023 BMW AG + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************** + */ +import React, { useContext } from "react"; import { Tab, Tabs } from "react-bootstrap"; -import FavoritesTable from "../favorites/FavoritesTable"; -import { - MaterialDemandFavoriteResponse, - SingleCapacityGroupFavoriteResponse, - CompanyDtoFavoriteResponse, - EventFavoriteResponse -} from "../../interfaces/Favorite_interface"; -import {FavoritesContext} from "../../contexts/FavoritesContextProvider"; -import {BsFillBookmarkStarFill} from "react-icons/bs"; +import { BsFillBookmarkStarFill } from "react-icons/bs"; +import { FavoritesContext } from "../../contexts/FavoritesContextProvider"; +import { LoadingMessage } from "../common/LoadingMessages"; +import FavoritesTableCapacityGroup from "../favorites/FavoritesTableCapacityGroup"; +import FavoritesTableEvents from "../favorites/FavoritesTableEvents"; +import FavoriteTableMaterialDemands from "../favorites/FavoritesTableMaterialDemands"; const FavoritesPage: React.FC = () => { - const favoritesContext = useContext(FavoritesContext); - - if (!favoritesContext) { - return
Loading...
; - } - - const { favorites } = favoritesContext; + const { favorites } = useContext(FavoritesContext)!; if (!favorites) { - return
Loading...
; + return ; } return ( <> -
-
-
- -

Favorites

-
-
- - - [ - {item.materialDescriptionCustomer}, - {item.materialNumberCustomer}, - {item.materialNumberSupplier}, - {item.customer}, - {item.supplier}, - {item.unitOfMeasure}, - ]} - /> - - - [ - {item.customer}, - {item.supplier}, - {item.capacityGroupId}, - {item.capacityGroupName}, - ]} - /> - - - [ - {item.bpn}, - {item.companyName}, - {item.zipCode}, - {item.country}, - {item.myCompany}, - ]} - /> - - - [ - {item.id}, - {item.eventType}, - {item.timeCreated}, - {item.userAccount}, - {item.description} - ]} - /> - - -
-
+
+
+
+ +

My Favorites

+
+
+ + + + + + + + + {/* [ + {item.bpn}, + {item.companyName}, + {item.zipCode}, + {item.country}, + {item.myCompany}, + ]} + /> */} + + + + + +
+
); }; diff --git a/demand-capacity-mgmt-frontend/src/contexts/EventsContextProvider.tsx b/demand-capacity-mgmt-frontend/src/contexts/EventsContextProvider.tsx index 499c142d..ae6018a2 100644 --- a/demand-capacity-mgmt-frontend/src/contexts/EventsContextProvider.tsx +++ b/demand-capacity-mgmt-frontend/src/contexts/EventsContextProvider.tsx @@ -112,7 +112,6 @@ const EventsContextProvider: React.FC> = (props) => const archiveLog = async (event: EventProp) => { try { - console.log(event) const api = createAPIInstance(access_token); await api.post('/loggingHistory/archivedLog', event); } catch (error) { diff --git a/demand-capacity-mgmt-frontend/src/contexts/FavoritesContextProvider.tsx b/demand-capacity-mgmt-frontend/src/contexts/FavoritesContextProvider.tsx index f3f53a83..02c4098f 100644 --- a/demand-capacity-mgmt-frontend/src/contexts/FavoritesContextProvider.tsx +++ b/demand-capacity-mgmt-frontend/src/contexts/FavoritesContextProvider.tsx @@ -22,13 +22,12 @@ import React, { createContext, useEffect, useState } from 'react'; -import { FavoriteResponse } from '../interfaces/Favorite_interface'; +import { FavoritePayload, FavoriteResponse, FavoriteType } from '../interfaces/favorite_interface'; import createAPIInstance from "../util/Api"; import { useUser } from './UserContext'; -import { FavoritePayload,FavoriteType } from '../interfaces/Favorite_interface'; interface FavoritesContextData { - favorites: FavoriteResponse | null | undefined; + favorites: FavoriteResponse | null; fetchFavorites: () => Promise; // If you've added a refresh function refresh?: () => void; @@ -37,11 +36,11 @@ interface FavoritesContextData { fetchFavoritesByType: (type: string) => Promise; } -export const FavoritesContext = createContext(undefined); +export const FavoritesContext = createContext(undefined); const FavoritesContextProvider: React.FC> = (props) => { - const [favorites, setFavorites] = useState(undefined); + const [favorites, setFavorites] = useState(null); const { access_token } = useUser(); @@ -50,7 +49,6 @@ const FavoritesContextProvider: React.FC> = (props) const fetchFavorites = async () => { try { const response = await api.get('/favorite'); - console.log('Fetched data:', response.data); setFavorites(response.data); } catch (error) { console.error('Error fetching event history:', error); @@ -60,7 +58,6 @@ const FavoritesContextProvider: React.FC> = (props) const fetchFavoritesByType = async (type: string): Promise => { try { const response = await api.get(`/favorite/${type}`); - console.log('Fetched favorites by type:', response.data); setFavorites(response.data); return response.data; } catch (error) { diff --git a/demand-capacity-mgmt-frontend/src/contexts/InfoMenuContextProvider.tsx b/demand-capacity-mgmt-frontend/src/contexts/InfoMenuContextProvider.tsx index 263aef58..e920d993 100644 --- a/demand-capacity-mgmt-frontend/src/contexts/InfoMenuContextProvider.tsx +++ b/demand-capacity-mgmt-frontend/src/contexts/InfoMenuContextProvider.tsx @@ -22,11 +22,11 @@ import React, { FunctionComponent, createContext, useCallback, useContext, useEffect, useState } from 'react'; +import { InfoMenuData } from '../interfaces/infomenu_interfaces'; +import createAPIInstance from "../util/Api"; import { CapacityGroupContext } from './CapacityGroupsContextProvider'; import { DemandContext } from './DemandContextProvider'; import { EventsContext } from './EventsContextProvider'; -import { InfoMenuData } from '../interfaces/InfoMenu_interfaces'; -import createAPIInstance from "../util/Api"; import { useUser } from "./UserContext"; interface InfoMenuContextData { diff --git a/demand-capacity-mgmt-frontend/src/index.css b/demand-capacity-mgmt-frontend/src/index.css index 1f295da1..c0a2222d 100644 --- a/demand-capacity-mgmt-frontend/src/index.css +++ b/demand-capacity-mgmt-frontend/src/index.css @@ -215,16 +215,20 @@ code { } -/*Something else*/ .table-checkbox { + display: flex; + margin-top: 0.7rem; + margin-left: 50%; -ms-transform: scale(1.5); - /* IE */ -moz-transform: scale(1.5); - /* FF */ -webkit-transform: scale(1.5); - /* Safari and Chrome */ -o-transform: scale(1.5); - /* Opera */ +} + +.inlinefav { + margin-top: 0.25rem; + margin-left: 0.7em; + display: flex; } .red-delete-item { diff --git a/demand-capacity-mgmt-frontend/src/interfaces/event_interfaces.tsx b/demand-capacity-mgmt-frontend/src/interfaces/event_interfaces.tsx index eaf7ca3e..430420ec 100644 --- a/demand-capacity-mgmt-frontend/src/interfaces/event_interfaces.tsx +++ b/demand-capacity-mgmt-frontend/src/interfaces/event_interfaces.tsx @@ -20,6 +20,8 @@ * ******************************************************************************** */ +import { FaArrowDown, FaArrowUp, FaEnvelope, FaExclamation, FaLink, FaUnlink, FaWrench } from "react-icons/fa" + export interface EventProp { id: number @@ -43,4 +45,14 @@ export enum EventType { STATUS_REDUCTION = 'STATUS_REDUCTION', LINKED = 'LINKED', UN_LINKED = 'UN_LINKED' -} \ No newline at end of file +} + +export const eventTypeIcons: { [key: string]: React.ReactNode } = { + GENERAL_EVENT: , + TODO: , + ALERT: , + STATUS_IMPROVEMENT: , + STATUS_REDUCTION: , + LINKED: , + UN_LINKED: , +}; \ No newline at end of file