Przeglądaj źródła

Fetch assigned projects

tags/Baseline_30082024_FRONTEND_UAT
Wayne 1 rok temu
rodzic
commit
301bacdec6
10 zmienionych plików z 265 dodań i 268 usunięć
  1. +171
    -1
      package-lock.json
  2. +14
    -0
      src/app/api/projects/index.ts
  3. +22
    -17
      src/app/utils/fetchUtil.ts
  4. +4
    -1
      src/components/TimesheetModal/TimesheetModal.tsx
  5. +13
    -115
      src/components/TimesheetTable/EntryInputTable.tsx
  6. +18
    -7
      src/components/TimesheetTable/TimesheetTable.tsx
  7. +10
    -43
      src/components/UserWorkspacePage/AssignedProjects.tsx
  8. +3
    -20
      src/components/UserWorkspacePage/ProjectGrid.tsx
  9. +6
    -4
      src/components/UserWorkspacePage/UserWorkspacePage.tsx
  10. +4
    -60
      src/components/UserWorkspacePage/UserWorkspaceWrapper.tsx

+ 171
- 1
package-lock.json Wyświetl plik

@@ -11,6 +11,7 @@
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@faker-js/faker": "^8.4.1",
"@fontsource/inter": "^5.0.16",
"@fontsource/plus-jakarta-sans": "^5.0.18",
"@mui/icons-material": "^5.15.0",
@@ -37,7 +38,8 @@
"react-select": "^5.8.0",
"reactstrap": "^9.2.2",
"styled-components": "^6.1.8",
"sweetalert2": "^11.10.3"
"sweetalert2": "^11.10.3",
"xlsx-js-style": "^1.2.0"
},
"devDependencies": {
"@types/lodash": "^4.14.202",
@@ -1933,6 +1935,21 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@faker-js/faker": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/fakerjs"
}
],
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
"npm": ">=6.14.13"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
@@ -3530,6 +3547,21 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/adler-32": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz",
"integrity": "sha512-/vUqU/UY4MVeFsg+SsK6c+/05RZXIHZMGJA+PX5JyWI0ZRcBpupnRuPLU/NXXoFwMYCPCoxIfElM2eS+DUXCqQ==",
"dependencies": {
"exit-on-epipe": "~1.0.1",
"printj": "~1.1.0"
},
"bin": {
"adler32": "bin/adler32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -4150,6 +4182,26 @@
}
]
},
"node_modules/cfb": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"dependencies": {
"adler-32": "~1.3.0",
"crc-32": "~1.2.0"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/cfb/node_modules/adler-32": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -4236,6 +4288,26 @@
"node": ">=6"
}
},
"node_modules/codepage": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.14.0.tgz",
"integrity": "sha512-iz3zJLhlrg37/gYRWgEPkaFTtzmnEv1h+r7NgZum2lFElYQPi0/5bnmuDfODHxfp0INEfnRqyfyeIJDbb7ahRw==",
"dependencies": {
"commander": "~2.14.1",
"exit-on-epipe": "~1.0.1"
},
"bin": {
"codepage": "bin/codepage.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/codepage/node_modules/commander": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz",
"integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw=="
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -4316,6 +4388,17 @@
"node": ">=10"
}
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -5431,6 +5514,14 @@
"node": ">=0.8.x"
}
},
"node_modules/exit-on-epipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz",
"integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5476,6 +5567,11 @@
"reusify": "^1.0.4"
}
},
"node_modules/fflate": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.3.11.tgz",
"integrity": "sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A=="
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -5599,6 +5695,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@@ -7880,6 +7984,17 @@
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
},
"node_modules/printj": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz",
"integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==",
"bin": {
"printj": "bin/printj.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -8631,6 +8746,17 @@
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead"
},
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -9853,6 +9979,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wmf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/word": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/workbox-background-sync": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz",
@@ -10238,6 +10380,34 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/xlsx-js-style": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz",
"integrity": "sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==",
"dependencies": {
"adler-32": "~1.2.0",
"cfb": "^1.1.4",
"codepage": "~1.14.0",
"commander": "~2.17.1",
"crc-32": "~1.2.0",
"exit-on-epipe": "~1.0.1",
"fflate": "^0.3.8",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
},
"bin": {
"xlsx": "bin/xlsx.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/xlsx-js-style/node_modules/commander": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
"integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",


+ 14
- 0
src/app/api/projects/index.ts Wyświetl plik

@@ -59,6 +59,11 @@ export interface AssignedProject {
endDate: string;
};
};
// Manhour info
hoursSpent: number;
hoursSpentOther: number;
hoursAllocated: number;
hoursAllocatedOther: number;
}

