@@ -11,6 +11,7 @@ | |||||
"@emotion/cache": "^11.11.0", | "@emotion/cache": "^11.11.0", | ||||
"@emotion/react": "^11.11.1", | "@emotion/react": "^11.11.1", | ||||
"@emotion/styled": "^11.11.0", | "@emotion/styled": "^11.11.0", | ||||
"@faker-js/faker": "^8.4.1", | |||||
"@fontsource/inter": "^5.0.16", | "@fontsource/inter": "^5.0.16", | ||||
"@fontsource/plus-jakarta-sans": "^5.0.18", | "@fontsource/plus-jakarta-sans": "^5.0.18", | ||||
"@mui/icons-material": "^5.15.0", | "@mui/icons-material": "^5.15.0", | ||||
@@ -37,7 +38,8 @@ | |||||
"react-select": "^5.8.0", | "react-select": "^5.8.0", | ||||
"reactstrap": "^9.2.2", | "reactstrap": "^9.2.2", | ||||
"styled-components": "^6.1.8", | "styled-components": "^6.1.8", | ||||
"sweetalert2": "^11.10.3" | |||||
"sweetalert2": "^11.10.3", | |||||
"xlsx-js-style": "^1.2.0" | |||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@types/lodash": "^4.14.202", | "@types/lodash": "^4.14.202", | ||||
@@ -1933,6 +1935,21 @@ | |||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" | "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": { | "node_modules/@floating-ui/core": { | ||||
"version": "1.6.0", | "version": "1.6.0", | ||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", | "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" | "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": { | "node_modules/ajv": { | ||||
"version": "6.12.6", | "version": "6.12.6", | ||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", | "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": { | "node_modules/chalk": { | ||||
"version": "2.4.2", | "version": "2.4.2", | ||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", | ||||
@@ -4236,6 +4288,26 @@ | |||||
"node": ">=6" | "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": { | "node_modules/color-convert": { | ||||
"version": "1.9.3", | "version": "1.9.3", | ||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", | ||||
@@ -4316,6 +4388,17 @@ | |||||
"node": ">=10" | "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": { | "node_modules/cross-spawn": { | ||||
"version": "7.0.3", | "version": "7.0.3", | ||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", | ||||
@@ -5431,6 +5514,14 @@ | |||||
"node": ">=0.8.x" | "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": { | "node_modules/fast-deep-equal": { | ||||
"version": "3.1.3", | "version": "3.1.3", | ||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", | ||||
@@ -5476,6 +5567,11 @@ | |||||
"reusify": "^1.0.4" | "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": { | "node_modules/file-entry-cache": { | ||||
"version": "6.0.1", | "version": "6.0.1", | ||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", | "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" | "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": { | "node_modules/fraction.js": { | ||||
"version": "4.3.7", | "version": "4.3.7", | ||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", | "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", | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", | ||||
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" | "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": { | "node_modules/prop-types": { | ||||
"version": "15.8.1", | "version": "15.8.1", | ||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", | ||||
@@ -8631,6 +8746,17 @@ | |||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", | "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", | ||||
"deprecated": "Please use @jridgewell/sourcemap-codec instead" | "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": { | "node_modules/streamsearch": { | ||||
"version": "1.1.0", | "version": "1.1.0", | ||||
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", | ||||
@@ -9853,6 +9979,22 @@ | |||||
"url": "https://github.com/sponsors/ljharb" | "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": { | "node_modules/workbox-background-sync": { | ||||
"version": "6.6.0", | "version": "6.6.0", | ||||
"resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-6.6.0.tgz", | "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", | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" | "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": { | "node_modules/yallist": { | ||||
"version": "4.0.0", | "version": "4.0.0", | ||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", | ||||
@@ -59,6 +59,11 @@ export interface AssignedProject { | |||||
endDate: string; | endDate: string; | ||||
}; | }; | ||||
}; | }; | ||||
// Manhour info | |||||
hoursSpent: number; | |||||
hoursSpentOther: number; | |||||
hoursAllocated: number; | |||||
hoursAllocatedOther: number; | |||||
} | } | ||||
export const preloadProjects = () => { | export const preloadProjects = () => { | ||||
@@ -131,3 +136,12 @@ export const fetchProjectWorkNatures = cache(async () => { | |||||
next: { tags: ["projectWorkNatures"] }, | next: { tags: ["projectWorkNatures"] }, | ||||
}); | }); | ||||
}); | }); | ||||
export const fetchAssignedProjects = cache(async () => { | |||||
return serverFetchJson<AssignedProject[]>( | |||||
`${BASE_API_URL}/projects/assignedProjects`, | |||||
{ | |||||
next: { tags: ["assignedProjects"] }, | |||||
}, | |||||
); | |||||
}); |
@@ -8,16 +8,17 @@ export const serverFetch: typeof fetch = async (input, init) => { | |||||
const session = await getServerSession<any, SessionWithTokens>(authOptions); | const session = await getServerSession<any, SessionWithTokens>(authOptions); | ||||
const accessToken = session?.accessToken; | const accessToken = session?.accessToken; | ||||
console.log(accessToken) | |||||
console.log(accessToken); | |||||
return fetch(input, { | return fetch(input, { | ||||
...init, | ...init, | ||||
headers: { | headers: { | ||||
...init?.headers, | ...init?.headers, | ||||
...(accessToken | ...(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); | const response = await serverFetch(...args); | ||||
if (response.ok) { | if (response.ok) { | ||||
const body = response.body | |||||
const body = response.body; | |||||
// console.log(body) | // console.log(body) | ||||
// console.log(body?.tee()[0].getReader()) | // 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) { | while (!done) { | ||||
const read = await reader?.read() | |||||
const read = await reader?.read(); | |||||
// version 1 | // version 1 | ||||
if (read?.done) { | if (read?.done) { | ||||
done = true | |||||
done = true; | |||||
} else { | } 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) | // console.log("1", finalUInt8Array) | ||||
} | } | ||||
@@ -115,7 +117,10 @@ export async function serverFetchBlob<T>(...args: FetchParams) { | |||||
// console.log("2", finalUInt8Array) | // console.log("2", finalUInt8Array) | ||||
// console.log(bodyValue) | // console.log(bodyValue) | ||||
return { filename: response.headers.get("filename"), blobValue: finalUInt8Array } as T; | |||||
return { | |||||
filename: response.headers.get("filename"), | |||||
blobValue: finalUInt8Array, | |||||
} as T; | |||||
} else { | } else { | ||||
switch (response.status) { | switch (response.status) { | ||||
case 401: | case 401: | ||||
@@ -16,11 +16,13 @@ import { FormProvider, useForm } from "react-hook-form"; | |||||
import { RecordTimesheetInput } from "@/app/api/timesheets/actions"; | import { RecordTimesheetInput } from "@/app/api/timesheets/actions"; | ||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
import { AssignedProject } from "@/app/api/projects"; | |||||
interface Props { | interface Props { | ||||
isOpen: boolean; | isOpen: boolean; | ||||
onClose: () => void; | onClose: () => void; | ||||
timesheetType: "time" | "leave"; | timesheetType: "time" | "leave"; | ||||
assignedProjects: AssignedProject[]; | |||||
} | } | ||||
const modalSx: SxProps = { | const modalSx: SxProps = { | ||||
@@ -37,6 +39,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
isOpen, | isOpen, | ||||
onClose, | onClose, | ||||
timesheetType, | timesheetType, | ||||
assignedProjects, | |||||
}) => { | }) => { | ||||
const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
@@ -73,7 +76,7 @@ const TimesheetModal: React.FC<Props> = ({ | |||||
marginBlock: 4, | marginBlock: 4, | ||||
}} | }} | ||||
> | > | ||||
<TimesheetTable /> | |||||
<TimesheetTable assignedProjects={assignedProjects} /> | |||||
</Box> | </Box> | ||||
<CardActions sx={{ justifyContent: "flex-end" }}> | <CardActions sx={{ justifyContent: "flex-end" }}> | ||||
<Button | <Button | ||||
@@ -26,113 +26,10 @@ import isBetween from "dayjs/plugin/isBetween"; | |||||
dayjs.extend(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< | type TimeEntryRow = Partial< | ||||
TimeEntry & { | 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 { t } = useTranslation("home"); | ||||
const taskGroupsByProject = useMemo(() => { | const taskGroupsByProject = useMemo(() => { | ||||
return mockProjects.reduce<{ | |||||
return assignedProjects.reduce<{ | |||||
[projectId: AssignedProject["id"]]: { | [projectId: AssignedProject["id"]]: { | ||||
value: TaskGroup["id"]; | value: TaskGroup["id"]; | ||||
label: string; | label: string; | ||||
@@ -164,16 +61,16 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => { | |||||
), | ), | ||||
}; | }; | ||||
}, {}); | }, {}); | ||||
}, []); | |||||
}, [assignedProjects]); | |||||
// To check for start / end planned dates | // To check for start / end planned dates | ||||
const milestonesByProject = useMemo(() => { | const milestonesByProject = useMemo(() => { | ||||
return mockProjects.reduce<{ | |||||
return assignedProjects.reduce<{ | |||||
[projectId: AssignedProject["id"]]: AssignedProject["milestones"]; | [projectId: AssignedProject["id"]]: AssignedProject["milestones"]; | ||||
}>((acc, project) => { | }>((acc, project) => { | ||||
return { ...acc, [project.id]: { ...project.milestones } }; | return { ...acc, [project.id]: { ...project.milestones } }; | ||||
}, {}); | }, {}); | ||||
}, []); | |||||
}, [assignedProjects]); | |||||
const { getValues, setValue } = useFormContext<RecordTimesheetInput>(); | const { getValues, setValue } = useFormContext<RecordTimesheetInput>(); | ||||
const currentEntries = getValues(day); | const currentEntries = getValues(day); | ||||
@@ -322,7 +219,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => { | |||||
editable: true, | editable: true, | ||||
type: "singleSelect", | type: "singleSelect", | ||||
valueOptions() { | 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 []; | return []; | ||||
} | } | ||||
const projectInfo = mockProjects.find( | |||||
const projectInfo = assignedProjects.find( | |||||
(p) => p.id === updatedRow.projectId, | (p) => p.id === updatedRow.projectId, | ||||
); | ); | ||||
@@ -364,7 +261,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => { | |||||
return []; | return []; | ||||
} | } | ||||
const projectInfo = mockProjects.find( | |||||
const projectInfo = assignedProjects.find( | |||||
(p) => p.id === updatedRow.projectId, | (p) => p.id === updatedRow.projectId, | ||||
); | ); | ||||
@@ -399,6 +296,7 @@ const EntryInputTable: React.FC<{ day: string }> = ({ day }) => { | |||||
handleCancel, | handleCancel, | ||||
apiRef, | apiRef, | ||||
taskGroupsByProject, | taskGroupsByProject, | ||||
assignedProjects, | |||||
], | ], | ||||
); | ); | ||||
@@ -18,8 +18,13 @@ import React, { useState } from "react"; | |||||
import { useFormContext } from "react-hook-form"; | import { useFormContext } from "react-hook-form"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
import EntryInputTable from "./EntryInputTable"; | 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 { t } = useTranslation("home"); | ||||
const { watch } = useFormContext<RecordTimesheetInput>(); | const { watch } = useFormContext<RecordTimesheetInput>(); | ||||
@@ -40,7 +45,12 @@ const TimesheetTable: React.FC = () => { | |||||
{days.map((day, index) => { | {days.map((day, index) => { | ||||
const entries = currentInput[day]; | const entries = currentInput[day]; | ||||
return ( | return ( | ||||
<DayRow key={`${day}${index}`} day={day} entries={entries} /> | |||||
<DayRow | |||||
key={`${day}${index}`} | |||||
day={day} | |||||
entries={entries} | |||||
assignedProjects={assignedProjects} | |||||
/> | |||||
); | ); | ||||
})} | })} | ||||
</TableBody> | </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 { | const { | ||||
t, | t, | ||||
i18n: { language }, | i18n: { language }, | ||||
@@ -106,7 +117,7 @@ const DayRow: React.FC<{ day: string; entries: TimeEntry[] }> = ({ | |||||
> | > | ||||
<Collapse in={open} timeout="auto" unmountOnExit> | <Collapse in={open} timeout="auto" unmountOnExit> | ||||
<Box> | <Box> | ||||
<EntryInputTable day={day} /> | |||||
<EntryInputTable day={day} assignedProjects={assignedProjects} /> | |||||
</Box> | </Box> | ||||
</Collapse> | </Collapse> | ||||
</TableCell> | </TableCell> | ||||
@@ -1,15 +1,10 @@ | |||||
import React, { useEffect, useMemo } from "react"; | |||||
import React, { useEffect } from "react"; | |||||
import { | import { | ||||
Card, | Card, | ||||
CardContent, | CardContent, | ||||
FormControl, | |||||
Grid, | Grid, | ||||
IconButton, | IconButton, | ||||
InputAdornment, | InputAdornment, | ||||
InputLabel, | |||||
MenuItem, | |||||
Select, | |||||
SelectChangeEvent, | |||||
Stack, | Stack, | ||||
TextField, | TextField, | ||||
Typography, | Typography, | ||||
@@ -18,13 +13,15 @@ import { useTranslation } from "react-i18next"; | |||||
import { Clear, Search } from "@mui/icons-material"; | import { Clear, Search } from "@mui/icons-material"; | ||||
import ProjectGrid from "./ProjectGrid"; | import ProjectGrid from "./ProjectGrid"; | ||||
import { Props as UserWorkspaceProps } from "./UserWorkspacePage"; | 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"); | const { t } = useTranslation("home"); | ||||
// Projects | // Projects | ||||
const [filteredProjects, setFilterProjects] = React.useState(allProjects); | |||||
const [filteredProjects, setFilterProjects] = | |||||
React.useState(assignedProjects); | |||||
// Query related | // Query related | ||||
const [query, setQuery] = React.useState(""); | const [query, setQuery] = React.useState(""); | ||||
@@ -37,28 +34,15 @@ const AssignedProjects: React.FC<UserWorkspaceProps> = ({ allProjects }) => { | |||||
setQuery(""); | 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(() => { | useEffect(() => { | ||||
setFilterProjects( | setFilterProjects( | ||||
allProjects.filter( | |||||
assignedProjects.filter( | |||||
(p) => | (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 ( | return ( | ||||
<> | <> | ||||
@@ -88,23 +72,6 @@ const AssignedProjects: React.FC<UserWorkspaceProps> = ({ allProjects }) => { | |||||
}} | }} | ||||
/> | /> | ||||
</Grid> | </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> | </Grid> | ||||
</Stack> | </Stack> | ||||
</CardContent> | </CardContent> | ||||
@@ -1,11 +1,11 @@ | |||||
import React from "react"; | 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 { useTranslation } from "react-i18next"; | ||||
import { manhourFormatter } from "@/app/utils/formatUtil"; | import { manhourFormatter } from "@/app/utils/formatUtil"; | ||||
import { AssignedProject } from "@/app/api/projects"; | |||||
interface Props { | interface Props { | ||||
projects: ProjectHours[]; | |||||
projects: AssignedProject[]; | |||||
} | } | ||||
const ProjectGrid: React.FC<Props> = ({ projects }) => { | const ProjectGrid: React.FC<Props> = ({ projects }) => { | ||||
@@ -17,23 +17,6 @@ const ProjectGrid: React.FC<Props> = ({ projects }) => { | |||||
<Grid key={`${project.code}${idx}`} item xs={4}> | <Grid key={`${project.code}${idx}`} item xs={4}> | ||||
<Card> | <Card> | ||||
<CardContent> | <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="overline">{project.code}</Typography> | ||||
<Typography | <Typography | ||||
variant="h6" | variant="h6" | ||||
@@ -8,14 +8,14 @@ import { Add } from "@mui/icons-material"; | |||||
import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
import ButtonGroup from "@mui/material/ButtonGroup"; | import ButtonGroup from "@mui/material/ButtonGroup"; | ||||
import AssignedProjects from "./AssignedProjects"; | import AssignedProjects from "./AssignedProjects"; | ||||
import { ProjectHours } from "./UserWorkspaceWrapper"; | |||||
import TimesheetModal from "../TimesheetModal"; | import TimesheetModal from "../TimesheetModal"; | ||||
import { AssignedProject } from "@/app/api/projects"; | |||||
export interface Props { | 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 [isTimeheetModalVisible, setTimeheetModalVisible] = useState(false); | ||||
const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | const [isLeaveModalVisible, setLeaveModalVisible] = useState(false); | ||||
const { t } = useTranslation("home"); | const { t } = useTranslation("home"); | ||||
@@ -67,13 +67,15 @@ const UserWorkspacePage: React.FC<Props> = ({ allProjects }) => { | |||||
timesheetType="time" | timesheetType="time" | ||||
isOpen={isTimeheetModalVisible} | isOpen={isTimeheetModalVisible} | ||||
onClose={handleCloseTimesheetModal} | onClose={handleCloseTimesheetModal} | ||||
assignedProjects={assignedProjects} | |||||
/> | /> | ||||
<TimesheetModal | <TimesheetModal | ||||
timesheetType="leave" | timesheetType="leave" | ||||
isOpen={isLeaveModalVisible} | isOpen={isLeaveModalVisible} | ||||
onClose={handleCloseLeaveModal} | onClose={handleCloseLeaveModal} | ||||
assignedProjects={assignedProjects} | |||||
/> | /> | ||||
<AssignedProjects allProjects={allProjects} /> | |||||
<AssignedProjects assignedProjects={assignedProjects} /> | |||||
</> | </> | ||||
); | ); | ||||
}; | }; | ||||
@@ -1,65 +1,9 @@ | |||||
import { fetchAssignedProjects } from "@/app/api/projects"; | |||||
import UserWorkspacePage from "./UserWorkspacePage"; | 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; | export default UserWorkspaceWrapper; |