diff --git a/docker-compose.yml b/docker-compose.yml index faeadff7..ab4be392 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ x-frontend: &frontend VITE_ZONE: DEV VITE_USER_POOLS_ID: ca-central-1_t2HSZBHur VITE_USER_POOLS_WEB_CLIENT_ID: 3g6n2ha1loi4kp1jhaq359vrvb + VITE_BACKEND_URL: http://localhost:8080 healthcheck: test: curl http://localhost:3000" interval: 15s diff --git a/frontend/src/components/ActionsTable/index.jsx b/frontend/src/components/ActionsTable/index.jsx deleted file mode 100644 index e4c37c60..00000000 --- a/frontend/src/components/ActionsTable/index.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from "@carbon/react"; -import StatusTag from "../StatusTag"; - -import ActivityTag from "../ActivityTag"; -const ActionsTable = ({rows,headers}) => { - return ( - - - - {headers.map(header => - {header.header} - )} - - - - {rows.map((row, idx) => - {Object.keys(row).filter(key => key !== 'id').map(key => { - return ( - - {key === "status" ? ( - - ): - key === "activityType" && !row["fileFormat"] ? ( - - ): - key === "activityType" && row["fileFormat"] ? ( - - ): - row[key]} - - ); - })} - )} - -
- ); -}; - -ActionsTable.defaultProps = { - rows:[], - headers:[] -} - -export default ActionsTable; diff --git a/frontend/src/components/ActionsTable/index.tsx b/frontend/src/components/ActionsTable/index.tsx new file mode 100644 index 00000000..70b09335 --- /dev/null +++ b/frontend/src/components/ActionsTable/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Table, TableHead, TableRow, TableHeader, TableBody, TableCell } from "@carbon/react"; +import StatusTag from "../StatusTag"; +import ActivityTag from "../ActivityTag"; + +interface IActionsTable { + rows: any[]; + headers: any[]; +} + +const ActionsTable: React.FC = ({rows, headers}) => ( + + + + {headers.map(header => + {header.header} + )} + + + + {rows.map((row, idx) => + {Object.keys(row).filter(key => key !== 'id').map(key => { + return ( + + {key === "status" ? ( + + ): + key === "activityType" && !row["fileFormat"] ? ( + + ): + key === "activityType" && row["fileFormat"] ? ( + + ): + row[key]} + + ); + })} + )} + +
+); + +export default ActionsTable; diff --git a/frontend/src/components/ActionsTable/testData.js b/frontend/src/components/ActionsTable/testData.ts similarity index 100% rename from frontend/src/components/ActionsTable/testData.js rename to frontend/src/components/ActionsTable/testData.ts diff --git a/frontend/src/components/BarChartGrouped/index.jsx b/frontend/src/components/BarChartGrouped/index.tsx similarity index 56% rename from frontend/src/components/BarChartGrouped/index.jsx rename to frontend/src/components/BarChartGrouped/index.tsx index 8219976a..d3828147 100644 --- a/frontend/src/components/BarChartGrouped/index.jsx +++ b/frontend/src/components/BarChartGrouped/index.tsx @@ -1,57 +1,66 @@ import React, { useState, useEffect } from "react"; -import { GroupedBarChart } from "@carbon/charts-react"; +import { GroupedBarChart, ScaleTypes } from "@carbon/charts-react"; import { Dropdown, DatePicker, DatePickerInput } from "@carbon/react"; import "@carbon/charts/styles.css"; import "./BarChartGrouped.scss"; import { fetchOpeningsPerYear } from "../../services/OpeningService"; +interface IDropdownItem { + value: string, + text: string +} + const BarChartGrouped = () => { - const [windowWidth, setWindowWidth] = useState(window.innerWidth); - const [chartData, setChartData] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [orgUnitCode, setOrgUnitCode] = useState(null); - const [statusCode, setStatusCode] = useState(null); - const [startDate, setStartDate] = useState(null); - const [endDate, setEndDate] = useState(null); + const [windowWidth, setWindowWidth] = useState(window.innerWidth); + const [chartData, setChartData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [orgUnitCode, setOrgUnitCode] = useState(''); + const [statusCode, setStatusCode] = useState(''); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); const handleResize = () => { setWindowWidth(window.innerWidth); }; useEffect(() => { + const fetchChartData = async () => { + try { + setIsLoading(true); + let formattedStartDate: string | null = null; + let formattedEndDate: string | null = null; + + if (startDate) { + formattedStartDate = formatDateToString(startDate); + } + if (endDate) { + formattedEndDate = formatDateToString(endDate); + } + + const data = await fetchOpeningsPerYear({ + orgUnitCode, + statusCode, + entryDateStart: formattedStartDate, + entryDateEnd: formattedEndDate + }); + setChartData(data); + setIsLoading(false); + } catch (error) { + console.error("Error fetching chart data:", error); + setIsLoading(false); + } + }; + fetchChartData(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, [orgUnitCode, statusCode, startDate, endDate]); - const fetchChartData = async () => { - try { - setIsLoading(true); - let formattedStartDate = startDate; - let formattedEndDate = endDate; - - if (startDate) { - formattedStartDate = formatDateToString(startDate); - } - if (endDate) { - formattedEndDate = formatDateToString(endDate); - } - - const data = await fetchOpeningsPerYear(orgUnitCode, statusCode, formattedStartDate, formattedEndDate); - setChartData(data); - setIsLoading(false); - } catch (error) { - console.error("Error fetching chart data:", error); - setIsLoading(false); - } - }; - - - const formatDateToString = (dates) => { - const date = dates[0] - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); + const formatDateToString = (dateToFormat: Date | null) => { + if (!dateToFormat) return null; + const year = dateToFormat.getFullYear(); + const month = String(dateToFormat.getMonth() + 1).padStart(2, "0"); + const day = String(dateToFormat.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; @@ -65,7 +74,7 @@ const BarChartGrouped = () => { mapsTo: "value", }, bottom: { - scaleType: "labels", + scaleType: ScaleTypes.LABELS, mapsTo: "key", }, }, @@ -111,6 +120,14 @@ const BarChartGrouped = () => { // Add more options as needed ]; + const setOrgUnitCodeSelected = ({selectedItem}:{selectedItem: IDropdownItem}) => { + setOrgUnitCode(selectedItem.value); + }; + + const setStatusCodeSelected = ({selectedItem}:{selectedItem: IDropdownItem}) => { + setStatusCode(selectedItem.value); + }; + return (
@@ -118,24 +135,26 @@ const BarChartGrouped = () => { item ? item.text : ''} - onChange={({ selectedItem }) => setOrgUnitCode(selectedItem.value)} + itemToString={(item: IDropdownItem) => item ? item.text : ''} + onChange={setOrgUnitCodeSelected} />
item ? item.text : ''} - onChange={({ selectedItem }) => setStatusCode(selectedItem.value)} + itemToString={(item: IDropdownItem) => item ? item.text : ''} + onChange={setStatusCodeSelected} />
setStartDate(date)} + onChange={(date: Date) => setStartDate(date)} > {
setEndDate(date)} + onChange={(date: Date) => setEndDate(date)} > { +interface IDonutChart { + group: any; + value: any; +} + +interface IDropdownItem { + value: string, + text: string +} + +const DonutChartView: React.FC = () => { const [windowWidth, setWindowWidth] = useState(window.innerWidth); - const [chartData, setChartData] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [orgUnitCode, setOrgUnitCode] = useState(""); - const [clientNumber, setClientNumber] = useState(""); - const [startDate, setStartDate] = useState(null); - const [endDate, setEndDate] = useState(null); + const [chartData, setChartData] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [orgUnitCode, setOrgUnitCode] = useState(""); + const [clientNumber, setClientNumber] = useState(""); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); const handleResize = () => { setWindowWidth(window.innerWidth); @@ -28,7 +38,12 @@ const DonutChartView = () => { setIsLoading(true); const formattedStartDate = formatDateToString(startDate); const formattedEndDate = formatDateToString(endDate); - const data = await fetchFreeGrowingMilestones(orgUnitCode, clientNumber, formattedStartDate, formattedEndDate); + const data = await fetchFreeGrowingMilestones({ + orgUnitCode, + clientNumber, + entryDateStart: formattedStartDate, + entryDateEnd: formattedEndDate + }); setChartData(data); setIsLoading(false); } catch (error) { @@ -37,12 +52,11 @@ const DonutChartView = () => { } }; - const formatDateToString = (dates) => { - if (!dates) return null; - const date = dates[0]; - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); + const formatDateToString = (dateToFormat: Date | null) => { + if (!dateToFormat) return null; + const year = dateToFormat.getFullYear(); + const month = String(dateToFormat.getMonth() + 1).padStart(2, "0"); + const day = String(dateToFormat.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; @@ -61,27 +75,42 @@ const DonutChartView = () => { }; // Sample data for dropdowns - const items = [ + const items: IDropdownItem[] = [ { value: 'DCR', text: 'DCR' }, - { value: 2, text: 'Option 2' }, - { value: 3, text: 'Option 3' }, - { value: 4, text: 'Option 4' }, + { value: '2', text: 'Option 2' }, + { value: '3', text: 'Option 3' }, + { value: '4', text: 'Option 4' }, // Add more options as needed ]; + const setOrgUnitCodeSelected = ({selectedItem}:{selectedItem: IDropdownItem}) => { + setOrgUnitCode(selectedItem.value); + }; + return (
- item ? item.text : ''} onChange={option => setOrgUnitCode(option.selectedItem.value)} /> + item ? item.text : ''} + onChange={setOrgUnitCodeSelected} + />
- setClientNumber(event.target.value)} /> + ) => setClientNumber(event.target.value)} + />
setStartDate(date)} + onChange={(date: Date) => setStartDate(date)} > {
setEndDate(date)} + onChange={(date: Date) => setEndDate(date)} > { - const iconName = props; - const Base = Carbon[iconName] - - return -} - -export default Icon; diff --git a/frontend/src/components/Icon/index.tsx b/frontend/src/components/Icon/index.tsx new file mode 100644 index 00000000..ed8c490e --- /dev/null +++ b/frontend/src/components/Icon/index.tsx @@ -0,0 +1,9 @@ +import * as Carbon from '@carbon/icons-react'; + +const Icon = (props: string) => { + const iconName = props; + const Base = Carbon[iconName] + return ; +} + +export default Icon; diff --git a/frontend/src/components/MyRecentActions/filesData.js b/frontend/src/components/MyRecentActions/filesData.ts similarity index 100% rename from frontend/src/components/MyRecentActions/filesData.js rename to frontend/src/components/MyRecentActions/filesData.ts diff --git a/frontend/src/components/MyRecentActions/index.jsx b/frontend/src/components/MyRecentActions/index.tsx similarity index 93% rename from frontend/src/components/MyRecentActions/index.jsx rename to frontend/src/components/MyRecentActions/index.tsx index 0c74d092..7021ec50 100644 --- a/frontend/src/components/MyRecentActions/index.jsx +++ b/frontend/src/components/MyRecentActions/index.tsx @@ -4,8 +4,8 @@ import ActionsTable from "../ActionsTable"; import { fetchRecentActions } from '../../services/OpeningService'; import { rows as fileRows, headers as fileHeaders } from "./filesData"; -const MyRecentActions = () => { - const [recentActions, setRecentActions] = useState([]); +const MyRecentActions: React.FC = () => { + const [recentActions, setRecentActions] = useState([]); const headers = [ { diff --git a/frontend/src/components/MyRecentActions/testData.js b/frontend/src/components/MyRecentActions/testData.ts similarity index 100% rename from frontend/src/components/MyRecentActions/testData.js rename to frontend/src/components/MyRecentActions/testData.ts diff --git a/frontend/src/components/OpeningMetricsTab/index.tsx b/frontend/src/components/OpeningMetricsTab/index.tsx index 00365c6e..f2dadc0c 100644 --- a/frontend/src/components/OpeningMetricsTab/index.tsx +++ b/frontend/src/components/OpeningMetricsTab/index.tsx @@ -19,17 +19,15 @@ const OpeningMetricsTab: React.FC = () => { setShowSpatial(!showSpatial) } - const userDetails = useSelector((state:any) => state.userDetails) - const goToActivity = () => { - console.log("clicked a row") - } - const { user } = userDetails - return ( - <> + return ( + <>
- +
@@ -53,8 +51,8 @@ const OpeningMetricsTab: React.FC = () => {
- - ); - }; + + ); +}; export default OpeningMetricsTab; diff --git a/frontend/src/components/OpeningScreenDataTable/index.jsx b/frontend/src/components/OpeningScreenDataTable/index.tsx similarity index 77% rename from frontend/src/components/OpeningScreenDataTable/index.jsx rename to frontend/src/components/OpeningScreenDataTable/index.tsx index 0c9cbaa9..7db4555f 100644 --- a/frontend/src/components/OpeningScreenDataTable/index.jsx +++ b/frontend/src/components/OpeningScreenDataTable/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useContext, useState } from 'react'; import { DataTable, TableBatchAction, @@ -25,60 +25,31 @@ import * as Icons from '@carbon/icons-react' import StatusTag from '../StatusTag'; // Import the StatusTag component import './styles.scss' import EmptySection from '../EmptySection'; +import PaginationContext from '../../contexts/PaginationContext'; -export const batchActionClick = (selectedRows) => () => { - console.log('Batch action clicked with selected rows:', selectedRows); - // Add your logic to handle batch actions here -}; - -// A custom hook to handle pagination logic -const usePagination = (data, initialItemsPerPage) => { - const [currentPage, setCurrentPage] = useState(1); - const [itemsPerPage, setItemsPerPage] = useState(initialItemsPerPage); - - // Update the total number of pages when itemsPerPage changes - const totalPages = Math.ceil(data.length / itemsPerPage); - - // Get the current page data - const currentData = () => { - const start = (currentPage - 1) * itemsPerPage; - const end = start + itemsPerPage; - return data.slice(start, end); - }; - - // Update the current page when the user changes the page - const handlePageChange = ({ page }) => { - setCurrentPage(page); - }; - - // Update the items per page when the user changes the value - const handleItemsPerPageChange = (event) => { - setCurrentPage(event.page); - setItemsPerPage(event.pageSize); - }; - - return { - currentData, - currentPage, - totalPages, - handlePageChange, - handleItemsPerPageChange, - itemsPerPage, // Expose the current itemsPerPage value - }; -}; +interface IOpeningScreenDataTable { + rows: any[], + headers: any[], + setOpeningId: Function, +} -export default function OpeningScreenDataTable({ rows, headers, error, setOpeningId }) { - const [filteredRows, setFilteredRows] = useState(rows); +const OpeningScreenDataTable: React.FC = ({ rows, headers, setOpeningId }) => { + const [filteredRows, setFilteredRows] = useState(rows); const { - currentData, + getCurrentData, currentPage, totalPages, handlePageChange, handleItemsPerPageChange, itemsPerPage, // Use itemsPerPage from the hook - } = usePagination(filteredRows, 5); + setPageData, + setInitialItemsPerPage + } = useContext(PaginationContext); - const handleSearchChange = (searchTerm) => { + setPageData(filteredRows); + setInitialItemsPerPage(5); + + const handleSearchChange = (searchTerm: string) => { const filtered = rows.filter((item) => Object.values(item) .join(' ') @@ -88,20 +59,25 @@ export default function OpeningScreenDataTable({ rows, headers, error, setOpenin setFilteredRows(filtered); }; - const clickViewAction = useCallback((id) => { + const clickViewAction = useCallback((id: string) => { console.log(`Clicked view on id ${id}`); }, []); - const selectRowEvent = useCallback((openingId, selected) => { + const selectRowEvent = useCallback((openingId: string, selected: boolean) => { if (!selected) { console.log(`Selected row id=${openingId} selected=${JSON.stringify(!selected)}`); setOpeningId(openingId); } }, []); + const batchActionClick = (selectedRows: any[]) => () => { + console.log('Batch action clicked with selected rows:', selectedRows); + // Add your logic to handle batch actions here + }; + return (
- + {({ rows, headers, @@ -115,6 +91,19 @@ export default function OpeningScreenDataTable({ rows, headers, error, setOpenin getTableProps, getTableContainerProps, selectRow, + }:{ + rows: any[], + headers: any[], + getHeaderProps: Function, + getRowProps: Function, + getSelectionProps: Function, + getToolbarProps: Function, + getBatchActionProps: Function, + onInputChange: Function, + selectedRows: any[], + getTableProps: Function, + getTableContainerProps: Function, + selectRow: any }) => { const batchActionProps = getBatchActionProps(); @@ -126,7 +115,7 @@ export default function OpeningScreenDataTable({ rows, headers, error, setOpenin handleSearchChange(e.target.value)} + onChange={(e: React.ChangeEvent) => handleSearchChange(e.target.value)} placeholder="Filter by opening ID, File ID, timber mark, cut block, status..." persistent /> @@ -190,10 +179,10 @@ export default function OpeningScreenDataTable({ rows, headers, error, setOpenin selectRowEvent(row.id, row.isSelected) + onClick: (e: Event) => selectRowEvent(row.id, row.isSelected) }) } /> - {row.cells.map((cell, j) => ( + {row.cells.map((cell: any, j: number) => ( {cell.info.header === "status" ? ( @@ -253,12 +242,14 @@ export default function OpeningScreenDataTable({ rows, headers, error, setOpenin pageSize={itemsPerPage} pageSizes={[5, 20, 50]} itemsPerPageText="Items per page" - onChange={({ page, pageSize }) => { - handlePageChange({ page, pageSize }); - handleItemsPerPageChange({ page,pageSize }); + onChange={({ page, pageSize }:{ page: number, pageSize: number}) => { + handlePageChange( page ); + handleItemsPerPageChange(page, pageSize); }} /> ) : null}
); } + +export default OpeningScreenDataTable; diff --git a/frontend/src/components/OpeningScreenDataTable/testData.js b/frontend/src/components/OpeningScreenDataTable/testData.ts similarity index 99% rename from frontend/src/components/OpeningScreenDataTable/testData.js rename to frontend/src/components/OpeningScreenDataTable/testData.ts index e69a2106..16751c64 100644 --- a/frontend/src/components/OpeningScreenDataTable/testData.js +++ b/frontend/src/components/OpeningScreenDataTable/testData.ts @@ -261,4 +261,4 @@ export const headers = [ key: 'actions', header: 'Actions', }, -]; \ No newline at end of file +]; diff --git a/frontend/src/components/OpeningsTab/index.tsx b/frontend/src/components/OpeningsTab/index.tsx index 2f8e0728..1928940e 100644 --- a/frontend/src/components/OpeningsTab/index.tsx +++ b/frontend/src/components/OpeningsTab/index.tsx @@ -93,7 +93,6 @@ const OpeningsTab: React.FC = ({showSpatial, setShowSpatial}) => { )} diff --git a/frontend/src/components/TableSkeleton/index.jsx b/frontend/src/components/TableSkeleton/index.jsx deleted file mode 100644 index 277e5a8e..00000000 --- a/frontend/src/components/TableSkeleton/index.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import React, { useState } from 'react'; -import { - DataTableSkeleton -} from '@carbon/react'; -import './styles.scss' - -export default function TableSkeleton({ headers }) { - return ( -
- -
- ); -} diff --git a/frontend/src/components/TableSkeleton/index.tsx b/frontend/src/components/TableSkeleton/index.tsx new file mode 100644 index 00000000..33781367 --- /dev/null +++ b/frontend/src/components/TableSkeleton/index.tsx @@ -0,0 +1,23 @@ +import React, { useState } from 'react'; +import { + DataTableSkeleton +} from '@carbon/react'; +import './styles.scss' + +interface Props { + headers: { + key: string; + header: string; + }[]; +} + +const TableSkeleton: React.FC = ({headers}) => ( +
+ +
+); + +export default TableSkeleton; diff --git a/frontend/src/contexts/PaginationContext.ts b/frontend/src/contexts/PaginationContext.ts new file mode 100644 index 00000000..d1d949aa --- /dev/null +++ b/frontend/src/contexts/PaginationContext.ts @@ -0,0 +1,16 @@ +import { createContext } from "react"; + +export interface PaginationContextData { + getCurrentData(): void; + currentPage: number; + totalPages: number; + handlePageChange(page: number): void; + handleItemsPerPageChange(page: number, pageSize: number): void; + itemsPerPage: number; + setPageData(data: any[]): void; + setInitialItemsPerPage(items: number): void; +} + +const PaginationContext = createContext({} as PaginationContextData); + +export default PaginationContext; diff --git a/frontend/src/contexts/PaginationProvider.tsx b/frontend/src/contexts/PaginationProvider.tsx new file mode 100644 index 00000000..dd3e3644 --- /dev/null +++ b/frontend/src/contexts/PaginationProvider.tsx @@ -0,0 +1,62 @@ +import { useMemo, useState } from "react"; +import PaginationContext, { PaginationContextData } from "./PaginationContext"; + +const PaginationProvider: React.FC<{children: React.ReactNode}> = ({ children }) => { + const [data, setData] = useState([]); + const [initialItemsPerPage, setInitialItemsPerPage] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage, setItemsPerPage] = useState(5); + + // Update the total number of pages when itemsPerPage changes + const totalPages = Math.ceil(data.length / itemsPerPage); + + // Get the current page data + const getCurrentData = () => { + const start = (currentPage - 1) * itemsPerPage; + const end = start + itemsPerPage; + return data.slice(start, end); + }; + + // Update the current page when the user changes the page + const handlePageChange = (page: number) => { + setCurrentPage(page); + }; + + // Update the items per page when the user changes the value + const handleItemsPerPageChange = (page: number, pageSize: number) => { + setCurrentPage(page); + setItemsPerPage(pageSize); + }; + + const setPageData = (data: any[]): void => { + setData(data); + }; + + const contextValue: PaginationContextData = useMemo(() => ({ + getCurrentData, + currentPage, + totalPages, + handlePageChange, + handleItemsPerPageChange, + itemsPerPage, + setPageData, + setInitialItemsPerPage, + }), [ + getCurrentData, + currentPage, + totalPages, + handlePageChange, + handleItemsPerPageChange, + itemsPerPage, + setPageData, + setInitialItemsPerPage, + ]); + + return ( + + { children } + + ) +}; + +export default PaginationProvider; diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 55b8e106..e14d5164 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -8,6 +8,7 @@ import store from './store' import App from './App'; import { ThemePreference } from './utils/ThemePreference'; import { createRoot } from 'react-dom/client'; +import PaginationProvider from './contexts/PaginationProvider'; const container:any = document.getElementById('root'); const root = createRoot(container); @@ -16,7 +17,9 @@ root.render( - + + + diff --git a/frontend/src/services/OpeningService.js b/frontend/src/services/OpeningService.js deleted file mode 100644 index 9e4b8568..00000000 --- a/frontend/src/services/OpeningService.js +++ /dev/null @@ -1,175 +0,0 @@ -import axios from 'axios'; -import { getAuthIdToken } from './AuthService'; -import { env } from '../env'; - -const backendUrl = env.VITE_BACKEND_URL; - -export async function fetchRecentOpenings() { - let authToken = getAuthIdToken(); - try { - const response = await axios.get(backendUrl.concat("/api/openings/recent-openings?page=0&perPage=100"), { - headers: { - Authorization: `Bearer ${authToken}` - } - }); - - const { data } = response; - - if (data && data.data) { - // Extracting row information from the fetched data - const rows = data.data.map(opening => ({ - id: opening.openingId.toString(), - openingId: opening.openingId.toString(), - fileId: opening.fileId ? opening.fileId : '-', - cuttingPermit: opening.cuttingPermit ? opening.cuttingPermit : '-', - timberMark: opening.timberMark ? opening.timberMark : '-', - cutBlock: opening.cutBlock ? opening.cutBlock : '-', - grossAreaHa: opening.grossAreaHa ? opening.grossAreaHa.toString() : '-', - status: opening.status ? opening.status.description : '-', - category: opening.category ? opening.category.code : '-', - disturbanceStart: opening.disturbanceStart ? opening.disturbanceStart : '-', - createdAt: opening.entryTimestamp ? opening.entryTimestamp.split('T')[0] : '-', - lastViewed: opening.updateTimestamp ? opening.updateTimestamp.split('T')[0] : '-' - })); - // Returning the rows - return rows; - } else { - console.log('No data found in the response.'); - return []; - } - } catch (error) { - console.error('Error fetching recent openings:', error); - throw error; - } -} - -export async function fetchOpeningsPerYear(orgUnitCode, statusCode, entryDateStart, entryDateEnd) { - let authToken = await getAuthIdToken(); - try { - // Construct URL with optional parameters - let url = backendUrl.concat("/api/dashboard-metrics/submission-trends"); - if (orgUnitCode || statusCode || entryDateStart || entryDateEnd) { - url += '?'; - if (orgUnitCode) url += `orgUnitCode=${orgUnitCode}&`; - if (statusCode) url += `statusCode=${statusCode}&`; - if (entryDateStart) url += `entryDateStart=${entryDateStart}&`; - if (entryDateEnd) url += `entryDateEnd=${entryDateEnd}&`; - // Remove trailing '&' if present - url = url.replace(/&$/, ''); - } - - const response = await axios.get(url, { - headers: { - Authorization: `Bearer ${authToken}` - } - }); - - const { data } = response; - - if (data && Array.isArray(data)) { - // Format data for BarChartGrouped component - const formattedData = data.map(item => ({ - group: "Openings", - key: item.monthName, - value: item.amount - })); - - return formattedData; - } else { - console.log('No data found in the response.'); - return []; - } - } catch (error) { - console.error('Error fetching openings per year:', error); - throw error; - } -} - -export async function fetchFreeGrowingMilestones(orgUnitCode, clientNumber, entryDateStart, entryDateEnd) { - let authToken = await getAuthIdToken(); - let url = backendUrl.concat("/api/dashboard-metrics/free-growing-milestones"); - - // Construct URL with optional parameters - if (orgUnitCode || clientNumber || entryDateStart || entryDateEnd) { - url += '?'; - if (orgUnitCode) url += `orgUnitCode=${orgUnitCode}&`; - if (clientNumber) url += `clientNumber=${clientNumber}&`; - if (entryDateStart) url += `entryDateStart=${entryDateStart}&`; - if (entryDateEnd) url += `entryDateEnd=${entryDateEnd}&`; - // Remove trailing '&' if present - url = url.replace(/&$/, ''); - } - console.log("the url being called:") - console.log(url) - - try { - const response = await axios.get(url, { - headers: { - Authorization: `Bearer ${authToken}` - } - }); - - const { data } = response; - - if (data && Array.isArray(data)) { - // Transform data for DonutChartView component - const transformedData = data.map(item => ({ - group: item.label, - value: item.amount - })); - - return transformedData; - } else { - console.log('No data found in the response.'); - return []; - } - } catch (error) { - console.error('Error fetching free growing milestones:', error); - throw error; - } -} - -export async function fetchRecentActions() { - let authToken = await getAuthIdToken(); - try { - // Comment out the actual API call for now - // const response = await axios.get(backendUrl.concat("/api/dashboard-metrics/my-recent-actions/requests")); - // headers: { - // Authorization: `Bearer ${authToken}` - // } - // }); - - // Temporarily use the sample data for testing - // const { data } = response; - const data = [ - { - "activityType": "Update", - "openingId": 1541297, - "statusCode": "APP", - "statusDescription": "Approved", - "lastUpdatedLabel": "1 minute ago", - "lastUpdated": "2024-05-16T19:59:21.635Z" - }, - // Add more sample objects here if needed - ]; - - if (Array.isArray(data)) { - // Transforming response data into a format consumable by the component - const rows = data.map(action => ({ - activityType: action.activityType, - openingID: action.openingId.toString(), // Convert openingId to string if needed - status: action.statusDescription, - lastUpdated: action.lastUpdatedLabel // Use lastUpdatedLabel from API - })); - - // Returning the transformed data - return rows; - } else { - console.log('No data found in the response.'); - return []; - } - } catch (error) { - console.error('Error fetching recent actions:', error); - throw error; - } -} diff --git a/frontend/src/services/OpeningService.ts b/frontend/src/services/OpeningService.ts new file mode 100644 index 00000000..4e1a3352 --- /dev/null +++ b/frontend/src/services/OpeningService.ts @@ -0,0 +1,196 @@ +import axios from 'axios'; +import { getAuthIdToken } from './AuthService'; +import { env } from '../env'; + +const backendUrl = env.VITE_BACKEND_URL; + +interface IOpening { + openingId: number; + fileId: string; + cuttingPermit: string | null; + timberMark: string | null; + cutBlock: string | null; + grossAreaHa: number | null; + status: {code: string, description: string} | null; + category: {code: string, description: string} | null; + disturbanceStart: string | null; + entryTimestamp: string | null; + updateTimestamp: string | null; +} + +export async function fetchRecentOpenings() { + let authToken = getAuthIdToken(); + try { + const response = await axios.get(backendUrl.concat("/api/openings/recent-openings?page=0&perPage=100"), { + headers: { + Authorization: `Bearer ${authToken}` + } + }); + + if (response.status >= 200 && response.status < 300) { + const { data } = response; + + if (data.data) { + // Extracting row information from the fetched data + const rows: any[] = data.data.map((opening: IOpening) => ({ + id: opening.openingId.toString(), + openingId: opening.openingId.toString(), + fileId: opening.fileId ? opening.fileId : '-', + cuttingPermit: opening.cuttingPermit ? opening.cuttingPermit : '-', + timberMark: opening.timberMark ? opening.timberMark : '-', + cutBlock: opening.cutBlock ? opening.cutBlock : '-', + grossAreaHa: opening.grossAreaHa ? opening.grossAreaHa.toString() : '-', + status: opening.status ? opening.status.description : '-', + category: opening.category ? opening.category.code : '-', + disturbanceStart: opening.disturbanceStart ? opening.disturbanceStart : '-', + createdAt: opening.entryTimestamp ? opening.entryTimestamp.split('T')[0] : '-', + lastViewed: opening.updateTimestamp ? opening.updateTimestamp.split('T')[0] : '-' + })); + + return rows; + } + } + return []; + } catch (error) { + console.error('Error fetching recent openings:', error); + throw error; + } +} + +interface IOpeningPerYear { + orgUnitCode: string; + statusCode: string; + entryDateStart: string | null; + entryDateEnd: string | null; +} + +export async function fetchOpeningsPerYear(props: IOpeningPerYear) { + let authToken = getAuthIdToken(); + try { + // Construct URL with optional parameters + let url = backendUrl.concat("/api/dashboard-metrics/submission-trends"); + if (props.orgUnitCode || props.statusCode || props.entryDateStart || props.entryDateEnd) { + url += '?'; + if (props.orgUnitCode) url += `orgUnitCode=${props.orgUnitCode}&`; + if (props.statusCode) url += `statusCode=${props.statusCode}&`; + if (props.entryDateStart) url += `entryDateStart=${props.entryDateStart}&`; + if (props.entryDateEnd) url += `entryDateEnd=${props.entryDateEnd}&`; + // Remove trailing '&' if present + url = url.replace(/&$/, ''); + } + + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${authToken}` + } + }); + + const { data } = response; + if (data && Array.isArray(data)) { + // Format data for BarChartGrouped component + const formattedData = data.map(item => ({ + group: "Openings", + key: item.monthName, + value: item.amount + })); + + return formattedData; + } else { + return []; + } + } catch (error) { + console.error('Error fetching openings per year:', error); + throw error; + } +} + +interface IFreeGrowingProps { + orgUnitCode: string; + clientNumber: string; + entryDateStart: string | null; + entryDateEnd: string | null; +} + +export async function fetchFreeGrowingMilestones(props: IFreeGrowingProps) { + let authToken = getAuthIdToken(); + let url = backendUrl.concat("/api/dashboard-metrics/free-growing-milestones"); + + // Construct URL with optional parameters + if (props.orgUnitCode || props.clientNumber || props.entryDateStart || props.entryDateEnd) { + url += '?'; + if (props.orgUnitCode) url += `orgUnitCode=${props.orgUnitCode}&`; + if (props.clientNumber) url += `clientNumber=${props.clientNumber}&`; + if (props.entryDateStart) url += `entryDateStart=${props.entryDateStart}&`; + if (props.entryDateEnd) url += `entryDateEnd=${props.entryDateEnd}&`; + // Remove trailing '&' if present + url = url.replace(/&$/, ''); + } + + try { + const response = await axios.get(url, { + headers: { + Authorization: `Bearer ${authToken}` + } + }); + + const { data } = response; + if (data && Array.isArray(data)) { + // Transform data for DonutChartView component + const transformedData = data.map(item => ({ + group: item.label, + value: item.amount + })); + + return transformedData; + } else { + return []; + } + } catch (error) { + console.error('Error fetching free growing milestones:', error); + throw error; + } +} + +export async function fetchRecentActions() { + let authToken = getAuthIdToken(); + try { + // Comment out the actual API call for now + // const response = await axios.get(backendUrl.concat("/api/dashboard-metrics/my-recent-actions/requests")); + // headers: { + // Authorization: `Bearer ${authToken}` + // } + // }); + + // Temporarily use the sample data for testing + // const { data } = response; + const data = [ + { + "activityType": "Update", + "openingId": 1541297, + "statusCode": "APP", + "statusDescription": "Approved", + "lastUpdatedLabel": "1 minute ago", + "lastUpdated": "2024-05-16T19:59:21.635Z" + }, + // Add more sample objects here if needed + ]; + + if (Array.isArray(data)) { + // Transforming response data into a format consumable by the component + const rows = data.map(action => ({ + activityType: action.activityType, + openingID: action.openingId.toString(), // Convert openingId to string if needed + status: action.statusDescription, + lastUpdated: action.lastUpdatedLabel // Use lastUpdatedLabel from API + })); + + // Returning the transformed data + return rows; + } else { + return []; + } + } catch (error) { + console.error('Error fetching recent actions:', error); + throw error; + } +}