export const preloadProjects = () => {
@@ -131,3 +136,12 @@ export const fetchProjectWorkNatures = cache(async () => {
next: { tags: ["projectWorkNatures"] },
});
});

export const fetchAssignedProjects = cache(async () => {
return serverFetchJson<AssignedProject[]>(
`${BASE_API_URL}/projects/assignedProjects`,
{
next: { tags: ["assignedProjects"] },
},
);
});

+ 22
- 17
src/app/utils/fetchUtil.ts Wyświetl plik

@@ -8,16 +8,17 @@ export const serverFetch: typeof fetch = async (input, init) => {
const session = await getServerSession<any, SessionWithTokens>(authOptions);
const accessToken = session?.accessToken;

console.log(accessToken)
console.log(accessToken);
return fetch(input, {
...init,
headers: {
...init?.headers,
...(accessToken
? {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}
Authorization: `Bearer ${accessToken}`,
Accept:
"application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}
: {}),
},
});
@@ -61,27 +62,28 @@ export async function serverFetchBlob<T>(...args: FetchParams) {
const response = await serverFetch(...args);

if (response.ok) {
const body = response.body
const body = response.body;
// console.log(body)
// console.log(body?.tee()[0].getReader())


const reader = body?.getReader()
let finalUInt8Array = new Uint8Array()
let done = false
const reader = body?.getReader();
let finalUInt8Array = new Uint8Array();
let done = false;

while (!done) {
const read = await reader?.read()
const read = await reader?.read();

// version 1
if (read?.done) {
done = true
done = true;
} else {
const tempUInt8Array = new Uint8Array(finalUInt8Array.length + read?.value.length!!)
tempUInt8Array.set(finalUInt8Array)
tempUInt8Array.set(read?.value!!, finalUInt8Array.length)
finalUInt8Array = new Uint8Array(tempUInt8Array.length!!)
finalUInt8Array.set(tempUInt8Array)
const tempUInt8Array = new Uint8Array(
finalUInt8Array.length + read?.value.length!,
);
tempUInt8Array.set(finalUInt8Array);
tempUInt8Array.set(read?.value!, finalUInt8Array.length);
finalUInt8Array = new Uint8Array(tempUInt8Array.length!);
finalUInt8Array.set(tempUInt8Array);

// console.log("1", finalUInt8Array)
}
@@ -115,7 +117,10 @@ export async function serverFetchBlob<T>(...args: FetchParams) {
// console.log("2", finalUInt8Array)
// console.log(bodyValue)

return { filename: response.headers.get("filename"), blobValue: finalUInt8Array } as T;
return {
filename: response.headers.get("filename"),
blobValue: finalUInt8Array,
} as T;
} else {
switch (response.status) {
case 401:


+ 4
- 1
src/components/TimesheetModal/TimesheetModal.tsx Wyświetl plik

@@ -16,11 +16,13 @@ import { FormProvider, useForm } from "react-hook-form";
import { RecordTimesheetInput } from "@/app/api/timesheets/actions";
import dayjs from "dayjs";
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import { AssignedProject } from "@/app/api/projects";

interface Props {
isOpen: boolean;
onClose: () => void;
timesheetType: "time" | "leave";
assignedProjects: AssignedProject[];
}

const modalSx: SxProps = {
@@ -37,6 +39,7 @@ const TimesheetModal: React.FC<Props> = ({
isOpen,
onClose,
timesheetType,
assignedProjects,
}) => {
const { t } = useTranslation("home");

@@ -73,7 +76,7 @@ const TimesheetModal: React.FC<Props> = ({
marginBlock: 4,
}}
>
<TimesheetTable />
<TimesheetTable assignedProjects={assignedProjects} />
</Box>
<CardActions sx={{ justifyContent: "flex-end" }}>
<Button


+ 13
- 115
src/components/TimesheetTable/EntryInputTable.tsx Wyświetl plik

@@ -26,113 +26,10 @@ import isBetween from "dayjs/plugin/isBetween";

dayjs.extend(isBetween);

const mockProjects: AssignedProject[] = [
{
id: 1,
name: "Consultancy Project A",
code: "M1001 (C)",
tasks: [
{
id: 1,
name: "1.1 Preparation of preliminary Cost Estimate / Cost Plan including Revised & Refined",
description: null,
taskGroup: {
id: 1,
name: "1. Design & Cost Planning / Estimating",
},
},
{
id: 6,
name: "2.1 Advise on tendering & contractual arrangement",
description: null,
taskGroup: {
id: 2,
name: "2. Tender Documentation",
},
},
],
milestones: {
1: {
startDate: "2000-01-01",
endDate: "2100-01-01",
},
2: {
startDate: "2100-01-01",
endDate: "2100-01-02",
},
},
},
{
id: 2,
name: "Consultancy Project B",
code: "M1354 (C)",
tasks: [
{
id: 1,
name: "1.1 Preparation of preliminary Cost Estimate / Cost Plan including Revised & Refined",
description: null,
taskGroup: {
id: 1,
name: "1. Design & Cost Planning / Estimating",
},
},
{
id: 10,
name: "3.5 Attend tender interviews",
description: null,
taskGroup: {
id: 3,
name: "3. Tender Analysis & Report & Contract Documentation",
},
},
],
milestones: {
1: {
startDate: "2000-01-01",
endDate: "2100-01-01",
},
3: {
startDate: "2100-01-01",
endDate: "2100-01-02",
},
},
},
{
id: 3,
name: "Consultancy Project C",
code: "M1973 (C)",
tasks: [
{
id: 1,
name: "1.1 Preparation of preliminary Cost Estimate / Cost Plan including Revised & Refined",
description: null,
taskGroup: {
id: 1,
name: "1. Design & Cost Planning / Estimating",
},
},
{
id: 20,
name: "4.10 Preparation of Statement of Final Account",
description: null,
taskGroup: {
id: 4,
name: "4. Construction / Post Construction",
},
},
],
milestones: {
1: {
startDate: "2000-01-01",
endDate: "2100-01-01",
},
4: {
startDate: "2100-01-01",
endDate: "2100-01-02",
},
},
},
];
interface Props {
day: string;
assignedProjects: AssignedProject[];
}

type TimeEntryRow = Partial<
TimeEntry & {
@@ -144,10 +41,10 @@ type TimeEntryRow = Partial<
}
>;

const EntryInputTable: React.FC<{ day: string }> = ({ day }) => {
const EntryInputTable: React.FC<Props> = ({ day, assignedProjects }) => {
const { t } = useTranslation("home");
const taskGroupsByProject = useMemo(() => {
return mockProjects.reduce<{
return assignedProjects.reduce<{
[projectId: AssignedProject["id"]]: {
value: TaskGroup["id"];
label: string;
@@ -164,16 +61,16 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => {
),
};
}, {});
}, []);
}, [assignedProjects]);

// To check for start / end planned dates
const milestonesByProject = useMemo(() => {
return mockProjects.reduce<{
return assignedProjects.reduce<{
[projectId: AssignedProject["id"]]: AssignedProject["milestones"];
}>((acc, project) => {
return { ...acc, [project.id]: { ...project.milestones } };
}, {});
}, []);
}, [assignedProjects]);

const { getValues, setValue } = useFormContext<RecordTimesheetInput>();
const currentEntries = getValues(day);
@@ -322,7 +219,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => {
editable: true,
type: "singleSelect",
valueOptions() {
return mockProjects.map((p) => ({ value: p.id, label: p.name }));
return assignedProjects.map((p) => ({ value: p.id, label: p.name }));
},
},
{
@@ -339,7 +236,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => {
return [];
}

const projectInfo = mockProjects.find(
const projectInfo = assignedProjects.find(
(p) => p.id === updatedRow.projectId,
);

@@ -364,7 +261,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => {
return [];
}

const projectInfo = mockProjects.find(
const projectInfo = assignedProjects.find(
(p) => p.id === updatedRow.projectId,
);

@@ -399,6 +296,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => {
handleCancel,
apiRef,
taskGroupsByProject,
assignedProjects,
],
);



+ 18
- 7
src/components/TimesheetTable/TimesheetTable.tsx Wyświetl plik

@@ -18,8 +18,13 @@ import React, { useState } from "react";
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import EntryInputTable from "./EntryInputTable";
import { AssignedProject } from "@/app/api/projects";

const TimesheetTable: React.FC = () => {
interface Props {
assignedProjects: AssignedProject[];
}

const TimesheetTable: React.FC<Props> = ({ assignedProjects }) => {
const { t } = useTranslation("home");

const { watch } = useFormContext<RecordTimesheetInput>();
@@ -40,7 +45,12 @@ const TimesheetTable: React.FC = () => {
{days.map((day, index) => {
const entries = currentInput[day];
return (
<DayRow key={`${day}${index}`} day={day} entries={entries} />
<DayRow
key={`${day}${index}`}
day={day}
entries={entries}
assignedProjects={assignedProjects}
/>
);
})}
</TableBody>
@@ -49,10 +59,11 @@ const TimesheetTable: React.FC = () => {
);
};

const DayRow: React.FC<{ day: string; entries: TimeEntry[] }> = ({
day,
entries,
}) => {
const DayRow: React.FC<{
day: string;
entries: TimeEntry[];
assignedProjects: AssignedProject[];
}> = ({ day, entries, assignedProjects }) => {
const {
t,
i18n: { language },
@@ -106,7 +117,7 @@ const DayRow: React.FC<{ day: string; entries: TimeEntry[] }> = ({
>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box>
<EntryInputTable day={day} />
<EntryInputTable day={day} assignedProjects={assignedProjects} />
</Box>
</Collapse>
</TableCell>


+ 10
- 43
src/components/UserWorkspacePage/AssignedProjects.tsx Wyświetl plik

@@ -1,15 +1,10 @@
import React, { useEffect, useMemo } from "react";
import React, { useEffect } from "react";
import {
Card,
CardContent,
FormControl,
Grid,
IconButton,
InputAdornment,
InputLabel,
MenuItem,
Select,
SelectChangeEvent,
Stack,
TextField,
Typography,
@@ -18,13 +13,15 @@ import { useTranslation } from "react-i18next";
import { Clear, Search } from "@mui/icons-material";
import ProjectGrid from "./ProjectGrid";
import { Props as UserWorkspaceProps } from "./UserWorkspacePage";
import uniq from "lodash/uniq";

const AssignedProjects: React.FC<UserWorkspaceProps> = ({ allProjects }) => {
const AssignedProjects: React.FC<UserWorkspaceProps> = ({
assignedProjects,
}) => {
const { t } = useTranslation("home");

// Projects
const [filteredProjects, setFilterProjects] = React.useState(allProjects);
const [filteredProjects, setFilterProjects] =
React.useState(assignedProjects);

// Query related
const [query, setQuery] = React.useState("");
@@ -37,28 +34,15 @@ const AssignedProjects: React.FC<UserWorkspaceProps> = ({ allProjects }) => {
setQuery("");
}, []);

// Filter
const allStatuses = useMemo(() => {
return uniq([
"All",
...allProjects.map((project) => project.projectStatus),
]);
}, [allProjects]);
const [statusFilter, setStatusFilter] = React.useState("All");
const onStatusChange = React.useCallback((e: SelectChangeEvent) => {
setStatusFilter(e.target.value);
}, []);

useEffect(() => {
setFilterProjects(
allProjects.filter(
assignedProjects.filter(
(p) =>
(p.code.toLowerCase().includes(query.toLowerCase()) ||
p.name.toLowerCase().includes(query.toLowerCase())) &&
(p.projectStatus === statusFilter || statusFilter === "All"),
p.code.toLowerCase().includes(query.toLowerCase()) ||
p.name.toLowerCase().includes(query.toLowerCase()),
),
);
}, [allProjects, query, statusFilter]);
}, [assignedProjects, query]);

return (
<>
@@ -88,23 +72,6 @@ const AssignedProjects: React.FC<UserWorkspaceProps> = ({ allProjects }) => {
}}
/>
</Grid>
<Grid item xs={3}>
<FormControl fullWidth>
<InputLabel size="small">{t("Project Status")}</InputLabel>
<Select
label={t("Project Status")}
size="small"
value={statusFilter}
onChange={onStatusChange}
>
{allStatuses.map((option, index) => (
<MenuItem key={`${option}-${index}`} value={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</Stack>
</CardContent>


+ 3
- 20
src/components/UserWorkspacePage/ProjectGrid.tsx Wyświetl plik

@@ -1,11 +1,11 @@
import React from "react";
import { ProjectHours } from "./UserWorkspaceWrapper";
import { Box, Card, CardContent, Chip, Grid, Typography } from "@mui/material";
import { Box, Card, CardContent, Grid, Typography } from "@mui/material";
import { useTranslation } from "react-i18next";
import { manhourFormatter } from "@/app/utils/formatUtil";
import { AssignedProject } from "@/app/api/projects";

interface Props {
projects: ProjectHours[];
projects: AssignedProject[];
}

const ProjectGrid: React.FC<Props> = ({ projects }) => {
@@ -17,23 +17,6 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => {
<Grid key={`${project.code}${idx}`} item xs={4}>
<Card>
<CardContent>
<Box
sx={{
display: "flex",
justifyContent: "flex-end",
marginBlockEnd: 1,
}}
>
<Chip
size="small"
label={project.projectStatus}
color={
project.projectStatus === "On Track"
? "success"
: "warning"
}
/>
</Box>
<Typography variant="overline">{project.code}</Typography>
<Typography
variant="h6"


+ 6
- 4
src/components/UserWorkspacePage/UserWorkspacePage.tsx Wyświetl plik

@@ -8,14 +8,14 @@ import { Add } from "@mui/icons-material";
import { Typography } from "@mui/material";
import ButtonGroup from "@mui/material/ButtonGroup";
import AssignedProjects from "./AssignedProjects";
import { ProjectHours } from "./UserWorkspaceWrapper";
import TimesheetModal from "../TimesheetModal";
import { AssignedProject } from "@/app/api/projects";

export interface Props {
allProjects: ProjectHours[];
assignedProjects: AssignedProject[];
}

const UserWorkspacePage: React.FC<Props> = ({ allProjects }) => {
const UserWorkspacePage: React.FC<Props> = ({ assignedProjects }) => {
const [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false);
const [isLeaveModalVisible, setLeaveModalVisible] = useState(false);
const { t } = useTranslation("home");
@@ -67,13 +67,15 @@ const UserWorkspacePage: React.FC<Props> = ({ allProjects }) => {
timesheetType="time"
isOpen={isTimeheetModalVisible}
onClose={handleCloseTimesheetModal}
assignedProjects={assignedProjects}
/>
<TimesheetModal
timesheetType="leave"
isOpen={isLeaveModalVisible}
onClose={handleCloseLeaveModal}
assignedProjects={assignedProjects}
/>
<AssignedProjects allProjects={allProjects} />
<AssignedProjects assignedProjects={assignedProjects} />
</>
);
};


+ 4
- 60
src/components/UserWorkspacePage/UserWorkspaceWrapper.tsx Wyświetl plik

@@ -1,65 +1,9 @@
import { fetchAssignedProjects } from "@/app/api/projects";
import UserWorkspacePage from "./UserWorkspacePage";

export interface ProjectHours {
code: string;
name: string;
hoursSpent: number;
hoursSpentOther: number;
hoursAllocated: number;
hoursAllocatedOther: number;
projectStatus: "On Track" | "Potential Delay";
}

const mockProjectCards: ProjectHours[] = [
{
code: "M1001 (C)",
name: "Consultancy Project A",
hoursSpent: 12.75,
hoursSpentOther: 0.0,
hoursAllocated: 150.0,
hoursAllocatedOther: 30.0,
projectStatus: "On Track",
},
{
code: "M1301 (C)",
name: "Consultancy Project AAA",
hoursSpent: 4.25,
hoursSpentOther: 0.25,
hoursAllocated: 30.0,
hoursAllocatedOther: 0.0,
projectStatus: "On Track",
},
{
code: "M1354 (C)",
name: "Consultancy Project BBB",
hoursSpent: 57.0,
hoursSpentOther: 6.5,
hoursAllocated: 100.0,
hoursAllocatedOther: 20.0,
projectStatus: "On Track",
},
{
code: "M1973 (C)",
name: "Construction Project CCC",
hoursSpent: 12.75,
hoursSpentOther: 0.0,
hoursAllocated: 150.0,
hoursAllocatedOther: 30.0,
projectStatus: "Potential Delay",
},
{
code: "M2014 (T)",
name: "Consultancy Project DDD",
hoursSpent: 1.0,
hoursSpentOther: 0.0,
hoursAllocated: 10.0,
hoursAllocatedOther: 0.0,
projectStatus: "Potential Delay",
},
];

const UserWorkspaceWrapper: React.FC = () => {
return <UserWorkspacePage allProjects={mockProjectCards} />;
const UserWorkspaceWrapper: React.FC = async () => {
const assignedProjects = await fetchAssignedProjects();
return <UserWorkspacePage assignedProjects={assignedProjects} />;
};

export default UserWorkspaceWrapper;

Ładowanie…
Anuluj
Zapisz