diff --git a/package-lock.json b/package-lock.json index 96c55a8..2a69adb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@mui/x-data-grid": "^6.18.7", "@mui/x-date-pickers": "^6.18.7", "@unly/universal-language-detector": "^2.0.3", + "apexcharts": "^3.45.1", "dayjs": "^1.11.10", "i18next": "^23.7.11", "i18next-resources-to-backend": "^1.2.0", @@ -26,6 +27,7 @@ "next": "14.0.4", "next-auth": "^4.24.5", "react": "^18", + "react-apexcharts": "^1.4.1", "react-dom": "^18", "react-hook-form": "^7.49.2", "react-i18next": "^13.5.0", @@ -1525,6 +1527,11 @@ "resolved": "https://registry.npmjs.org/@unly/utils/-/utils-1.0.3.tgz", "integrity": "sha512-QTRknIDX56FvzGcIpBum5D/oRSlX3dkZ+l1op1jsFlYCTd925OGUb991V7zsFv3ePcqFfvfqfR5cNVv+w4JAOw==" }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "node_modules/accept-language-parser": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz", @@ -1619,6 +1626,20 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.45.1.tgz", + "integrity": "sha512-pPjj/SA6dfPvR/IKRZF0STdfBGpBh3WRt7K0DFuW9P8erypYkX17EHu3/molPRfo2zSiQwTVpshHC5ncysqfkA==", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -5410,6 +5431,18 @@ "node": ">=0.10.0" } }, + "node_modules/react-apexcharts": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", + "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "apexcharts": "^3.41.0", + "react": ">=0.13" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", @@ -6118,6 +6151,89 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/synckit": { "version": "0.8.6", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.6.tgz", diff --git a/package.json b/package.json index 1433b76..ff98a0e 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@mui/x-data-grid": "^6.18.7", "@mui/x-date-pickers": "^6.18.7", "@unly/universal-language-detector": "^2.0.3", + "apexcharts": "^3.45.1", "dayjs": "^1.11.10", "i18next": "^23.7.11", "i18next-resources-to-backend": "^1.2.0", @@ -27,6 +28,7 @@ "next": "14.0.4", "next-auth": "^4.24.5", "react": "^18", + "react-apexcharts": "^1.4.1", "react-dom": "^18", "react-hook-form": "^7.49.2", "react-i18next": "^13.5.0", diff --git a/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx b/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx new file mode 100644 index 0000000..ef95fff --- /dev/null +++ b/src/app/(main)/dashboard/ProjectStatusByClient/page.tsx @@ -0,0 +1,31 @@ +import { Metadata } from "next"; +import { I18nProvider } from "@/i18n"; +import DashboardPage from "@/components/DashboardPage/DashboardPage"; +import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense} from "react"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import Typography from "@mui/material/Typography"; +import ProgressByClient from "@/components/ProgressByClient"; + +export const metadata: Metadata = { + title: "Project Status by Client", +}; + + +const ProjectStatusByClient: React.FC = () => { + + return ( + + + Project Status by Client + + }> + + + + + ); +}; +export default ProjectStatusByClient; diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx index f51894c..f3c1f10 100644 --- a/src/app/(main)/dashboard/page.tsx +++ b/src/app/(main)/dashboard/page.tsx @@ -1,15 +1,22 @@ import { Metadata } from "next"; import { I18nProvider } from "@/i18n"; import DashboardPage from "@/components/DashboardPage/DashboardPage"; +import DashboardPageButton from "@/components/DashboardPage/DashboardTabButton"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense} from "react"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; export const metadata: Metadata = { title: "Dashboard", }; -const Dashboard: React.FC = async () => { + +const Dashboard: React.FC = () => { + return ( - + {/* */} ); }; diff --git a/src/app/(main)/projects/page.tsx b/src/app/(main)/projects/page.tsx index 1fe1800..90116b6 100644 --- a/src/app/(main)/projects/page.tsx +++ b/src/app/(main)/projects/page.tsx @@ -1,5 +1,6 @@ import { preloadProjects } from "@/app/api/projects"; import ProjectSearch from "@/components/ProjectSearch"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; import { getServerI18n } from "@/i18n"; import Add from "@mui/icons-material/Add"; import Button from "@mui/material/Button"; diff --git a/src/app/api/clientprojects/index.ts b/src/app/api/clientprojects/index.ts new file mode 100644 index 0000000..5c65810 --- /dev/null +++ b/src/app/api/clientprojects/index.ts @@ -0,0 +1,53 @@ +import { cache } from "react"; + +export interface ClientProjectResult { + id: number; + clientCode: string; + clientName: string; + SubsidiaryClientCode: string; + SubsidiaryClientName: string; + NoOfProjects: number; +} + +export const preloadProjects = () => { + fetchClientProjects(); +}; + +export const fetchClientProjects = cache(async () => { + return mockProjects; +}); + +const mockProjects: ClientProjectResult[] = [ + { + id: 1, + clientCode: "CUST-001", + clientName: "Client A", + SubsidiaryClientCode: "N/A", + SubsidiaryClientName: "N/A", + NoOfProjects: 5, + }, + { + id: 1, + clientCode: "CUST-001", + clientName: "Client A", + SubsidiaryClientCode: "SUBS-001", + SubsidiaryClientName: "Subsidiary A", + NoOfProjects: 5, + }, + { + id: 1, + clientCode: "CUST-001", + clientName: "Client A", + SubsidiaryClientCode: "SUBS-002", + SubsidiaryClientName: "Subsidiary B", + NoOfProjects: 3, + }, + { + id: 1, + clientCode: "CUST-001", + clientName: "Client A", + SubsidiaryClientCode: "SUBS-003", + SubsidiaryClientName: "Subsidiary C", + NoOfProjects: 1, + }, +]; diff --git a/src/components/AppBar/AppBar.tsx b/src/components/AppBar/AppBar.tsx index a38d2bd..2942050 100644 --- a/src/components/AppBar/AppBar.tsx +++ b/src/components/AppBar/AppBar.tsx @@ -16,7 +16,7 @@ const AppBar: React.FC = ({ avatarImageSrc, profileName }) => { - + diff --git a/src/components/AppBar/NavigationToggle.tsx b/src/components/AppBar/NavigationToggle.tsx index 97b156b..9241fc6 100644 --- a/src/components/AppBar/NavigationToggle.tsx +++ b/src/components/AppBar/NavigationToggle.tsx @@ -1,5 +1,4 @@ "use client"; - import IconButton from "@mui/material/IconButton"; import MenuIcon from "@mui/icons-material/Menu"; import NavigationContent from "../NavigationContent"; @@ -18,21 +17,21 @@ const NavigationToggle: React.FC = () => { return ( <> - - + + - + ; dataGridHeight?: number; [key: string]: any; + checkboxSelection?: boolean; + onRowSelectionModelChange?: (newSelectionModel: GridRowSelectionModel) => void; + selectionModel?: any; } const CustomDatagrid: React.FC = ({ @@ -19,9 +24,12 @@ const CustomDatagrid: React.FC = ({ rows, columns, columnWidth, - Style = true, + Style = false, sx, dataGridHeight, + checkboxSelection, // Destructure the new prop + onRowSelectionModelChange, // Destructure the new prop + selectionModel, ...props }) => { const modifiedColumns = columns.map((column) => { @@ -35,6 +43,16 @@ const CustomDatagrid: React.FC = ({ return { ...row }; }); + // Event handler to be called when the selection changes + const handleSelectionModelChange = (newSelectionModel: GridRowSelectionModel) => { + // setSelectionModel(newSelectionModel); + // To log selected row data, filter rows based on the new selection model + const selectedRowsData = rows.filter((row) => + newSelectionModel.includes(row.id) + ); + console.log(selectedRowsData); + }; + const getBackgroundColor = (color: string, mode: 'light' | 'dark') => mode === 'dark' ? darken(color, 0.7) : lighten(color, 0.7); @@ -99,15 +117,47 @@ const CustomDatagrid: React.FC = ({ })); return ( -
- - {Title && } - +
+ {Title ? ( + + {Title && } + {Style ? ( + ) : ( + = ({ '& .MuiDataGrid-cell:hover': { color: 'primary.main' }, + height: 300, + '& .MuiDataGrid-root': { + overflow: 'auto', + }, + ...sx + }} + {...props} + /> + )} + + ) + : (Style ? ( + = ({ rows={rowsWithDefaultValues} columns={modifiedColumns} editMode="row" + style={{marginRight:20}} + checkboxSelection={checkboxSelection} + onRowSelectionModelChange={onRowSelectionModelChange} initialState={{ pagination: { paginationModel: { pageSize: 10 } }, }} @@ -143,7 +229,7 @@ const CustomDatagrid: React.FC = ({ '& .MuiDataGrid-cell:hover': { color: 'primary.main' }, - height: 400, + height: 300, '& .MuiDataGrid-root': { overflow: 'auto', }, @@ -151,9 +237,7 @@ const CustomDatagrid: React.FC = ({ }} {...props} /> - )} - - + ))}
); }; diff --git a/src/components/CustomSearchForm/CustomSearchForm.tsx b/src/components/CustomSearchForm/CustomSearchForm.tsx index 54a7c81..8f15d26 100644 --- a/src/components/CustomSearchForm/CustomSearchForm.tsx +++ b/src/components/CustomSearchForm/CustomSearchForm.tsx @@ -214,6 +214,11 @@ const FormComponent: FC = ({ fields, onSubmit, resetForm, sx defaultValue={field.value !== undefined && field.value !== null ? `${field.value}` : ''} required={field.required === true ? field.required : false} sx={{ ...sx }} + InputProps={{ + style: { + borderRadius: "10px", + } + }} /> ); @@ -222,12 +227,12 @@ const FormComponent: FC = ({ fields, onSubmit, resetForm, sx - - @@ -254,7 +259,7 @@ const CustomSearchForm: FC = ({ applySearch, fields, title, sx return ( - + ); diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index b41411f..4307976 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -1,20 +1,50 @@ -"use client"; +"use client" import Grid from "@mui/material/Grid"; import Paper from "@mui/material/Paper"; import { TFunction } from "i18next"; import { useTranslation } from "react-i18next"; import PageTitle from "../PageTitle/PageTitle"; import DashboardTabButton from "./DashboardTabButton"; +import { ThemeProvider } from '@mui/material/styles'; +import theme from '../../theme'; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; +import React, { useCallback, useState } from "react"; +import { useRouter } from "next/navigation"; +import ProgressByClient from "./ProgressByClient"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense } from "react"; const DashboardPage: React.FC = () => { + const [tabIndex, setTabIndex] = useState(0); const { t } = useTranslation("dashboard"); + const router = useRouter(); + const handleCancel = () => { + router.back(); + }; + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [], + ); return ( - + + + + + + + + + {tabIndex === 2 && } + {/* - + */} + ); }; diff --git a/src/components/DashboardPage/DashboardTabButton.tsx b/src/components/DashboardPage/DashboardTabButton.tsx index c97ca91..dda77c6 100644 --- a/src/components/DashboardPage/DashboardTabButton.tsx +++ b/src/components/DashboardPage/DashboardTabButton.tsx @@ -1,11 +1,13 @@ "use client"; import Grid from "@mui/material/Grid"; -import { useState } from 'react' +import { useState,useCallback} from 'react' import Paper from "@mui/material/Paper"; import { TFunction } from "i18next"; import { useTranslation } from "react-i18next"; import PageTitle from "../PageTitle/PageTitle"; import ProgressByClient from "./ProgressByClient"; +import Tabs, { TabsProps } from "@mui/material/Tabs"; +import Tab from "@mui/material/Tab"; import '../../app/global.css'; const DashboardTabButton: React.FC = () => { @@ -27,34 +29,48 @@ const DashboardTabButton: React.FC = () => { return
Project Financial Summary
; } }; + const [tabIndex, setTabIndex] = useState(0); + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [], + ); return ( - -
- {activeTab !== 'financialSummary' ? - : - - } - {activeTab !== 'cashFlow' ? - : - - } - {activeTab !== 'progressByClient' ? - : - - } - {activeTab !== 'resourceUtilization' ? - : - - } - {activeTab !== 'staffUtilization' ? - : - - } -
-
- {renderContent()} -
-
+ // + //
+ // {activeTab !== 'financialSummary' ? + // : + // + // } + // {activeTab !== 'cashFlow' ? + // : + // + // } + // {activeTab !== 'progressByClient' ? + // : + // + // } + // {activeTab !== 'resourceUtilization' ? + // : + // + // } + // {activeTab !== 'staffUtilization' ? + // : + // + // } + //
+ //
+ // {renderContent()} + //
+ //
+ + + + + + + ); }; diff --git a/src/components/DashboardPage/ProgressByClient.tsx b/src/components/DashboardPage/ProgressByClient.tsx index 6388bf6..b69fede 100644 --- a/src/components/DashboardPage/ProgressByClient.tsx +++ b/src/components/DashboardPage/ProgressByClient.tsx @@ -1,14 +1,23 @@ -"use client"; + import * as React from "react"; import Grid from "@mui/material/Grid"; -import { useState } from 'react' +import { useState,useEffect, useMemo } from 'react' import Paper from "@mui/material/Paper"; import { TFunction } from "i18next"; import { useTranslation } from "react-i18next"; import {Card,CardHeader} from '@mui/material'; import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; +import ReactApexChart from 'react-apexcharts'; +import { ApexOptions } from 'apexcharts'; +import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import ReportProblemIcon from '@mui/icons-material/ReportProblem'; +import dynamic from 'next/dynamic'; import '../../app/global.css'; +import { AnyARecord } from "dns"; +import SearchBox, { Criterion } from "../SearchBox"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense } from "react"; const ProgressByClient: React.FC = () => { const [activeTab, setActiveTab] = useState('financialSummary'); @@ -19,74 +28,143 @@ const ProgressByClient: React.FC = () => { const [clientName, setClientName] = useState(''); const [subsidiaryClientCode, setSubsidiaryClientCode] = useState(''); const [subsidiaryClientName, setSubsidiaryClientName] = useState(''); + const [projectArray, setProjectArray] : any[] = useState([]); + const [percentageArray, setPercentageArray] : any[] = useState([]); + const [selectionModel, setSelectionModel] : any[] = React.useState([]); const [dropdownDemo, setDropdownDemo] = useState(''); const [dateDemo, setDateDemo] = useState(null); const [checkboxDemo, setCheckboxDemo] = useState(false); const [receiptFromDate, setReceiptFromDate] = useState(null); const [receiptToDate, setReceiptToDate] = useState(null); - const rows = [{id: 1,processNo:"1",processDescription:"把豬頸背肉絲解凍及洗淨隔乾水份", mat:"豬頸背肉絲", matLot:"Lot001", actlQty:"11.30",unit: "kg", equipment:"解凍盤",operator:"HKPC01"}, - {id: 2,processNo:"2",processDescription:"加入調味料作腌製", mat:"洋葱", matLot:"Lot002", actlQty:"2.20",unit: "kg", equipment:"調味盤",operator:"HKPC01"}, - {id: 3,processNo:"3",processDescription:"加入調味料作腌製", mat:"茄膏", matLot:"Lot003", actlQty:"50.00",unit: "g", equipment:"調味盤",operator:"HKPC01"}, - {id: 4,processNo:"4",processDescription:"加入調味料作腌製", mat:"家樂牌黃汁粉", matLot:"Lot004", actlQty:"500.00",unit: "g", equipment:"調味盤",operator:"HKPC01"}, - {id: 5,processNo:"5",processDescription:"放入0-4度雪櫃暫存備用", mat:"-", matLot:"-", actlQty:"-",unit: "-", equipment:"雪櫃",operator:"HKPC01"}, - {id: 6,processNo:"6",processDescription:"洋葱洗淨切碎備用", mat:"洋葱", matLot:"Lot002", actlQty:"131.00",unit: "g", equipment:"切割機",operator:"HKPC01"}, - {id: 7,processNo:"7",processDescription:"用蒸焗爐煮至熟透", mat:"-", matLot:"-", actlQty:"-",unit: "-", equipment:"蒸焗爐",operator:"HKPC01"},] + const [selectedRows, setSelectedRows] = useState([]); + const rows = [{id: 1,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"N/A", clientSubsidiaryName:"N/A", noOfProjects:"5"}, + {id: 2,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-001", clientSubsidiaryName:"Subsidiary A", noOfProjects:"5"}, + {id: 3,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-002", clientSubsidiaryName:"Subsidiary B", noOfProjects:"3"}, + {id: 4,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-003", clientSubsidiaryName:"Subsidiary C", noOfProjects:"1"} + ] + const rows2 = [{id: 1,project:"Consultancy Project 123",team:"XXX", teamLeader:"XXX", currentStage:"Contract Documentation", budgetedManhour:"200.00",spentManhour:"120.00",remainedManhour:"80.00",comingPaymentMilestone:"31/03/2024",alert:false}, + {id: 2,project:"Consultancy Project 456",team:"XXX", teamLeader:"XXX", currentStage:"Report Preparation", budgetedManhour:"400.00",spentManhour:"200.00",remainedManhour:"200.00",comingPaymentMilestone:"20/02/2024",alert:false}, + {id: 3,project:"Construction Project A",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"187.50",spentManhour:"200.00",remainedManhour:"12.50",comingPaymentMilestone:"13/12/2023",alert:true}, + {id: 4,project:"Construction Project B",team:"XXX", teamLeader:"XXX", currentStage:"Post Construction", budgetedManhour:"100.00",spentManhour:"40.00",remainedManhour:"60.00",comingPaymentMilestone:"05/01/2024",alert:false}, + {id: 5,project:"Construction Project C",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"300.00",spentManhour:"150.00",remainedManhour:"150.00",comingPaymentMilestone:"31/03/2024",alert:false}, + ] const columns = [ { - id: 'processNo', - field: 'processNo', - headerName: "工序次序", - flex: 0.7, + id: 'clientCode', + field: 'clientCode', + headerName: "Client Code", + flex: 1, }, { - id: 'processDescription', - field: 'processDescription', - headerName: "工序描述", + id: 'clientName', + field: 'clientName', + headerName: "Client Name", flex: 1, }, { - id: 'mat', - field: 'mat', - headerName: "原料", + id: 'clientSubsidiaryCode', + field: 'clientSubsidiaryCode', + headerName: "Client Subsidiary Code", flex: 1, }, { - id: 'matLot', - field: 'matLot', - headerName: "原料批次", + id: 'noOfProjects', + field: 'noOfProjects', + headerName: "No. of Projects", flex: 1, }, + ]; + + const columns2 = [ { - id: 'actlQty', - field: 'actlQty', - headerName: "投入數量", - flex: 0.7, - align: "right", - headerAlign: 'right', - }, - { - id: 'unit', - field: 'unit', - headerName: "單位", - flex: 0.7, - align: "left", - headerAlign: 'left', - }, - { - id: 'equipment', - field: 'equipment', - headerName: "設備", + id: 'project', + field: 'project', + headerName: "Project", flex: 1, - }, - { - id: 'operator', - field: 'operator', - headerName: "操作人員", + }, + { + id: 'team', + field: 'team', + headerName: "Team", flex: 1, + }, + { + id: 'teamLeader', + field: 'teamLeader', + headerName: "Team Leader", + flex: 1, + }, + { + id: 'currentStage', + field: 'currentStage', + headerName: "Current Stage", + flex: 1, + }, + { + id: 'budgetedManhour', + field: 'budgetedManhour', + headerName: "Budgeted Manhour", + flex: 1, + }, + { + id: 'spentManhour', + field: 'spentManhour', + headerName: "Spent Manhour", + renderCell: (params:any) => { + if (params.row.budgetedManhour - params.row.spentManhour <= 0) { + return( + {params.row.spentManhour} + ) + } else { + return ( + {params.row.spentManhour} + ) + } }, - - ]; + flex: 1, + }, + { + id: 'remainedManhour', + field: 'remainedManhour', + headerName: "Remained Manhour", + renderCell: (params:any) => { + if (params.row.budgetedManhour - params.row.spentManhour <= 0) { + return( + ({params.row.remainedManhour}) + ) + } else { + return ( + {params.row.remainedManhour} + ) + } + }, + flex: 1, + }, + { + id: 'comingPaymentMilestone', + field: 'comingPaymentMilestone', + headerName: "Coming Payment Milestone", + flex: 1, + }, + { + id: 'alert', + field: 'alert', + headerName: "Alert", + renderCell: (params:any) => { + if (params.row.alert === true) { + return ( + + ) + } else { + return ( + + ) + } + }, + flex: 1, + }, +]; const InputFields = [ { id: "clientCode", label: "Client Code", type: 'text', value: clientCode, setValue: setClientCode }, @@ -100,14 +178,173 @@ const ProgressByClient: React.FC = () => { // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, ]; + const stageDeadline = ["31/03/2024","20/02/2024","01/12/2023","05/01/2024","31/03/2023"] + + const series2: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ + data: [17.1, 28.6, 5.7, 48.6], + }]; + + const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ + name: 'Current Stage Completion Percentage', + data: [80, 55, 40, 65, 70], + }]; + + const options2 : ApexOptions = { + chart: { + type: 'donut', + }, + plotOptions: { + pie: { + donut:{ + labels:{ + show:false, + } + } + }, + }, + labels: [projectArray], + legend: { + show: false, + }, + responsive: [{ + breakpoint: 480, + options: { + chart: { + width: 200 + }, + legend: { + position: 'bottom', + show:false + } + } + }] + } + + const options: ApexOptions = { + chart: { + type: 'bar', + height: 350 + }, + colors: ['#FF4560', '#00E396', '#008FFB', '#775DD0', '#FEB019'], + plotOptions: { + bar: { + horizontal: true, + distributed: true, + }, + }, + dataLabels: { + enabled: false + }, + xaxis: { + categories: [ + 'Consultancy Project 123', + 'Consultancy Project 456', + 'Construction Project A', + 'Construction Project B', + 'Construction Project C', + ], + }, + yaxis: { + title: { + text: 'Projects' + }, + labels: { + maxWidth: 200, + style: { + cssClass: 'apexcharts-yaxis-label', + }, + }, + }, + title: { + text: 'Current Stage Completion Percentage', + align: 'center' + }, + grid: { + borderColor: '#f1f1f1', + }, + annotations: { + } + }; + + const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { + const selectedRowsData = rows2.filter((row) => + newSelectionModel.includes(row.id) + ); + console.log(selectedRowsData) + const projectArray:any[] = [] + let otherPercentage = 100 + let totalBudgetManhour = 0 + const percentageArray = [] + for (let i = 0; i <= selectedRowsData.length; i++) { + if (i === selectedRowsData.length) { + projectArray.push("Other") + } else { + projectArray.push(selectedRowsData[i].project) + totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour) + } + } + for (let i = 0; i <= selectedRowsData.length; i++) { + if (i === selectedRowsData.length) { + percentageArray.push(otherPercentage) + } else { + let percentage = ((Number(selectedRowsData[i].spentManhour) / totalBudgetManhour) * 100).toFixed(1) + percentageArray.push(Number(percentage)) + otherPercentage -= Number(percentage) + } + } + setSelectionModel(newSelectionModel) + setProjectArray(projectArray) + setPercentageArray(percentageArray) + }; + const applySearch = (data: any) => { console.log(data) setSearchCriteria(data) } return ( - - + {/* */} + {/* */} +
+ + + +
+ +
+ {/*
+

Stage Deadline

+ {stageDeadline.map((date, index) => { + const marginTop = index === 0 ? 25 : 20; + return ( +

{date}

+ ); + })} +
*/} + +
+ +
+
+
+
+
+ + + + + + +
); }; diff --git a/src/components/NavigationContent/NavigationContent.tsx b/src/components/NavigationContent/NavigationContent.tsx index 9586b57..3e0ce58 100644 --- a/src/components/NavigationContent/NavigationContent.tsx +++ b/src/components/NavigationContent/NavigationContent.tsx @@ -18,16 +18,22 @@ import Typography from "@mui/material/Typography"; import { usePathname } from "next/navigation"; import Link from "next/link"; import { NAVIGATION_CONTENT_WIDTH } from "@/config/uiConfig"; +import ArrowCircleLeftOutlinedIcon from '@mui/icons-material/ArrowCircleLeftOutlined'; +import ArrowCircleLeftRoundedIcon from '@mui/icons-material/ArrowCircleLeftRounded'; interface NavigationItem { icon: React.ReactNode; label: string; path: string; + children?: NavigationItem[]; } const navigationItems: NavigationItem[] = [ { icon: , label: "User Workspace", path: "/home" }, - { icon: , label: "Dashboard", path: "/dashboard" }, + { icon: , label: "Dashboard", path: "", children: [ + { icon: , label: "Project Status by Client", path: "/dashboard/ProjectStatusByClient" }, + { icon: , label: "Subitem 2", path: "/dashboard/subitem2" }, + ]}, { icon: , label: "Expense Claim", path: "/claim" }, { icon: , label: "Project Management", path: "/projects" }, { icon: , label: "Task Template", path: "/tasks" }, @@ -40,15 +46,54 @@ const NavigationContent: React.FC = () => { const { t } = useTranslation("common"); const pathname = usePathname(); + const [openItems, setOpenItems] = React.useState([]); + const toggleItem = (path: string) => { + setOpenItems(prevOpenItems => + prevOpenItems.includes(path) + ? prevOpenItems.filter(item => item !== path) + : [...prevOpenItems, path] + ); + }; + + const renderNavigationItem = (item: NavigationItem) => { + const isOpen = openItems.includes(item.path); + + return ( + + item.children && toggleItem(item.path)} + > + {item.icon} + + + {item.children && isOpen && ( + + {item.children.map(child => renderNavigationItem(child))} + + )} + + ); + }; + return ( {/* Replace this with company logo and/or name */} - TSMS + TSMS + {/* */} - {navigationItems.map(({ icon, label, path }, index) => { + {navigationItems.map(item => renderNavigationItem(item))} + {/* {navigationItems.map(({ icon, label, path }, index) => { return ( { ); - })} + })} */} ); diff --git a/src/components/ProgressByClient/ProgressByClient.tsx b/src/components/ProgressByClient/ProgressByClient.tsx new file mode 100644 index 0000000..80e1a2f --- /dev/null +++ b/src/components/ProgressByClient/ProgressByClient.tsx @@ -0,0 +1,434 @@ +"use client"; +import * as React from "react"; +import Grid from "@mui/material/Grid"; +import { useState,useEffect, useMemo } from 'react' +import Paper from "@mui/material/Paper"; +import { TFunction } from "i18next"; +import { useTranslation } from "react-i18next"; +import {Card,CardHeader} from '@mui/material'; +import CustomSearchForm from "../CustomSearchForm/CustomSearchForm"; +import CustomDatagrid from '../CustomDatagrid/CustomDatagrid'; +import ReactApexChart from 'react-apexcharts'; +import { ApexOptions } from 'apexcharts'; +import { GridColDef, GridRowSelectionModel} from '@mui/x-data-grid'; +import ReportProblemIcon from '@mui/icons-material/ReportProblem'; +import dynamic from 'next/dynamic'; +import '../../app/global.css'; +import { AnyARecord, AnyCnameRecord } from "dns"; +import SearchBox, { Criterion } from "../SearchBox"; +import ProgressByClientSearch from "@/components/ProgressByClientSearch"; +import { Suspense } from "react"; + +const ProgressByClient: React.FC = () => { + const [activeTab, setActiveTab] = useState('financialSummary'); + const [SearchCriteria, setSearchCriteria] = React.useState({}) + const { t } = useTranslation("dashboard"); + + const [clientCode, setClientCode] = useState(''); + const [clientName, setClientName] = useState(''); + const [subsidiaryClientCode, setSubsidiaryClientCode] = useState(''); + const [subsidiaryClientName, setSubsidiaryClientName] = useState(''); + const [projectArray, setProjectArray] : any[] = useState([]); + const [percentageArray, setPercentageArray] : any[] = useState([]); + const [colorArray, setColorArray] : any[] = useState([]); + const [selectionModel, setSelectionModel] : any[] = React.useState([]); + const [pieChartColor, setPieChartColor] : any[] = React.useState([]); + const [totalSpentPercentage, setTotalSpentPercentage] : any = React.useState(); + const [projectBudgetManhour, setProjectBudgetManhour] :any = React.useState('-'); + const [actualManhourSpent, setActualManhourSpent] :any = React.useState('-'); + const [remainedManhour, setRemainedManhour] :any = React.useState('-'); + const [lastUpdate, setLastUpdate] :any = React.useState('-'); + const [dropdownDemo, setDropdownDemo] = useState(''); + const [dateDemo, setDateDemo] = useState(null); + const [checkboxDemo, setCheckboxDemo] = useState(false); + const [receiptFromDate, setReceiptFromDate] = useState(null); + const [receiptToDate, setReceiptToDate] = useState(null); + const [selectedRows, setSelectedRows] = useState([]); + const rows = [{id: 1,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"N/A", clientSubsidiaryName:"N/A", noOfProjects:"5"}, + {id: 2,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-001", clientSubsidiaryName:"Subsidiary A", noOfProjects:"5"}, + {id: 3,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-002", clientSubsidiaryName:"Subsidiary B", noOfProjects:"3"}, + {id: 4,clientCode:"CUST-001",clientName:"Client A", clientSubsidiaryCode:"SUBS-003", clientSubsidiaryName:"Subsidiary C", noOfProjects:"1"} + ] + //['#f57f90', '#94f7d6', '#87c5f5', '#ab95f5', '#fcd68b'] + const rows2 = [{id: 1,project:"Consultancy Project 123",team:"XXX", teamLeader:"XXX", currentStage:"Contract Documentation", budgetedManhour:"200.00",spentManhour:"120.00",remainedManhour:"80.00",comingPaymentMilestone:"31/03/2024",alert:false,color:"#f57f90"}, + {id: 2,project:"Consultancy Project 456",team:"XXX", teamLeader:"XXX", currentStage:"Report Preparation", budgetedManhour:"400.00",spentManhour:"200.00",remainedManhour:"200.00",comingPaymentMilestone:"20/02/2024",alert:false,color:"#94f7d6"}, + {id: 3,project:"Construction Project A",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"187.50",spentManhour:"200.00",remainedManhour:"12.50",comingPaymentMilestone:"13/12/2023",alert:true,color:"#87c5f5"}, + {id: 4,project:"Construction Project B",team:"XXX", teamLeader:"XXX", currentStage:"Post Construction", budgetedManhour:"100.00",spentManhour:"40.00",remainedManhour:"60.00",comingPaymentMilestone:"05/01/2024",alert:false,color:"#ab95f5"}, + {id: 5,project:"Construction Project C",team:"YYY", teamLeader:"YYY", currentStage:"Construction", budgetedManhour:"300.00",spentManhour:"150.00",remainedManhour:"150.00",comingPaymentMilestone:"31/03/2024",alert:false,color:"#fcd68b"}, + ] + + const columns = [ + { + id: 'clientCode', + field: 'clientCode', + headerName: "Client Code", + flex: 1, + }, + { + id: 'clientName', + field: 'clientName', + headerName: "Client Name", + flex: 1, + }, + { + id: 'clientSubsidiaryCode', + field: 'clientSubsidiaryCode', + headerName: "Client Subsidiary Code", + flex: 1, + }, + { + id: 'noOfProjects', + field: 'noOfProjects', + headerName: "No. of Projects", + flex: 1, + }, + ]; + + const columns2 = [ + { + id:"color", + field:'color', + headerName:'', + renderCell: (params:any) => { + return ( + + ) + }, + flex:0.1, + }, + { + id: 'project', + field: 'project', + headerName: "Project", + flex: 1, + }, + { + id: 'team', + field: 'team', + headerName: "Team", + flex: 0.8, + }, + { + id: 'teamLeader', + field: 'teamLeader', + headerName: "Team Leader", + flex: 0.8, + }, + { + id: 'currentStage', + field: 'currentStage', + headerName: "Current Stage", + flex: 1, + }, + { + id: 'budgetedManhour', + field: 'budgetedManhour', + headerName: "Budgeted Manhour", + flex: 0.8, + }, + { + id: 'spentManhour', + field: 'spentManhour', + headerName: "Spent Manhour", + renderCell: (params:any) => { + if (params.row.budgetedManhour - params.row.spentManhour <= 0) { + return( + {params.row.spentManhour} + ) + } else { + return ( + {params.row.spentManhour} + ) + } + }, + flex: 0.8, + }, + { + id: 'remainedManhour', + field: 'remainedManhour', + headerName: "Remained Manhour", + renderCell: (params:any) => { + if (params.row.budgetedManhour - params.row.spentManhour <= 0) { + return( + ({params.row.remainedManhour}) + ) + } else { + return ( + {params.row.remainedManhour} + ) + } + }, + flex: 1, + }, + { + id: 'comingPaymentMilestone', + field: 'comingPaymentMilestone', + headerName: "Coming Payment Milestone", + flex: 1, + }, + { + id: 'alert', + field: 'alert', + headerName: "Alert", + renderCell: (params:any) => { + if (params.row.alert === true) { + return ( + + ) + } else { + return ( + + ) + } + }, + flex: 0.2, + }, +]; + + const InputFields = [ + { id: "clientCode", label: "Client Code", type: 'text', value: clientCode, setValue: setClientCode }, + { id: "clientName", label: "Client Name", type: 'text', value: clientName, setValue: setClientName }, + { id: "subsidiaryClientCode", label: "Subsidiary Client Code", type:'text', value:subsidiaryClientCode, setValue: setSubsidiaryClientCode}, + { id: "subsidiaryClientName", label: "Subsidiary Client Name", type:'text', value:subsidiaryClientName, setValue: setSubsidiaryClientName}, + // { id: 'dropdownDemo', label: "dropdownDemo", type: 'dropdown', options: [{id:"1", label:"1"}], value: dropdownDemo, setValue: setDropdownDemo }, + // { id: 'dateDemo', label:'dateDemo', type: 'date', value: dateDemo, setValue: setDateDemo }, + // { id: 'checkboxDemo', label:'checkboxDemo', type: 'checkbox', value: checkboxDemo, setValue: setCheckboxDemo }, + // { id: ['receiptFromDate','receiptToDate'], label: ["收貨日期","收貨日期"], value: [receiptFromDate ? receiptFromDate : null, receiptToDate ? receiptToDate : null], + // setValue: [setReceiptFromDate, setReceiptToDate],type: 'dateRange' }, + ]; + + const stageDeadline = ["31/03/2024","20/02/2024","01/12/2023","05/01/2024","31/03/2023"] + + const series2: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ + data: [17.1, 28.6, 5.7, 48.6], + }]; + + const series: ApexAxisChartSeries | ApexNonAxisChartSeries = [{ + name: 'Current Stage Completion Percentage', + data: [80, 55, 40, 65, 70], + }]; + + const options2 : ApexOptions = { + chart: { + type: 'donut', + }, + colors: colorArray, + plotOptions: { + pie: { + donut:{ + labels:{ + show:true, + name:{ + show:true, + }, + value:{ + show:true, + fontWeight: 500, + fontSize: '30px', + color: "#3e98c7", + }, + total:{ + show: true, + showAlways: true, + label: 'Spent', + fontFamily: 'sans-serif', + formatter: function (val) { + return totalSpentPercentage + "%" + } + } + } + } + }, + }, + labels: projectArray, + legend: { + show: false, + }, + responsive: [{ + breakpoint: 480, + options: { + chart: { + width: 200 + }, + legend: { + position: 'bottom', + show:false + } + } + }] + } + + const options: ApexOptions = { + chart: { + type: 'bar', + height: 350 + }, + colors: ['#f57f90', '#94f7d6', '#87c5f5', '#ab95f5', '#fcd68b'], + plotOptions: { + bar: { + horizontal: true, + distributed: true, + }, + }, + dataLabels: { + enabled: false + }, + xaxis: { + categories: [ + 'Consultancy Project 123', + 'Consultancy Project 456', + 'Construction Project A', + 'Construction Project B', + 'Construction Project C', + ], + }, + yaxis: { + title: { + text: 'Projects' + }, + labels: { + maxWidth: 200, + style: { + cssClass: 'apexcharts-yaxis-label', + }, + }, + }, + title: { + text: 'Current Stage Completion Percentage', + align: 'center' + }, + grid: { + borderColor: '#f1f1f1', + }, + annotations: { + } + }; + + const handleSelectionChange = (newSelectionModel: GridRowSelectionModel) => { + const selectedRowsData = rows2.filter((row) => + newSelectionModel.includes(row.id) + ); + console.log(selectedRowsData) + const projectArray = [] + const pieChartColorArray = [] + let totalSpent = 0 + let totalBudgetManhour = 0 + const percentageArray = [] + for (let i = 0; i <= selectedRowsData.length; i++) { + if (i === selectedRowsData.length && i > 0) { + projectArray.push("Remained") + } else if (selectedRowsData.length > 0) { + projectArray.push(selectedRowsData[i].project) + totalBudgetManhour += Number(selectedRowsData[i].budgetedManhour) + totalSpent += Number(selectedRowsData[i].spentManhour) + pieChartColorArray.push(selectedRowsData[i].color) + } + } + for (let i = 0; i <= selectedRowsData.length; i++) { + if (i === selectedRowsData.length && i > 0) { + const remainedManhour = (totalBudgetManhour - totalSpent) + percentageArray.push(Number(((remainedManhour / totalBudgetManhour) * 100).toFixed(1))) + } else if (selectedRowsData.length > 0) { + let percentage = ((Number(selectedRowsData[i].spentManhour) / totalBudgetManhour) * 100).toFixed(1) + percentageArray.push(Number(percentage)) + } + } + setProjectBudgetManhour(totalBudgetManhour.toFixed(2)) + setActualManhourSpent(totalSpent.toFixed(2)) + setRemainedManhour((totalBudgetManhour - totalSpent).toFixed(2)) + setLastUpdate(new Date().toLocaleDateString('en-GB')) + setSelectionModel(newSelectionModel) + console.log(projectArray) + setProjectArray(projectArray) + setPercentageArray(percentageArray) + console.log(percentageArray) + setTotalSpentPercentage(((totalSpent/totalBudgetManhour)*100).toFixed(1)) + if (projectArray.length > 0 && projectArray.includes("Remained")) { + const nonLastRecordColors = pieChartColorArray; + setColorArray([...nonLastRecordColors.slice(0, projectArray.length - 1), "#a3a3a3"]); + } else { + setColorArray(pieChartColorArray); + } + }; + + const applySearch = (data: any) => { + console.log(data) + setSearchCriteria(data) + } + return ( + + {/* */} + {/* */} +
+ + + +
+ +
+ {/*
+

Stage Deadline

+ {stageDeadline.map((date, index) => { + const marginTop = index === 0 ? 25 : 20; + return ( +

{date}

+ ); + })} +
*/} + +
+ +
+
+
+
+
+ + + + {percentageArray.length === 0 &&( +
Please select the project you want to check.
+ )} + {percentageArray.length > 0 &&( + + )} +
+
+ + +
+
Project Budget Manhour
+
{projectBudgetManhour}
+
+
+
+
Actual Manhour Spent
+
{actualManhourSpent}
+
+
+
+
Remained Manhour
+
{remainedManhour}
+
+
+
+
Last Update
+
{lastUpdate}
+
+
+
+
+
+ ); +}; + +export default ProgressByClient; diff --git a/src/components/ProgressByClient/index.ts b/src/components/ProgressByClient/index.ts new file mode 100644 index 0000000..3b2ccf9 --- /dev/null +++ b/src/components/ProgressByClient/index.ts @@ -0,0 +1 @@ +export { default } from "./ProgressByClient"; diff --git a/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx b/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx new file mode 100644 index 0000000..7ddbb00 --- /dev/null +++ b/src/components/ProgressByClientSearch/ProgressByClientSearch.tsx @@ -0,0 +1,57 @@ +"use client"; + +import { ProjectResult } from "@/app/api/projects"; +import React, { useMemo, useState } from "react"; +import SearchBox, { Criterion } from "../SearchBox"; +import { useTranslation } from "react-i18next"; +import SearchResults, { Column } from "../SearchResults"; +import { ClientProjectResult } from "@/app/api/clientprojects"; + +interface Props { + projects: ClientProjectResult[]; +} +type SearchQuery = Partial>; +type SearchParamNames = keyof SearchQuery; + +const ProgressByClientSearch: React.FC = ({ projects }) => { + const { t } = useTranslation("projects"); + + // If project searching is done on the server-side, then no need for this. + const [filteredProjects, setFilteredProjects] = useState(projects); + + const searchCriteria: Criterion[] = useMemo( + () => [ + { label: "Client Code", paramName: "clientCode", type: "text" }, + { label: "Client Name", paramName: "clientName", type: "text" }, + ], + [t], + ); + + const columns = useMemo[]>( + () => [ + { name: "clientCode", label: t("Project Code") }, + { name: "clientName", label: t("Project Name") }, + { name: "SubsidiaryClientCode", label: t("Project Category") }, + { name: "SubsidiaryClientName", label: t("Team") }, + { name: "NoOfProjects", label: t("Client") }, + ], + [t], + ); + + return ( + <> + { + console.log(query); + }} + /> + + items={filteredProjects} + columns={columns} + /> + + ); +}; + +export default ProgressByClientSearch; diff --git a/src/components/ProgressByClientSearch/ProgressByClientSearchLoading.tsx b/src/components/ProgressByClientSearch/ProgressByClientSearchLoading.tsx new file mode 100644 index 0000000..dd33fa0 --- /dev/null +++ b/src/components/ProgressByClientSearch/ProgressByClientSearchLoading.tsx @@ -0,0 +1,40 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +// Can make this nicer +export const ProgressByClientSearchLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ProgressByClientSearchLoading; diff --git a/src/components/ProgressByClientSearch/ProgressByClientSearchWrapper.tsx b/src/components/ProgressByClientSearch/ProgressByClientSearchWrapper.tsx new file mode 100644 index 0000000..b644fb4 --- /dev/null +++ b/src/components/ProgressByClientSearch/ProgressByClientSearchWrapper.tsx @@ -0,0 +1,18 @@ +import { fetchClientProjects } from "@/app/api/clientprojects"; +import React from "react"; +import ProgressByClientSearch from "./ProgressByClientSearch"; +import ProgressByClientSearchLoading from "./ProgressByClientSearchLoading"; + +interface SubComponents { + Loading: typeof ProgressByClientSearchLoading; +} + +const ProgressByClientSearchWrapper: React.FC & SubComponents = async () => { + const clentprojects = await fetchClientProjects(); + + return ; +}; + +ProgressByClientSearchWrapper.Loading = ProgressByClientSearchLoading; + +export default ProgressByClientSearchWrapper; diff --git a/src/components/ProgressByClientSearch/index.ts b/src/components/ProgressByClientSearch/index.ts new file mode 100644 index 0000000..4fdbe22 --- /dev/null +++ b/src/components/ProgressByClientSearch/index.ts @@ -0,0 +1 @@ +export { default } from "./ProgressByClientSearchWrapper"; diff --git a/src/components/SearchBox/SearchBox.tsx b/src/components/SearchBox/SearchBox.tsx index 542b049..24c03e8 100644 --- a/src/components/SearchBox/SearchBox.tsx +++ b/src/components/SearchBox/SearchBox.tsx @@ -77,7 +77,7 @@ function SearchBox({ criteria, onSearch }: Props) { return ( - {t("Search Criteria")} + {t("Search Criteria")} {criteria.map((c) => { return ( diff --git a/src/theme.ts b/src/theme.ts new file mode 100644 index 0000000..90d1aa6 --- /dev/null +++ b/src/theme.ts @@ -0,0 +1,12 @@ +import { createTheme } from '@mui/material/styles'; + +const theme = createTheme({ + palette: { + background: { + default: '#fcfcfc' + } + } + +}); + +export default theme; \ No newline at end of file diff --git a/src/theme/devias-material-kit/components.ts b/src/theme/devias-material-kit/components.ts index ee432e8..d1a360e 100644 --- a/src/theme/devias-material-kit/components.ts +++ b/src/theme/devias-material-kit/components.ts @@ -189,7 +189,7 @@ const components: ThemeOptions["components"] = { }, [`&.Mui-focused`]: { backgroundColor: "transparent", - borderColor: palette.primary.main, + borderColor: "palette.primary.main", boxShadow: `${palette.primary.main} 0 0 0 2px`, }, [`&.Mui-error`]: { @@ -216,7 +216,7 @@ const components: ThemeOptions["components"] = { [`&.Mui-focused`]: { backgroundColor: "transparent", [`& .MuiOutlinedInput-notchedOutline`]: { - borderColor: palette.primary.main, + borderColor: "palette.primary.main", boxShadow: `${palette.primary.main} 0 0 0 2px`, }, },