From 901cbab93df6c227839ffd2bbd1071e593910d7a Mon Sep 17 00:00:00 2001 From: paola9896 Date: Thu, 22 Sep 2022 20:16:42 +0000 Subject: [PATCH 01/10] metrics --- package-lock.json | 10 + package.json | 2 + src/img/icons/metrics_color.png | Bin 0 -> 6777 bytes src/js/Layout.js | 1 + src/js/PrivateLayout.js | 69 +- src/js/actions.js | 234 +++--- .../applicant-card/EmployeeExtendedCard.jsx | 6 +- src/js/csrftoken.js | 2 +- src/js/utils/api_wrapper.js | 12 - src/js/views/auth.js | 10 +- src/js/views/favorites.js | 2 - src/js/views/metrics/charts.js | 45 ++ .../Employees/DummyDataShifts.js | 253 +++++++ .../Employees/DummyDataWorkers.js | 1 + .../general-stats/Employees/Employees.js | 88 +++ .../general-stats/Employees/EmployeesData.js | 97 +++ .../metrics/general-stats/GeneralStats.js | 50 ++ .../general-stats/Hours/DummyDataShifts.js | 244 +++++++ .../general-stats/Hours/DummyDataWorkers.js | 1 + .../metrics/general-stats/Hours/Hours.js | 84 +++ .../metrics/general-stats/Hours/HoursData.js | 72 ++ .../metrics/general-stats/Shifts/DummyData.js | 282 +++++++ .../metrics/general-stats/Shifts/Shifts.js | 90 +++ .../general-stats/Shifts/ShiftsData.js | 94 +++ src/js/views/metrics/metrics.js | 166 +++++ .../metrics/punctuality/DummyDataShifts.js | 381 ++++++++++ .../views/metrics/punctuality/Punctuality.js | 82 +++ .../metrics/punctuality/PunctualityData.js | 42 ++ src/js/views/metrics/queue/DummyDataShifts.js | 343 +++++++++ .../views/metrics/queue/DummyDataWorkers.js | 687 ++++++++++++++++++ src/js/views/metrics/queue/Queue.js | 129 ++++ src/js/views/metrics/queue/WorkerStats.js | 115 +++ .../views/metrics/ratings/DummyDataWorkers.js | 28 + src/js/views/metrics/ratings/Ratings.js | 98 +++ src/js/views/metrics/ratings/RatingsData.js | 69 ++ src/js/views/profile.js | 1 - src/js/views/shift.test.js | 69 -- src/js/views/talents.js | 9 +- src/styles/_icons.scss | 1 + 39 files changed, 3725 insertions(+), 244 deletions(-) create mode 100644 src/img/icons/metrics_color.png create mode 100644 src/js/views/metrics/charts.js create mode 100644 src/js/views/metrics/general-stats/Employees/DummyDataShifts.js create mode 100644 src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js create mode 100644 src/js/views/metrics/general-stats/Employees/Employees.js create mode 100644 src/js/views/metrics/general-stats/Employees/EmployeesData.js create mode 100644 src/js/views/metrics/general-stats/GeneralStats.js create mode 100644 src/js/views/metrics/general-stats/Hours/DummyDataShifts.js create mode 100644 src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js create mode 100644 src/js/views/metrics/general-stats/Hours/Hours.js create mode 100644 src/js/views/metrics/general-stats/Hours/HoursData.js create mode 100644 src/js/views/metrics/general-stats/Shifts/DummyData.js create mode 100644 src/js/views/metrics/general-stats/Shifts/Shifts.js create mode 100644 src/js/views/metrics/general-stats/Shifts/ShiftsData.js create mode 100644 src/js/views/metrics/metrics.js create mode 100644 src/js/views/metrics/punctuality/DummyDataShifts.js create mode 100644 src/js/views/metrics/punctuality/Punctuality.js create mode 100644 src/js/views/metrics/punctuality/PunctualityData.js create mode 100644 src/js/views/metrics/queue/DummyDataShifts.js create mode 100644 src/js/views/metrics/queue/DummyDataWorkers.js create mode 100644 src/js/views/metrics/queue/Queue.js create mode 100644 src/js/views/metrics/queue/WorkerStats.js create mode 100644 src/js/views/metrics/ratings/DummyDataWorkers.js create mode 100644 src/js/views/metrics/ratings/Ratings.js create mode 100644 src/js/views/metrics/ratings/RatingsData.js delete mode 100644 src/js/views/shift.test.js diff --git a/package-lock.json b/package-lock.json index 5955ff4..0086de9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9114,6 +9114,11 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", "dev": true }, + "chart.js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz", + "integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==" + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -26632,6 +26637,11 @@ "lodash.isequal": "^4.5.0" } }, + "react-chartjs-2": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.3.1.tgz", + "integrity": "sha512-5i3mjP6tU7QSn0jvb8I4hudTzHJqS8l00ORJnVwI2sYu0ihpj83Lv2YzfxunfxTZkscKvZu2F2w9LkwNBhj6xA==" + }, "react-cropper": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/react-cropper/-/react-cropper-2.1.4.tgz", diff --git a/package.json b/package.json index aee5c2b..35aa193 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "bc-react-notifier": "^1.1.6", "bc-react-session": "^1.5.2", "bootstrap": "^4.4.1", + "chart.js": "^3.9.1", "deepmerge": "^2.1.1", "downloadjs": "^1.4.7", "events": "^1.1.1", @@ -118,6 +119,7 @@ "react": "^16.8.6", "react-ace": "^5.9.0", "react-calendar-timeline": "^0.22.0", + "react-chartjs-2": "^4.3.1", "react-cropper": "^2.1.4", "react-datepicker": "^4.7.0", "react-datetime": "^2.15.0", diff --git a/src/img/icons/metrics_color.png b/src/img/icons/metrics_color.png new file mode 100644 index 0000000000000000000000000000000000000000..9a09c3b205510cf648bcebbebf5a54b1078f4233 GIT binary patch literal 6777 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 + diff --git a/src/js/PrivateLayout.js b/src/js/PrivateLayout.js index 95e3c4b..55fd971 100644 --- a/src/js/PrivateLayout.js +++ b/src/js/PrivateLayout.js @@ -85,6 +85,7 @@ import { } from "./views/ratings.js"; import { PaymentsReport } from "./views/payments-report"; import { DeductionsReport } from "./views/deductions-report"; +import { Metrics } from "./views/metrics/metrics"; import { Profile, ManageUsers, @@ -110,7 +111,6 @@ import "bootstrap/dist/js/bootstrap.min.js"; import { CheckEmployeeDocuments2 } from "./views/check-documents2"; import Gleap from 'gleap'; - class PrivateLayout extends Flux.DashView { constructor() { super(); @@ -394,7 +394,7 @@ class PrivateLayout extends Flux.DashView { case "add_talent_to_favlist": option.title = "Search for the talent"; this.showRightBar(AddTalentToFavlist, option, { - formData: Favlist(option.data).getFormData(), + formData: Favlist(option.data).getFormData(), }); break; case "show_single_rating": @@ -644,8 +644,8 @@ class PrivateLayout extends Flux.DashView { level == "all" ? 0 : level == "last" - ? this.state.sideBarLevels.length - 1 - : parseInt(level, 10); + ? this.state.sideBarLevels.length - 1 + : parseInt(level, 10); // const lastLevel = this.state.sideBarLevels[this.state.sideBarLevels.length-1]; // if(Array.isArray(lastLevel.watchers)) lastLevel.watchers.forEach((w) => w.unsubscribe()); const newLevels = this.state.sideBarLevels.filter((e, i) => i < level); @@ -739,7 +739,7 @@ class PrivateLayout extends Flux.DashView { style={{ backgroundImage: `url(${logoURL})` }} /> ); - + return ( Profile +
  • + + + Metrics + +

  • Help
  • */} -
  • - - +
  • + +
  • @@ -961,8 +967,8 @@ class PrivateLayout extends Flux.DashView { b.updated_at < a.updated_at ? -1 : b.updated_at > a.updated_at - ? 1 - : 0 + ? 1 + : 0 ) .map((emp, i) => { return ( @@ -1021,13 +1027,13 @@ class PrivateLayout extends Flux.DashView { {emp.ended_at ? "clocked out at " + - moment(emp.ended_at).format( - "MM/DD/YYYY, hh:mm A" - ) + moment(emp.ended_at).format( + "MM/DD/YYYY, hh:mm A" + ) : "clocked in at " + - moment(emp.started_at).format( - "MM/DD/YYYY, hh:mm A" - )} + moment(emp.started_at).format( + "MM/DD/YYYY, hh:mm A" + )} {" "} to shift{" "} @@ -1046,7 +1052,7 @@ class PrivateLayout extends Flux.DashView { "LT" )}{" "} {typeof emp.shift.price == - "string" ? ( + "string" ? ( {" "} ${emp.shift.price} @@ -1084,7 +1090,7 @@ class PrivateLayout extends Flux.DashView { "LT" )}{" "} {typeof emp.shift.price == - "string" ? ( + "string" ? ( {" "} ${emp.shift.price} @@ -1148,7 +1154,7 @@ class PrivateLayout extends Flux.DashView { /> - + + - - + this.state.bar.show(option)} /> @@ -1233,11 +1240,11 @@ class PrivateLayout extends Flux.DashView { ); } hideComponent(admin) { - if (admin==="a@jobcore.co") { + if (admin === "a@jobcore.co") { // this.setState({ showHideHR: false }); // uncommenting makes tap talent search is visible only to JC-HR@admin.co console.log("showHideHR###", this.state.showHideHR) console.log("admin###", admin) - } + } } } export default PrivateLayout; diff --git a/src/js/actions.js b/src/js/actions.js index 6fd7e40..979784f 100644 --- a/src/js/actions.js +++ b/src/js/actions.js @@ -15,6 +15,7 @@ import log from "./utils/log"; import WEngine from "./utils/write_engine.js"; import qs from "query-string"; import { normalizeToSnakeCase } from "./utils/validation"; + const Models = { shifts: Shift, ratings: Rating, @@ -55,23 +56,23 @@ export const autoLogin = (token = "") => { Notify.error(error.message || error); log.error(error); }) - ); + ); }; export const stripeStatus = (email, password, keep, history, id) => { - GET('subscription_auth/'+ email).then( - function(value) { - Notify.success("Welcome!") - - }, - function(reason) { - history.push("/subscribe") - Notify.error("Your subscription is not active, please get a new one") + GET('subscription_auth/' + email).then( + function (value) { + Notify.success("Welcome!") + + }, + function (reason) { + history.push("/subscribe") + Notify.error("Your subscription is not active, please get a new one") } - ) - -} + ) + +} export const login = (email, password, keep, history, id) => { -new Promise((resolve, reject) => + new Promise((resolve, reject) => POST("login", { username_or_email: email, password: password, @@ -79,9 +80,9 @@ new Promise((resolve, reject) => exp_days: keep ? 30 : 1, }) .then( - setTimeout(() => {stripeStatus(email, password, keep, history, id)}, 1000) - ) - .then(function (data) { + setTimeout(() => { stripeStatus(email, password, keep, history, id) }, 1000) + ) + .then(function (data) { // if (Number(data.user.profile.employer) != Number(id)) { // let company = data.user.profile.other_employers.find(emp => emp.employer == Number(id) ); // updateCompanyUser({id: company.profile_id, employer: company.employer, employer_role: company.employer_role}, { 'Authorization': 'JWT ' + data.token }); @@ -119,7 +120,7 @@ new Promise((resolve, reject) => reject(error.message || error); Notify.error(error.message || error); log.error(error); - + }) ); } @@ -144,7 +145,7 @@ export const signup = (formData, history) => }) .then(function (data) { Notify.success("You have signed up successfully! You are being redirected to the login screen"); - setTimeout(() => {history.push(`/login?type=${formData.account_type}`)}, 2500) + setTimeout(() => { history.push(`/login?type=${formData.account_type}`) }, 2500) resolve(); }) .catch(function (error) { @@ -152,7 +153,7 @@ export const signup = (formData, history) => Notify.error(error.message || error); log.error(error); }) - }); + }); export const remind = (email) => new Promise((resolve, reject) => @@ -227,13 +228,13 @@ export const sendCompanyInvitation = (email, employer, employer_role, sender) => new Promise((resolve, reject) => POST( "user/email/company/send/" + - email + - "/" + - sender + - "/" + - employer + - "/" + - employer_role, + email + + "/" + + sender + + "/" + + employer + + "/" + + employer_role, { email: email, sender: sender, @@ -447,7 +448,7 @@ export const search = (entity, queryString = null) => new Promise((accept, reject) => GET(entity, queryString) .then(function (list) { - console.log("list", list); + //console.log("list", list); if (typeof entity.callback == "function") entity.callback(); Flux.dispatchEvent(entity.slug || entity, list); accept(list); @@ -482,7 +483,7 @@ export const create = (entity, data, status = WEngine.modes.LIVE) => new Promise((resolve, reject) => { POST("employers/me/" + (entity.url || entity), data) .then(function (incoming) { - console.log("incoming", incoming); + //console.log("incoming", incoming); if ( typeof entity.url === "string" && typeof entity.slug === "undefined" @@ -511,19 +512,19 @@ export const create = (entity, data, status = WEngine.modes.LIVE) => Object.assign({ ...data, id: inc.id }) ); } - console.log("slug", entity.slug); - console.log("entity", entity); - console.log("entities", entities); - console.log("newShifts", newShifts); + //console.log("slug", entity.slug); + //console.log("entity", entity); + //console.log("entities", entities); + //console.log("newShifts", newShifts); Flux.dispatchEvent(entity.slug || entity, entities.concat(newShifts)); } Notify.success( "The " + - (entity.slug || entity).substring( - 0, - (entity.slug || entity).length - 1 - ) + - " was created successfully" + (entity.slug || entity).substring( + 0, + (entity.slug || entity).length - 1 + ) + + " was created successfully" ); resolve(incoming); }) @@ -579,8 +580,8 @@ export const remove = (entity, data) => { const name = path.split("/"); Notify.success( "The " + - name[0].substring(0, name[0].length - 1) + - " was deleted successfully" + name[0].substring(0, name[0].length - 1) + + " was deleted successfully" ); }) .catch(function (error) { @@ -633,69 +634,69 @@ export const updateProfileMe = (data) => { }; export const updateEmployability = (data) => { - PUT(`employee/employability_expired_at/update/${data.catalog.employee.id}`, - data) - .then() + PUT(`employee/employability_expired_at/update/${data.catalog.employee.id}`, + data) + .then() } export const updateDocs = (data) => { - PUT(`employee/employment_verification_status/update/${data.catalog.employee.id}`, - data) - .then(response => response.json()) - .then(data => console.log(data)) + PUT(`employee/employment_verification_status/update/${data.catalog.employee.id}`, + data) + .then(response => response.json()) + .then(data => console.log(data)) } export const createSubscription = (data, history) => { const employer = store.getState("current_employer"); - - POST(`employers/me/subscription`, data) - .then(function (active_subscription) { - - Flux.dispatchEvent("current_employer", { - ...employer, - active_subscription, - }); - Notify.success("The subscription was created successfully"); - - - }).then( - setTimeout(() => {history.push("/welcome")}, 4000) - - ) - .catch(function (error) { - console.log("ERROR", error); - Notify.error(error.message || error); - log.error(error); - }) - + + POST(`employers/me/subscription`, data) + .then(function (active_subscription) { + + Flux.dispatchEvent("current_employer", { + ...employer, + active_subscription, + }); + Notify.success("The subscription was created successfully"); + + + }).then( + setTimeout(() => { history.push("/welcome") }, 4000) + + ) + .catch(function (error) { + console.log("ERROR", error); + Notify.error(error.message || error); + log.error(error); + }) + }; export const createStripePayment2 = async () => { const response = await POSTcsrf2('create-payment-single-emp') - .then( - Notify.success("The payment was received successfully") - ) - .catch(function (error) { - console.log("ERROR", error); - Notify.error(error.message || error); - log.error(error); - }) + .then( + Notify.success("The payment was received successfully") + ) + .catch(function (error) { + console.log("ERROR", error); + Notify.error(error.message || error); + log.error(error); + }) + + return response - return response - }; export const createStripePayment = async (stripeToken) => { const response = await POSTcsrf('create-payment-intent', stripeToken) - .then( - Notify.success("The payment was received successfully") - ) - .catch(function (error) { - console.log("ERROR", error); - Notify.error(error.message || error); - log.error(error); - }) + .then( + Notify.success("The payment was received successfully") + ) + .catch(function (error) { + console.log("ERROR", error); + Notify.error(error.message || error); + log.error(error); + }) + + return response - return response - }; export const updateSubscription = (data, history) => { @@ -951,8 +952,7 @@ export const updateTalentList = (action, employee, listId) => { }) ); Notify.success( - `The talent was successfully ${ - action == "add" ? "added" : "removed" + `The talent was successfully ${action == "add" ? "added" : "removed" }` ); resolve(updatedFavlist); @@ -1037,9 +1037,9 @@ export const makeEmployeePayment = ( paymentType === "CHECK" ? {} : { - employer_bank_account_id: employer_bank_account_id, - employee_bank_account_id: employee_bank_account_id, - }, + employer_bank_account_id: employer_bank_account_id, + employee_bank_account_id: employee_bank_account_id, + }, deductions_list: deductions_list, deductions: deductions, }; @@ -1183,10 +1183,10 @@ class _Store extends Flux.DashStore { !Array.isArray(clockins) ? [] : clockins.map((c) => ({ - ...c, - started_at: moment(c.starting_at), - ended_at: moment(c.ended_at), - })) + ...c, + started_at: moment(c.starting_at), + ended_at: moment(c.ended_at), + })) ); this.addEvent("jobcore-invites"); this.addEvent("ratings", (_ratings) => @@ -1216,26 +1216,26 @@ class _Store extends Flux.DashStore { applicants.constructor === Object) ? [] : applicants.map((app) => { - app.shift = Shift(app.shift).defaults().unserialize(); - return app; - }); + app.shift = Shift(app.shift).defaults().unserialize(); + return app; + }); }); this.addEvent("shifts", (shifts) => { shifts = Array.isArray(shifts.results) ? shifts.results : Array.isArray(shifts) - ? shifts - : null; + ? shifts + : null; let newShifts = !shifts || - (Object.keys(shifts).length === 0 && shifts.constructor === Object) + (Object.keys(shifts).length === 0 && shifts.constructor === Object) ? [] : shifts - .filter((s) => s.status !== "CANCELLED") - .map((shift) => { - //already transformed - return Shift(shift).defaults().unserialize(); - }); + .filter((s) => s.status !== "CANCELLED") + .map((shift) => { + //already transformed + return Shift(shift).defaults().unserialize(); + }); const applicants = this.getState("applications"); if (!applicants && Session.get().isValid) fetchAllMe(["applications"]); @@ -1267,8 +1267,8 @@ class _Store extends Flux.DashStore { ) ? employer.payroll_period_starting_time : employer.payroll_period_starting_time - ? moment(employer.payroll_period_starting_time) - : moment(employer.created_at).startOf("isoWeek"); + ? moment(employer.payroll_period_starting_time) + : moment(employer.created_at).startOf("isoWeek"); return employer; }); this.addEvent("single_payroll_detail", (payroll) => { @@ -1277,17 +1277,17 @@ class _Store extends Flux.DashStore { let paid = true; payroll.clockins = !clockins || - (Object.keys(clockins).length === 0 && clockins.constructor === Object) + (Object.keys(clockins).length === 0 && clockins.constructor === Object) ? [] : clockins.map((clockin) => { - //already transformed - if (clockin.status == "PENDING") { - approved = false; - paid = false; - } else if (clockin.status != "PAID") paid = false; + //already transformed + if (clockin.status == "PENDING") { + approved = false; + paid = false; + } else if (clockin.status != "PAID") paid = false; - return Clockin(clockin).defaults().unserialize(); - }); + return Clockin(clockin).defaults().unserialize(); + }); if (typeof payroll.talent != "undefined") payroll.talent.paymentsApproved = approved; diff --git a/src/js/components/applicant-card/EmployeeExtendedCard.jsx b/src/js/components/applicant-card/EmployeeExtendedCard.jsx index 9b68ae9..219552e 100644 --- a/src/js/components/applicant-card/EmployeeExtendedCard.jsx +++ b/src/js/components/applicant-card/EmployeeExtendedCard.jsx @@ -447,7 +447,7 @@ const EmployeeExtendedCard = (props) => {
    { - console.log("button hovered"); + //console.log("button hovered"); props.defineEmployee() }} > @@ -461,8 +461,8 @@ const EmployeeExtendedCard = (props) => { data-target="#exampleModalCenter" onClick={(e) => { e.stopPropagation(); - console.log("button clicked"); - console.log("props dentro", props); + //console.log("button clicked"); + //console.log("props dentro", props); // Notify.info("Press ESC to close this window") // if (!props.formLoading) getEmployeeDocumet(props, "w4"); bar.show({ diff --git a/src/js/csrftoken.js b/src/js/csrftoken.js index 70e6d98..6bd0b41 100644 --- a/src/js/csrftoken.js +++ b/src/js/csrftoken.js @@ -39,7 +39,7 @@ const CSRFToken = () => { // CSRFToken() setcsrftoken(Cookies.get('csrftoken')); - console.log("este es el useeffect") + //console.log("este es el useeffect") }, []); diff --git a/src/js/utils/api_wrapper.js b/src/js/utils/api_wrapper.js index 2a44662..7e5c152 100644 --- a/src/js/utils/api_wrapper.js +++ b/src/js/utils/api_wrapper.js @@ -65,9 +65,6 @@ const appendCompany = (data) => { */ export const GET = async (endpoint, queryString = null, extraHeaders = {}) => { let url = `${rootAPIendpoint}/${endpoint}`; - console.log("GET###") - console.log("endpoint###", endpoint) - console.log("url###", url) if (queryString) url += queryString; HEADERS['Authorization'] = `JWT ${getToken()}`; @@ -89,7 +86,6 @@ export const GET = async (endpoint, queryString = null, extraHeaders = {}) => { }; export const POST = (endpoint, postData, extraHeaders = {}) => { - console.log("POST###") if (['user/register', 'login', 'user/password/reset','employers/me/jobcore-invites'].indexOf(endpoint) == -1) { HEADERS['Authorization'] = `JWT ${getToken()}`; postData = appendCompany(postData); @@ -145,7 +141,6 @@ export const POST = (endpoint, postData, extraHeaders = {}) => { // var headers = new Headers(); // headers.append('X-CSRFToken', csrftoken); export const POSTcsrf = (endpoint, postData, extraHeaders = {}) => { - console.log("POST###") // Cookies.get('csrftoken') // console.log("postData###", postData) Cookies.set('stripetoken', postData.id) @@ -160,7 +155,6 @@ export const POSTcsrf = (endpoint, postData, extraHeaders = {}) => { body: JSON.stringify(postData), // mode: 'no-cors' }; - console.log("REQ###", REQ) const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ) .then((resp) => processResp(resp, req)) .then(data => resolve(data)) @@ -175,7 +169,6 @@ export const POSTcsrf = (endpoint, postData, extraHeaders = {}) => { }; export const POSTcsrf2 = (endpoint, postData, extraHeaders = {}) => { - console.log("POST###") // Cookies.get('csrftoken') // console.log("postData###", postData) // Cookies.set('stripetoken', postData.id) @@ -190,7 +183,6 @@ export const POSTcsrf2 = (endpoint, postData, extraHeaders = {}) => { body: JSON.stringify(postData), // mode: 'no-cors' }; - console.log("REQ###", REQ) const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ) .then((resp) => processResp(resp, req)) .then(data => resolve(data)) @@ -211,7 +203,6 @@ export const POSTcsrf2 = (endpoint, postData, extraHeaders = {}) => { // credentials: 'include' // }). export const PUTFiles = (endpoint, files) => { - console.log("PUTfiles###") const headers = { 'Authorization': `JWT ${getToken()}` }; @@ -239,7 +230,6 @@ export const PUTFiles = (endpoint, files) => { }; export const PUT = (endpoint, putData, extraHeaders = {}) => { - console.log("PUT###") if (['register', 'login','user/password/reset'].indexOf(endpoint) == -1) { HEADERS['Authorization'] = `JWT ${getToken()}`; } @@ -263,7 +253,6 @@ export const PUT = (endpoint, putData, extraHeaders = {}) => { }; export const DELETE = (endpoint, extraHeaders = {}) => { - console.log("DELETE###") HEADERS['Authorization'] = `JWT ${getToken()}`; const REQ = { @@ -284,7 +273,6 @@ export const DELETE = (endpoint, extraHeaders = {}) => { }; const processResp = function (resp, req = null) { - console.log(resp) PendingReq.remove(req); if (resp.ok) { if (resp.status == 204) return new Promise((resolve, reject) => resolve(true)); diff --git a/src/js/views/auth.js b/src/js/views/auth.js index 98a52f9..f2f87aa 100644 --- a/src/js/views/auth.js +++ b/src/js/views/auth.js @@ -158,9 +158,9 @@ export class Login extends React.Component { onClick={() => { // this.toggleInverted this.setState({hidePassword: !this.state.hidePassword}) - console.log("dentro del onClick#######") + //console.log("dentro del onClick#######") - console.log("this.state.shown#######", this.state.hidePassword) + //console.log("this.state.shown#######", this.state.hidePassword) }}> { /> */} {/*
    */} - + @@ -543,9 +543,9 @@ export class Signup extends React.Component { onClick={() => { // this.toggleInverted this.setState({hidePassword: !this.state.hidePassword}) - console.log("dentro del onClick#######") + //console.log("dentro del onClick#######") - console.log("this.state.shown#######", this.state.hidePassword) + //console.log("this.state.shown#######", this.state.hidePassword) }}> diff --git a/src/js/views/favorites.js b/src/js/views/favorites.js index 9986ef2..a09b9c7 100644 --- a/src/js/views/favorites.js +++ b/src/js/views/favorites.js @@ -175,8 +175,6 @@ export const AddFavlistsToTalent = ({ onCancel, catalog, }) => { - console.log(formData); - console.log(catalog); return ( {({ bar }) => ( diff --git a/src/js/views/metrics/charts.js b/src/js/views/metrics/charts.js new file mode 100644 index 0000000..8a6e843 --- /dev/null +++ b/src/js/views/metrics/charts.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { Pie, Bar } from 'react-chartjs-2'; +import { Chart as ChartJS } from 'chart.js/auto'; + +export const PieChart = ({ pieData }) => { + + return ( + + ) +} + +export const BarChart = ({ barData }) => { + + return ( + + ) +} + +/* + rotation: (-0.5*Math.PI) - (25/180 * Math.PI), + animation: { + animateRotate: true, + render: false, + easing="linear" + duration={500} + maxPointCountSupported={100} + },*/ \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Employees/DummyDataShifts.js b/src/js/views/metrics/general-stats/Employees/DummyDataShifts.js new file mode 100644 index 0000000..3c79197 --- /dev/null +++ b/src/js/views/metrics/general-stats/Employees/DummyDataShifts.js @@ -0,0 +1,253 @@ +export const DummyDataShifts = [ + { + clockin: [ + { + id: 16, + started_at: "2022-09-19T10:20:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.7", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-19T19:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 45, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 18, + started_at: "2022-09-20T09:20:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.5", + ended_at: "2022-09-20T20:45:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 49, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 17, + started_at: "2022-09-21T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 4, + shift: 46, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 19, + started_at: "2022-09-21T10:36:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 5, + shift: 47, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T09:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.8", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 4, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-01-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-01-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 3, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-04-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 3, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-04-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 2, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-04-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 2, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-04-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 1, + shift: 50, + author: 95 + } + ] + }, + { + clockin:[] + }, + { + clockin:[] + }, + { + clockin:[] + }, +]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js b/src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js new file mode 100644 index 0000000..daf0495 --- /dev/null +++ b/src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js @@ -0,0 +1 @@ +export const DummyDataWorkers = [ { id: 4 }, { id: 5 }, { id: 3 }, { id: 2 }, { id: 1 } ] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Employees/Employees.js b/src/js/views/metrics/general-stats/Employees/Employees.js new file mode 100644 index 0000000..7ff942b --- /dev/null +++ b/src/js/views/metrics/general-stats/Employees/Employees.js @@ -0,0 +1,88 @@ +import React from "react"; +import { PieChart } from '../../charts'; +import { EmployeesData } from "./EmployeesData"; + +// Colors +const purple = "#5c00b8"; +const lightPink = "#eb00eb"; + +export const Employees = () => { + + // Data for pie chart ------------------------------------------------------------------------------------- + + let pieData = EmployeesData.filter((item) => { return item.description !== "Total Employees" }) + + const employeesData = { + labels: pieData.map((data) => data.description), + datasets: [{ + label: "Employees", + data: pieData.map((data) => data.qty), + backgroundColor: [ + purple, lightPink + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Employees Table Starts */} +
    +

    Employees Table

    + + + + + + + + + + + + {EmployeesData.map((item, i) => { + return item.description === "Total Employees" ? ( + + + + + + ) : + ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Employees Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Employees Chart Starts*/} +
    +

    Employees Chart

    + +
    + +
    +
    + {/* Employees Chart Ends*/} +
    +
    + {/* Right Column Ends */} +
    + ) +} diff --git a/src/js/views/metrics/general-stats/Employees/EmployeesData.js b/src/js/views/metrics/general-stats/Employees/EmployeesData.js new file mode 100644 index 0000000..dddc4c5 --- /dev/null +++ b/src/js/views/metrics/general-stats/Employees/EmployeesData.js @@ -0,0 +1,97 @@ +import { DummyDataShifts } from "./DummyDataShifts" +import { DummyDataWorkers } from "./DummyDataWorkers" +import moment from "moment"; + +// Today +const now = moment().format("YYYY-MM-DD") + +// Today, four weeks in the past +const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD") + +const EmployeesDataGenerator = () => { + + // First array + let clockInsList = [] + + // Gathering the clock-ins of all the shifts + DummyDataShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + clockInsList.push(clockIn); + } + }) + }) + + // Array for all clock-ins + let recentClockIns = [] + + // Filtering out clock-ins that happened longer than 4 weeks ago + clockInsList.forEach((clockIn) => { + let clockInStart = moment(clockIn.started_at).format("YYYY-MM-DD"); + + if (clockInStart > fourWeeksBack && clockInStart < now) { + recentClockIns.push(clockIn) + } + }) + + // Array for worker ids + let workerIDs = [] + + // Gethering worker ids from recent clock-ins + recentClockIns.forEach((clockIn) => { + workerIDs.push(clockIn.employee) + }) + + // Filtering out repeated worker ids + let filteredWorkerIDs = [...new Set(workerIDs)]; + + // Calculating total, active, and inactive workers + let totalWorkers = DummyDataWorkers.length + let totalActiveWorkers = filteredWorkerIDs.length + let totalInactiveWorkers = totalWorkers - totalActiveWorkers + + // Setting up objects for the semi-final array + let activeWorkers = { + id: 1, + description: "Active Employees", + qty: totalActiveWorkers + } + + let inactiveWorkers = { + id: 2, + description: "Inactive Employees", + qty: totalInactiveWorkers + } + + // Creating the semi-final array + let semiFinalList = [] + + // Adding objects to the semi-final array + semiFinalList.push(activeWorkers) + semiFinalList.push(inactiveWorkers) + + // Generating final array with percentages as new properties + let finalList = semiFinalList.map(({ id, description, qty }) => ({ + id, + description, + qty, + pct: ((qty * 100) / totalWorkers).toFixed(0) + })); + + // Generating the object of total workers + let totalEmployees = { + id: 3, + description: "Total Employees", + qty: totalWorkers, + pct: "100" + } + + // Adding the object of total workers to the final array + finalList.push(totalEmployees) + + // Returning the final array + return finalList +} + +// Exporting the final array +export const EmployeesData = EmployeesDataGenerator() \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/GeneralStats.js b/src/js/views/metrics/general-stats/GeneralStats.js new file mode 100644 index 0000000..c6e23ff --- /dev/null +++ b/src/js/views/metrics/general-stats/GeneralStats.js @@ -0,0 +1,50 @@ +import React from "react"; +import { Employees } from "./Employees/Employees"; +import { Hours } from "./Hours/Hours"; +import { Shifts } from "./Shifts/Shifts"; + +export const GeneralStats = () => { + + // Return ---------------------------------------------------------------------------------------------------- + + return ( + <> +
    + {/* Tabs Controller Starts */} + + {/* Tabs Controller Ends */} + + {/* Tabs Content Starts */} + + {/* Tabs Content Ends */} +
    + + ) +} diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js new file mode 100644 index 0000000..8cd4362 --- /dev/null +++ b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js @@ -0,0 +1,244 @@ +export const DummyDataShifts = [ + { + clockin: [ + { + id: 16, + started_at: "2022-09-19T10:20:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.7", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-19T19:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 45, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 18, + started_at: "2022-09-20T09:20:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.5", + ended_at: "2022-09-20T20:45:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 49, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 17, + started_at: "2022-09-21T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 4, + shift: 46, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 19, + started_at: "2022-09-21T10:36:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 5, + shift: 47, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T09:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.8", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 4, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-01-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 3, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 3, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-09-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 2, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-08-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 2, + shift: 50, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 15, + started_at: "2022-08-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-01-10T19:28:08.811375Z", + updated_at: "2022-01-10T19:28:08.818207Z", + status: "PENDING", + employee: 1, + shift: 50, + author: 95 + } + ] + }, +]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js b/src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js new file mode 100644 index 0000000..daf0495 --- /dev/null +++ b/src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js @@ -0,0 +1 @@ +export const DummyDataWorkers = [ { id: 4 }, { id: 5 }, { id: 3 }, { id: 2 }, { id: 1 } ] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/Hours.js b/src/js/views/metrics/general-stats/Hours/Hours.js new file mode 100644 index 0000000..1afd8d6 --- /dev/null +++ b/src/js/views/metrics/general-stats/Hours/Hours.js @@ -0,0 +1,84 @@ +import React from "react"; +import { PieChart } from "../../charts"; +import { HoursData } from "./HoursData"; + +// Colors +const purple = "#5c00b8"; +const lightTeal = "#00ebeb"; +const darkTeal = "#009e9e"; +const lightPink = "#eb00eb"; +const darkPink = "#b200b2"; + +export const Hours = () => { + + // Data for pie chart ------------------------------------------------------------------------------------- + + const hoursData = { + labels: HoursData.map((data) => data.description), + datasets: [{ + label: "Hours", + data: HoursData.map((data) => data.qty), + backgroundColor: [ + purple, darkPink, lightPink, lightTeal, darkTeal + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Hours Table Starts */} +
    +

    Hours Table

    + + + + + + + + + + + + + + + + + + + + + + + +

    Description

    Quantity

    Percentages

    {HoursData[0].description}

    {HoursData[0].qty}

    {`${HoursData[0].pct}%`}

    {HoursData[1].description}

    {HoursData[1].qty}

    {`${HoursData[1].pct}%`}

    +
    + {/* Hours Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Hours Chart Starts*/} +
    +

    Hours Chart

    + +
    + +
    +
    + {/* Hours Chart Ends*/} +
    +
    + {/* Right Column Ends */} +
    + ) +} diff --git a/src/js/views/metrics/general-stats/Hours/HoursData.js b/src/js/views/metrics/general-stats/Hours/HoursData.js new file mode 100644 index 0000000..25beca7 --- /dev/null +++ b/src/js/views/metrics/general-stats/Hours/HoursData.js @@ -0,0 +1,72 @@ +import moment from "moment" +import { DummyDataShifts } from "./DummyDataShifts" +//import { DummyDataWorkers } from "./DummyDataWorkers" + +const HoursDataGenerator = () => { + + // First array + let clockInsList = []; + + // Gathering the clock-ins of all the shifts + DummyDataShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + clockInsList.push({ + start: clockIn.started_at, + end: clockIn.ended_at + }); + } + }); + }); + + // Adding all the hours worked from each shift + let hoursWorked = clockInsList.reduce( + (total, { start, end }) => + total + moment.duration(moment(start).diff(moment(end))).asHours(), + 0 + ); + + // Formatting hours worked + let hoursWorkedFormatted = (Math.round(hoursWorked * 4) / 4).toFixed(0); + + // THIS IS A PLACEHOLDER, we double the hours worked to mimic available hours + let availableHours = (hoursWorkedFormatted * 2).toString() + + // Generate object of worked hours + let workedHoursObj = { + id: 1, + description: "Hours Worked", + qty: hoursWorkedFormatted + }; + + // Generate semi-final list + let semiFinalList = []; + + // Adding object of worked hours to semi-final list + semiFinalList.push(workedHoursObj); + + // Generating final array with percentages as new properties + let finalList = semiFinalList.map(({ id, description, qty }) => ({ + id, + description, + qty, + pct: ((qty * 100) / availableHours).toFixed(0) + })); + + // Generating object of available hours + let availableHoursObj = { + id: 2, + description: "Available Hours", + qty: availableHours, + pct: "100" + }; + + // Adding object of available hours to final list + finalList.push(availableHoursObj); + + // Returning the final array + return finalList +} + +// Exporting the final array +export const HoursData = HoursDataGenerator() \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Shifts/DummyData.js b/src/js/views/metrics/general-stats/Shifts/DummyData.js new file mode 100644 index 0000000..1dd258f --- /dev/null +++ b/src/js/views/metrics/general-stats/Shifts/DummyData.js @@ -0,0 +1,282 @@ +export const DummyData = [ + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "COMPLETED", + clockin: [{ id: 17 }], + employees: [3] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "FILLED", + clockin: [], + employees: [4] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, + { + status: "OPEN", + clockin: [], + employees: [] + }, +] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Shifts/Shifts.js b/src/js/views/metrics/general-stats/Shifts/Shifts.js new file mode 100644 index 0000000..f634d25 --- /dev/null +++ b/src/js/views/metrics/general-stats/Shifts/Shifts.js @@ -0,0 +1,90 @@ +import React from "react"; +import { BarChart } from "../../charts"; +import { ShiftsData } from "./ShiftsData"; + +// Colors +const purple = "#5c00b8"; +const lightTeal = "#00ebeb"; +const darkTeal = "#009e9e"; +const lightPink = "#eb00eb"; +const darkPink = "#b200b2"; + +export const Shifts = () => { + + // Data for bar chart ------------------------------------------------------------------------------------- + + let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted "}) + + const shiftsData = { + labels: barData.map((data) => data.description), + datasets: [{ + label: "Shifts", + data: barData.map((data) => data.qty), + backgroundColor: [ + purple, darkPink, lightPink, lightTeal, darkTeal + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Shifts Table Starts */} +
    +

    Shifts Table

    + + + + + + + + + + + + {ShiftsData.map((item, i) => { + return item.description === "Total Shifts Posted" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Shifts Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Shifts Chart Starts*/} +
    +

    Shifts Chart

    + +
    + +
    +
    + {/* Shifts Chart Ends*/} +
    +
    + {/* Right Column Ends */} +
    + ) +} diff --git a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js new file mode 100644 index 0000000..b63fceb --- /dev/null +++ b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js @@ -0,0 +1,94 @@ +import { DummyData } from "./DummyData"; + +const ShiftsDataGenerator = () => { + + // First array + let shiftsList = []; + + // Gathering all the existing shifts + DummyData.forEach((shift) => { + shiftsList.push({ + status: shift.status, + clockin: shift.clockin, + employees: shift.employees + }); + }); + + // Setting up counters + let open = 0 + let filled = 0 + let completed = 0 + let rejected = 0 + let total = shiftsList.length + + // Adding values to each counter based on certain shift conditions + shiftsList.forEach((item) => { + if (item.status === "EXPIRED" && item.clockin.length === 0 && item.employees.length === 0) { + rejected++ + } else if (item.status === "FILLED") { + filled++ + } else if (item.status === "COMPLETED") { + completed++ + } else if (item.status === "OPEN") { + open++ + } + }) + + // Creating shift objects + let openShifts = { + description: "Open Shifts", + qty: open + } + + let filledShifts = { + description: "Filled Shifts", + qty: filled + } + + let workedShifts = { + description: "Worked Shifts", + qty: completed + } + + let rejectedShifts = { + description: "Rejected Shifts", + qty: rejected + } + + // Setting up base array for all shift objects + let cleanedArray = [] + + // Pushing shift object to base array + cleanedArray.push(openShifts) + cleanedArray.push(filledShifts) + cleanedArray.push(workedShifts) + cleanedArray.push(rejectedShifts) + + // Generating final array with percentages as new properties + let percentagesArray = cleanedArray.map(({ description, qty }) => ({ + description, + qty, + pct: ((qty * 100) / total).toFixed(0) + })); + + // Generating the object of total shifts + let totalShifs = { + description: "Total Shifts Posted", + qty: total, + pct: "100" + } + + // Adding the object of total shifts to the final array + percentagesArray.push(totalShifs) + + // Adding id's to each object in the final array + percentagesArray.forEach((item, i) => { + item.id = i + 1; + }); + + // Returning the final array + return percentagesArray +}; + +// Exporting the final array +export const ShiftsData = ShiftsDataGenerator() \ No newline at end of file diff --git a/src/js/views/metrics/metrics.js b/src/js/views/metrics/metrics.js new file mode 100644 index 0000000..f2399ac --- /dev/null +++ b/src/js/views/metrics/metrics.js @@ -0,0 +1,166 @@ +import React from "react"; +import Flux from "@4geeksacademy/react-flux-dash"; +import { Session } from "bc-react-session"; + +import { Queue } from "./queue/Queue"; +import { Punctuality } from "./punctuality/Punctuality"; +import { Ratings } from "./ratings/Ratings"; +import { GeneralStats } from "./general-stats/GeneralStats"; + +import { store, search } from "../../actions"; + + +export class Metrics extends Flux.DashView { + + constructor() { + super(); + this.state = { + // Queue Data ---------------------------------------------------------------------------------------------------------------------------- + + // Workers + DocStatus: "", + empStatus: "unverified", + employees: [], + + // Shifts + allShifts: [], + session: Session.get(), + calendarLoading: true, + }; + + // This updates the state values + this.handleStatusChange = this.handleStatusChange.bind(this); + } + + // Generating the list of shifts and the list of employees --------------------------------------------------------------------------------------- + + componentDidMount() { + // Shifts + const shifts = store.getState("shifts"); + + this.subscribe(store, "shifts", (_shifts) => { + this.setState({ allShifts: _shifts, calendarLoading: false }); + }); + + // Workers + this.filter(); + + this.subscribe(store, "employees", (employees) => { + if (Array.isArray(employees) && employees.length !== 0) + this.setState({ employees }); + }); + + this.handleStatusChange + } + + // Workers + componentWillUnmount() { + this.handleStatusChange + } + + handleStatusChange() { + this.setState({ DocStatus: props.catalog.employee.employment_verification_status }); + } + + filter(url) { + let queries = window.location.search; + + if (queries) queries = "&" + queries.substring(1); + + if (url && url.length > 50) { + const page = url.split("employees")[1]; + + if (page) { + search(`employees`, `${page + queries}`).then((data) => { + this.setState({ + employees: data.results, + }); + }); + + } else null; + + } else { + search(`employees`, `?envelope=true&limit=50${queries}`).then((data) => { + this.setState({ + employees: data.results, + }); + }); + } + } + + // Render --------------------------------------------------------------------------------------------------------------------------------------- + + render() { + // List of workers with verified documents + const verifiedEmpList = this.state.employees.filter((employees) => employees.employment_verification_status === "APPROVED") + + // List of all shifts + const listOfShifts = this.state.allShifts; + + // --------------------------------------------- + // Filtering expired lists + // let listOfShifts = + // (Array.isArray(shifts) && + // shifts.length > 0 && + // shifts.filter( + // (e) => e.status !== "EXPIRED" + // )) || + // []; + // --------------------------------------------- + + // Return ----------------------------------------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Title of the Page*/} +
    +

    Metrics

    +
    + +
    + {/* Tabs Controller Starts */} + + {/* Tabs Controller Ends */} + + {/* Tabs Content Starts */} + + {/* Tabs Content Ends */} +
    +
    + ); + } +} \ No newline at end of file diff --git a/src/js/views/metrics/punctuality/DummyDataShifts.js b/src/js/views/metrics/punctuality/DummyDataShifts.js new file mode 100644 index 0000000..cf74350 --- /dev/null +++ b/src/js/views/metrics/punctuality/DummyDataShifts.js @@ -0,0 +1,381 @@ +export const DummyDataShifts = [ + { + id: 45, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 16, + started_at: "2022-09-19T10:20:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.7", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-19T19:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 45, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-19T10:00:00Z", + ending_at: "2022-09-19T20:00:00Z", + created_at: "2022-09-10T19:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + }, + { + id: 49, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 18, + started_at: "2022-09-20T09:20:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.5", + ended_at: "2022-09-20T20:45:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 49, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-20T10:00:00Z", + ending_at: "2022-09-20T20:00:00Z", + created_at: "2022-09-10T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + }, + { + id: 46, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 17, + started_at: "2022-09-21T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 4, + shift: 46, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-21T10:00:00Z", + ending_at: "2022-09-21T20:00:00Z", + created_at: "2022-09-10T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [4] + }, + { + id: 47, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 19, + started_at: "2022-09-21T10:36:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 5, + shift: 47, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-22T10:00:00Z", + ending_at: "2022-09-22T20:30:00Z", + created_at: "2022-09-15T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + }, + { + id: 48, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 15, + started_at: "2022-09-10T09:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.8", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 4, + shift: 50, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-23T10:00:00Z", + ending_at: "2022-09-23T20:30:00Z", + created_at: "2022-09-15T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [4] + }, + { + id: 50, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 15, + started_at: "2022-09-10T10:05:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 50, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z", + created_at: "2022-09-10T19:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + } + ]; + + /* + FILLED + CLOCK-IN // NO CLOCK-OUT + + { + "id": 45, + "venue": { + "title": "250 Catalonia Avenue", + "id": 30, + "latitude": "25.744481", + "longitude": "-80.259618", + "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Server", + "id": 1 + }, + "status": "FILLED", + "clockin": [ + { + "id": 16, + "started_at": "2022-09-94T19:28:09Z", + "latitude_in": "25.74446444000", + "longitude_in": "-80.25952809000", + "distance_in_miles": "0.009", + "latitude_out": "0.00000000000", + "longitude_out": "0.00000000000", + "distance_out_miles": "0.000", + "ended_at": null, + "automatically_closed": false, + "created_at": "2022-09-19T19:28:08.811375Z", + "updated_at": "2022-09-19T19:28:08.818207Z", + "status": "PENDING", + "employee": 19, + "shift": 45, + "author": 95 + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "8.0", + "starting_at": "2022-09-28T19:30:00Z", + "ending_at": "2022-09208T21:30:00Z", + "created_at": "2022-09-19T19:21:34.638331Z", + "description": "", + "employer": 80, + "author": null, + "candidates": [], + "employees": [ + 19 + ] + } + + COMPLETED + + { + "id": 4335, + "venue": { + "title": "The Club of Knights", + "id": 47, + "latitude": "25.744510", + "longitude": "-80.260054", + "street_address": "270 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Cleaning Specialist", + "id": 41 + }, + "status": "COMPLETED", + "clockin": [ + { + "id": 3382, + "started_at": "2021-08-99T11:00:00Z", + "latitude_in": "25.74440950000", + "longitude_in": "-80.25995216000", + "distance_in_miles": "0.150", + "latitude_out": "25.74440950000", + "longitude_out": "-80.25995216000", + "distance_out_miles": "0.150", + "ended_at": "2021-08-09T17:00:00Z", + "automatically_closed": false, + "created_at": "2021-08-12T14:15:29.510326Z", + "updated_at": "2021-08-12T14:15:29.510343Z", + "status": "PENDING", + "employee": 179, + "shift": 4335, + "author": null + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "12.5", + "starting_at": "2021-08-09T11:00:00Z", + "ending_at": "2021-08209T19:00:04Z", + "created_at": "2021-08-12T14:09:11.474735Z", + "description": "", + "employer": 1, + "author": null, + "candidates": [], + "employees": [] + } + */ + \ No newline at end of file diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js new file mode 100644 index 0000000..0a4d4fd --- /dev/null +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -0,0 +1,82 @@ +import React from "react"; +import { PieChart } from '../charts'; +import { PunctualityData } from "./PunctualityData"; + +// Colors +const purple = "#5c00b8"; +const lightTeal = "#00ebeb"; +const darkTeal = "#009e9e"; +const green = "#06ff05"; +const lightPink = "#eb00eb"; +const darkPink = "#b200b2"; + +export const Punctuality = () => { + + // Data for pie chart ------------------------------------------------------------------------------------- + + const punctualityData = { + labels: PunctualityData.map((data) => data.reason), + datasets: [{ + label: "Punctuality", + data: PunctualityData.map((data) => data.qty), + backgroundColor: [ + purple, darkPink, lightPink, + green, lightTeal, darkTeal + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Punctuality Table Starts */} +
    +

    Punctuality Table

    + + + + + + + + + + + {PunctualityData.map((item, i) => { + return ( + + + + + ) + })} + +

    Reason

    Quantity

    {item.reason}

    {item.qty}

    +
    + {/* Punctuality Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Punctuality Chart Starts */} +
    +

    Punctuality Chart

    + +
    + +
    +
    + {/* Punctuality Chart Ends */} +
    +
    + {/* Right Column Ends */} +
    + ) +} diff --git a/src/js/views/metrics/punctuality/PunctualityData.js b/src/js/views/metrics/punctuality/PunctualityData.js new file mode 100644 index 0000000..57dc628 --- /dev/null +++ b/src/js/views/metrics/punctuality/PunctualityData.js @@ -0,0 +1,42 @@ +export const PunctualityData = [ + { + id: 1, + reason: "Forgot to Clock-In", + qty: 101, + }, + { + id: 2, + reason: "Forgot to Clock-Out", + qty: 212, + }, + { + id: 3, + reason: "Early Clock-In", + qty: 354, + }, + { + id: 4, + reason: "Early Clock-Out", + qty: 478, + }, + { + id: 5, + reason: "Late Clock-In", + qty: 564, + }, + { + id: 6, + reason: "Late Clock-Out", + qty: 654, + }, + { + id: 7, + reason: "Long Break", + qty: 751, + }, + { + id: 8, + reason: "Extra Hours", + qty: 894, + } +] diff --git a/src/js/views/metrics/queue/DummyDataShifts.js b/src/js/views/metrics/queue/DummyDataShifts.js new file mode 100644 index 0000000..974eae9 --- /dev/null +++ b/src/js/views/metrics/queue/DummyDataShifts.js @@ -0,0 +1,343 @@ +export const DummyDataShifts = [ + { + id: 45, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 16, + started_at: "2022-09-19T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-19T15:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 45, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-19T10:00:00Z", + ending_at: "2022-09-19T20:00:00Z", + created_at: "2022-09-10T19:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + }, + { + id: 49, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 18, + started_at: "2022-09-20T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-20T15:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 49, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-20T10:00:00Z", + ending_at: "2022-09-20T20:00:00Z", + created_at: "2022-09-10T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + }, + { + id: 46, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 17, + started_at: "2022-09-21T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T15:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 4, + shift: 46, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-21T10:00:00Z", + ending_at: "2022-09-21T20:00:00Z", + created_at: "2022-09-10T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [4] + }, + { + id: 47, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "FILLED", + clockin: [], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-22T10:00:00Z", + ending_at: "2022-09-22T20:30:00Z", + created_at: "2022-09-15T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + }, + { + id: 48, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "FILLED", + clockin: [], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-23T10:00:00Z", + ending_at: "2022-09-23T20:10:00Z", + created_at: "2022-09-15T19:21:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [4] + }, + { + id: 50, + venue: { + title: "250 Catalonia Avenue", + id: 30, + latitude: "25.744481", + longitude: "-80.259618", + street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", + zip_code: 33134 + }, + position: { + title: "Server", + id: 1 + }, + status: "COMPLETED", + clockin: [ + { + id: 16, + started_at: "2022-09-10T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.009", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-10T15:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + status: "PENDING", + employee: 5, + shift: 50, + author: 95 + } + ], + maximum_allowed_employees: 1, + minimum_hourly_rate: "8.0", + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z", + created_at: "2022-09-10T19:34.638331Z", + description: "", + employer: 80, + author: null, + candidates: [], + employees: [5] + } + ]; + + /* + FILLED + CLOCK-IN // NO CLOCK-OUT + + { + "id": 45, + "venue": { + "title": "250 Catalonia Avenue", + "id": 30, + "latitude": "25.744481", + "longitude": "-80.259618", + "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Server", + "id": 1 + }, + "status": "FILLED", + "clockin": [ + { + "id": 16, + "started_at": "2022-09-94T19:28:09Z", + "latitude_in": "25.74446444000", + "longitude_in": "-80.25952809000", + "distance_in_miles": "0.009", + "latitude_out": "0.00000000000", + "longitude_out": "0.00000000000", + "distance_out_miles": "0.000", + "ended_at": null, + "automatically_closed": false, + "created_at": "2022-09-19T19:28:08.811375Z", + "updated_at": "2022-09-19T19:28:08.818207Z", + "status": "PENDING", + "employee": 19, + "shift": 45, + "author": 95 + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "8.0", + "starting_at": "2022-09-28T19:30:00Z", + "ending_at": "2022-09208T21:30:00Z", + "created_at": "2022-09-19T19:21:34.638331Z", + "description": "", + "employer": 80, + "author": null, + "candidates": [], + "employees": [ + 19 + ] + } + + COMPLETED + + { + "id": 4335, + "venue": { + "title": "The Club of Knights", + "id": 47, + "latitude": "25.744510", + "longitude": "-80.260054", + "street_address": "270 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Cleaning Specialist", + "id": 41 + }, + "status": "COMPLETED", + "clockin": [ + { + "id": 3382, + "started_at": "2021-08-99T11:00:00Z", + "latitude_in": "25.74440950000", + "longitude_in": "-80.25995216000", + "distance_in_miles": "0.150", + "latitude_out": "25.74440950000", + "longitude_out": "-80.25995216000", + "distance_out_miles": "0.150", + "ended_at": "2021-08-09T17:00:00Z", + "automatically_closed": false, + "created_at": "2021-08-12T14:15:29.510326Z", + "updated_at": "2021-08-12T14:15:29.510343Z", + "status": "PENDING", + "employee": 179, + "shift": 4335, + "author": null + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "12.5", + "starting_at": "2021-08-09T11:00:00Z", + "ending_at": "2021-08209T19:00:04Z", + "created_at": "2021-08-12T14:09:11.474735Z", + "description": "", + "employer": 1, + "author": null, + "candidates": [], + "employees": [] + } + */ + \ No newline at end of file diff --git a/src/js/views/metrics/queue/DummyDataWorkers.js b/src/js/views/metrics/queue/DummyDataWorkers.js new file mode 100644 index 0000000..398a2a9 --- /dev/null +++ b/src/js/views/metrics/queue/DummyDataWorkers.js @@ -0,0 +1,687 @@ +export const DummyDataWorkers = [ + { + id: 5, + user: { + first_name: "Bill", + last_name: "Clinton", + email: "a+bill@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: 3, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2018-09-13T19:45:00Z", + updated_at: "2022-05-31T19:17:05.756262Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + }, + { + id: 4, + user: { + first_name: "Frank", + last_name: "Sinatra", + email: "a+employee4@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [ + { + id: 2, + title: "Preferred Employees", + created_at: "2018-09-13T19:45:00Z", + updated_at: "2018-09-13T19:45:00Z", + auto_accept_employees_on_this_list: false, + employer: 1, + employees: [4, 3] + } + ], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: 1, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2018-09-13T19:45:00Z", + updated_at: "2022-05-31T19:16:58.179401Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + } + ]; + + /* + + , + + { + id: 7, + user: { + first_name: "Martin", + last_name: "Luther King", + email: "a+martin@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2018-09-13T19:45:00Z", + updated_at: "2022-05-31T19:16:51.944212Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + }, + { + id: 6, + user: { + first_name: "Hilary", + last_name: "Clinton", + email: "a+hilary@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2018-09-13T19:45:00Z", + updated_at: "2022-05-31T19:16:45.061163Z", + response_time: 0, + total_invites: 0, + employability_expired_at: null, + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + }, + { + id: 2, + user: { + first_name: "John", + last_name: "Lennon", + email: "a+employee2@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [ + { + id: 1, + title: "Preferred Employees", + created_at: "2018-09-13T19:45:00Z", + updated_at: "2018-09-13T19:45:00Z", + auto_accept_employees_on_this_list: true, + employer: 1, + employees: [3, 2] + } + ], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2018-09-13T19:45:00Z", + updated_at: "2022-05-31T19:16:38.278596Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 3], + badges: [1, 2] + }, + { + id: 8, + user: { + first_name: "Bob", + last_name: "Patiño", + email: "a+employer2@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2019-12-02T19:31:08.229524Z", + updated_at: "2022-05-31T19:16:31.891312Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 3, + user: { + first_name: "Paul", + last_name: "McCartney", + email: "a+employee3@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [ + { + id: 1, + title: "Preferred Employees", + created_at: "2018-09-13T19:45:00Z", + updated_at: "2018-09-13T19:45:00Z", + auto_accept_employees_on_this_list: true, + employer: 1, + employees: [3, 2] + }, + { + id: 2, + title: "Preferred Employees", + created_at: "2018-09-13T19:45:00Z", + updated_at: "2018-09-13T19:45:00Z", + auto_accept_employees_on_this_list: false, + employer: 1, + employees: [4, 3] + } + ], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2018-09-13T19:45:00Z", + updated_at: "2022-05-31T19:16:25.336247Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [3, 4] + }, + { + id: 1, + user: { + first_name: "Alejo", + last_name: "Sanchez", + email: "a+employer@jobcore.co", + profile: { + picture: "", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2019-12-02T14:03:05.532124Z", + updated_at: "2022-05-31T19:16:18.809900Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-03T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + }, + { + id: 9, + user: { + first_name: "Sea", + last_name: "Aegean", + email: "agaege@gmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2019-12-15T17:06:58.768466Z", + updated_at: "2022-05-31T19:16:11.267685Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 10, + user: { + first_name: "Aeg", + last_name: "Aegean", + email: "aegaeg@gmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2019-12-15T17:09:40.996187Z", + updated_at: "2022-05-31T19:16:04.729902Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 11, + user: { + first_name: "Angel", + last_name: "Lacret", + email: "alacret@gmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2019-12-23T02:15:55.733701Z", + updated_at: "2022-05-31T19:15:59.168859Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-03T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 12, + user: { + first_name: "Esteban", + last_name: "Contreras", + email: "estebancont11@gmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2019-12-23T14:27:39.797020Z", + updated_at: "2022-05-31T19:15:52.039462Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 13, + user: { + first_name: "Cesar", + last_name: "Morales", + email: "david_avid1@hotmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", + bio: "Software developer react", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: true, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 48, + job_count: 0, + created_at: "2019-12-26T15:30:22.006415Z", + updated_at: "2022-05-31T19:15:44.543932Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-04T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 14, + user: { + first_name: "Moises", + last_name: "Marquina", + email: "marquinaabreu@gmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2020-01-10T20:48:46.219434Z", + updated_at: "2022-05-31T19:15:37.198090Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-05T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + }, + { + id: 16, + user: { + first_name: "Ronald", + last_name: "Macdonald", + email: "ronaldmac@jobcore.co", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", + bio: "", + phone_number: "7863299422" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2022-04-14T14:57:59.993342Z", + updated_at: "2022-05-31T19:15:22.175867Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-03T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + }, + { + id: 17, + user: { + first_name: "Bart", + last_name: "Simpson", + email: "bartsimpson@jobcore.co", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile2.png", + bio: "", + phone_number: "7863299422" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2022-04-14T16:06:13.564446Z", + updated_at: "2022-05-31T19:08:50.298810Z", + response_time: 0, + total_invites: 0, + employability_expired_at: "2100-06-02T00:00:00Z", + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [], + badges: [] + }, + { + id: 15, + user: { + first_name: "Luis", + last_name: "Llave", + email: "luisllave12357@gmail.com", + profile: { + picture: + "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png", + bio: "", + phone_number: "" + } + }, + favoritelist_set: [], + minimum_hourly_rate: "8.0", + stop_receiving_invites: false, + rating: null, + total_ratings: 0, + total_pending_payments: 0, + maximum_job_distance_miles: 50, + job_count: 0, + created_at: "2020-03-10T20:42:35.885388Z", + updated_at: "2022-04-11T16:21:59.973971Z", + response_time: 0, + total_invites: 0, + employability_expired_at: null, + employment_verification_status: "APPROVED", + filing_status: "SINGLE", + allowances: 0, + w4_year: 0, + step2c_checked: false, + dependants_deduction: "0.00", + other_income: "0.00", + additional_deductions: "0.00", + extra_withholding: "0.00", + positions: [1, 2, 3, 4, 5, 6], + badges: [] + } + + */ + \ No newline at end of file diff --git a/src/js/views/metrics/queue/Queue.js b/src/js/views/metrics/queue/Queue.js new file mode 100644 index 0000000..96671f7 --- /dev/null +++ b/src/js/views/metrics/queue/Queue.js @@ -0,0 +1,129 @@ +import React, { useState, useEffect } from "react"; +import "react-datepicker/dist/react-datepicker.css"; +import DatePicker from "react-datepicker"; +import moment from "moment"; + +import { WorkerStats } from "./WorkerStats"; +import { Button } from "../../../components/index"; +import { DummyDataShifts } from "./DummyDataShifts"; +import { DummyDataWorkers } from "./DummyDataWorkers"; + + +export const Queue = (props) => { + + // Setting up my variables --------------------------------------------------------------------------------- + + const [selectedDate, setSelectedDate] = useState(new Date()); + const [start, setStart] = useState( + moment().startOf("isoWeek").format("YYYY-MM-DD") + ); + const [end, setEnd] = useState( + moment().endOf("isoWeek").format("YYYY-MM-DD") + ); + + //const allShifts = props.shifts; + const allShifts = DummyDataShifts; + + //const workers = props.workers; + const workers = DummyDataWorkers + + // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- + + const filterShifts = () => { + let filteredShifts = []; + + allShifts.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); + let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); + + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + }); + + return filteredShifts; + }; + + // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- + + useEffect(() => { + let formattedStart = moment(selectedDate) + .startOf("isoWeek") + .format("YYYY-MM-DD"); + + setStart(formattedStart); + + let formattedEnd = moment(selectedDate) + .endOf("isoWeek") + .format("YYYY-MM-DD"); + + setEnd(formattedEnd); + }, [selectedDate]); + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    +

    Table of Employee Hours

    + {/* Top Column Starts */} +
    + {/* Controls for the Week Starts */} + {/* Col 1 */} +
    +

    {`Week of ${start} - ${end}`}

    +
    + + {/* Col 2 */} +
    +
    +

    Select a day of the desired week:

    +
    + +
    + setSelectedDate(date)} + /> +
    +
    + + {/* Col 3 */} +
    +
    + +
    + +
    + +
    +
    + {/* Controls for the Week Ends */} +
    + {/* Top Column Ends */} + + {/* Bottom Column Starts */} + {/* Table of Employees Starts */} +
    + {workers?.map((singleWorker, i) => { + return ( + ) + })} +
    + {/* Table of Employees Ends */} + {/* Bottom Column Ends */} +
    + ); +} \ No newline at end of file diff --git a/src/js/views/metrics/queue/WorkerStats.js b/src/js/views/metrics/queue/WorkerStats.js new file mode 100644 index 0000000..c70de17 --- /dev/null +++ b/src/js/views/metrics/queue/WorkerStats.js @@ -0,0 +1,115 @@ +import React from "react"; +import moment from "moment"; +import Avatar from "../../../components/avatar/Avatar"; +import { Button, Theme } from "../../../components/index"; + +// This is needed to render the button "Invite to Shift" +const allowLevels = window.location.search != ""; + +export const WorkerStats = (props) => { + + // Setting up my variables --------------------------------------------------------------------------------- + + const worker = props.worker; + const shifts = props.shifts; + + // Scheduled Hours ----------------------------------------------------------------------------------------- + + // Filtering scheduled Shifts + const workerShiftsScheduled = []; + + shifts.forEach((shift) => { + shift.employees.forEach((employee) => { + if (employee === worker.id) { + workerShiftsScheduled.push(shift); + } + }); + }); + + // Calculating total scheduled hours + let scheduledHours = workerShiftsScheduled.reduce( + (total, { starting_at, ending_at }) => + total + + moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(), + 0 + ); + + let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(2); + + // Worked Hours ------------------------------------------------------------------------------------------------ + + // Filtering worked Shifts + const workerShiftsClockedIn = []; + + workerShiftsScheduled.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + workerShiftsClockedIn.push(clockIn); + } + }); + }); + + // Calculating total worked hours + let workedHours = workerShiftsClockedIn.reduce( + (total, { started_at, ended_at }) => + total + + moment.duration(moment(ended_at).diff(moment(started_at))).asHours(), + 0 + ); + + let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(2); + + // Return ------------------------------------------------------------------------------------------------------ + + return ( + <> + + {({ bar }) => ( +
    + {/* Employee Image/Name/Rating Starts */} +
    +
    + +
    +
    +
    {`${worker.user.first_name} ${worker.user.last_name}`}
    +
    {worker.rating == null ? "No rating available" : worker.rating > 1 ? `Rating: ${worker.rating} stars` : `Rating: ${worker.rating} star`}
    +
    +
    + {/* Employee Image/Name/Rating Ends */} + + {/* Scheduled Hours Starts */} +
    +

    {`Scheduled Hours: ${scheduledHoursFormatted}`}

    +
    + {/* Scheduled Hours Ends */} + + {/* Worked Hours Starts */} +
    + +

    {`Worked Hours: ${workedHoursFormatted}`}

    +
    + {/* Worked Hours Ends */} + + {/* Invite Button Starts */} +
    + +
    + {/* Invite Button Ends */} +
    + )} +
    + + ); +}; \ No newline at end of file diff --git a/src/js/views/metrics/ratings/DummyDataWorkers.js b/src/js/views/metrics/ratings/DummyDataWorkers.js new file mode 100644 index 0000000..aecd2a6 --- /dev/null +++ b/src/js/views/metrics/ratings/DummyDataWorkers.js @@ -0,0 +1,28 @@ +export const DummyDataWorkers = [ + { rating: 3 }, + { rating: 1 }, + { rating: 1 }, + { rating: 1 }, + { rating: 3 }, + { rating: null }, + { rating: 4 }, + { rating: 5 }, + { rating: null }, + { rating: 5 }, + { rating: 1 }, + { rating: 2 }, + { rating: 2 }, + { rating: 2 }, + { rating: 1 }, + { rating: 3 }, + { rating: 3 }, + { rating: 3 }, + { rating: 3 }, + { rating: 4 }, + { rating: 4 }, + { rating: 4 }, + { rating: 4 }, + { rating: 4 }, + { rating: 4 }, + { rating: 4 } + ]; \ No newline at end of file diff --git a/src/js/views/metrics/ratings/Ratings.js b/src/js/views/metrics/ratings/Ratings.js new file mode 100644 index 0000000..929a0b1 --- /dev/null +++ b/src/js/views/metrics/ratings/Ratings.js @@ -0,0 +1,98 @@ +import React from "react"; +import { PieChart } from '../charts'; +import { RatingsData } from "./RatingsData"; + +// Colors +const purple = "#5c00b8"; +const lightTeal = "#00ebeb"; +const darkTeal = "#009e9e"; +const green = "#06ff05"; +const lightPink = "#eb00eb"; +const darkPink = "#b200b2"; + +export const Ratings = () => { + + // Data for pie chart ------------------------------------------------------------------------------------- + + let pieData = RatingsData.filter((item) => { return item.rating !== "Total Employees" }) + + const ratingsData = { + labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }), + datasets: [{ + label: "Employee Ratings", + data: pieData.map((data) => data.qty), + backgroundColor: [ + green, darkTeal, lightPink, + purple, lightTeal, darkPink + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Ratings Table Starts */} +
    +

    Employee Ratings Table

    + + + + + + + + + + + + {RatingsData.map((item, i) => { + return item.rating === null ? ( + + + + + + ) : item.rating === "Total Employees" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Star Rating

    Quantity

    Percentages

    Unavailable Rating

    {item.qty}

    {`${item.pct}%`}

    {item.rating}

    {item.qty}

    {`${item.pct}%`}

    {`${item.rating} Star Employees`}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Ratings Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Ratings Chart Starts */} +
    +

    Employee Ratings Chart

    + +
    + +
    +
    + {/* Ratings Ends */} +
    +
    + {/* Right Column Ends */} +
    + ) +} diff --git a/src/js/views/metrics/ratings/RatingsData.js b/src/js/views/metrics/ratings/RatingsData.js new file mode 100644 index 0000000..6cf1d6b --- /dev/null +++ b/src/js/views/metrics/ratings/RatingsData.js @@ -0,0 +1,69 @@ +import { DummyDataWorkers } from "./DummyDataWorkers"; + +const RatingDataGenerator = () => { + + // First array + let ratings = []; + + // Gathering all the ratings from each worker + DummyDataWorkers.forEach((worker) => { + ratings.push(worker.rating); + }); + + // Function to make an array of objects with all the ratings + const findOccurrences = (arr = []) => { + const results = []; + + arr.forEach((item) => { + const index = results.findIndex((obj) => { + return obj["rating"] === item; + }); + if (index === -1) { + results.push({ + rating: item, + qty: 1 + }); + } else { + results[index]["qty"]++; + } + }); + + return results; + }; + + // Generating array + let array = findOccurrences(ratings); + + // Calculating total of the quantities + let total = array.reduce((s, { qty }) => s + qty, 0); + + // Generating and adding percentages as new properties + let ratingsArray = array.map(({ rating, qty }) => ({ + rating, + qty, + pct: ((qty * 100) / total).toFixed(0) + })); + + // Organizing objects by numerical order of the "rating" property + let sortedArray = ratingsArray.sort((a, b) => (a.rating - b.rating)) + + // Moving the first object ("Unavailable Rating") to the last position of the array + sortedArray.push(sortedArray.shift()); + + // Generating an object with the totals + let totalsObj = { rating: "Total Employees", qty: total, pct: "100"} + + // Adding object with the totals to array + sortedArray.push(totalsObj) + + // Adding id's to each object in the array + sortedArray.forEach((item, i) => { + item.id = i + 1; + }); + + // Returning the array + return sortedArray +}; + +// Exporting the array +export const RatingsData = RatingDataGenerator() \ No newline at end of file diff --git a/src/js/views/profile.js b/src/js/views/profile.js index a2fec79..8e89856 100644 --- a/src/js/views/profile.js +++ b/src/js/views/profile.js @@ -258,7 +258,6 @@ export class Profile extends Flux.DashView { this.cropper = cropper; } callback = (data) => { - console.log("DATA", data); // if(data.action == 'next' && data.index == 0){ // this.props.history.push("/payroll"); diff --git a/src/js/views/shift.test.js b/src/js/views/shift.test.js deleted file mode 100644 index bab9060..0000000 --- a/src/js/views/shift.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import { ReactDOM } from 'react-dom'; - -// const Shift = require('./shifts.js'); - -// test('adds 1 + 2 to equal 3', () => { -// expect(sum(1, 2)).toBe(3); -// }); -const Shift = require('./shifts.js'); -// import {Shift} from "./shifts.js"; -jest.mock('./shifts.js'); -const mockFn = jest.fn(); -const mockMethod = jest.fn(); -const a = new mockFn(); - -Shift.withStatus(() => { - return { - method: mockMethod, - }; - }); -const some = new Shift(); -some.method('a', 'b'); -console.log('Calls to method: ', mockMethod.mock.calls); -test('Creating a shift with: no recurrent, single date, anyone', () => { - expect(Shift({ - allowedFavlists: [], - allowedTalents: [], - allowed_from_list: [], - application_restriction: "ANYONE", - candidates: [], - description: "N/A", - employees: [], - employer: 56, - ending_at: "2022-06-29T20:15:47.883Z", - has_sensitive_updates: true, - maximum_allowed_employees: "1", - minimum_allowed_rating: "0", - minimum_hourly_rate: "8", - pending_invites: [], - pending_jobcore_invites: [], - position: "3", - starting_at: "2022-06-29T18:15:47.883Z", - status: "OPEN", - venue: "12" - }).defaults()).toBe([{ - "allowedFavlists": [], - "allowedTalents": [], - "allowed_from_list": [], - "application_restriction": "ANYONE", - "candidates": [], - "description": "N/A", - "employees": [], - "employer": 56, - "ending_at": "2022-06-29T20:15:47.883Z", - "has_sensitive_updates": true, - "maximum_allowed_employees": "1", - "minimum_allowed_rating": "0", - "minimum_hourly_rate": "8", - "pending_invites": [], - "pending_jobcore_invites": [], - "position": "3", - "serialize": a, - "starting_at": "2022-06-29T18:15:47.883Z", - "status": "OPEN", - "unserialize": a, - "venue": "12", - "withStatus": a - }]) -}) \ No newline at end of file diff --git a/src/js/views/talents.js b/src/js/views/talents.js index 410bea2..39be432 100644 --- a/src/js/views/talents.js +++ b/src/js/views/talents.js @@ -256,7 +256,7 @@ export class ManageTalents extends Flux.DashView { } } const today = new Date() - console.log("empleados#########", employees.map(checkEmployability)) // leave this one for now [Israel] + //console.log("empleados#########", employees.map(checkEmployability)) // leave this one for now [Israel] const positions = this.state.positions; if (this.state.firstSearch) return

    Please search for an employee

    ; const allowLevels = window.location.search != ""; @@ -508,6 +508,9 @@ FilterTalents.propTypes = { /** * Talent Details + * + * Before, the Stars component was rendered inside a p tag, + * now its rendered inside a span tag */ export const TalentDetails = (props) => { const employee = props.catalog.employee; @@ -544,12 +547,12 @@ export const TalentDetails = (props) => { /> -

    + -

    +

    {typeof employee.fullName == "function" ? employee.fullName() diff --git a/src/styles/_icons.scss b/src/styles/_icons.scss index c125435..fbdb21d 100644 --- a/src/styles/_icons.scss +++ b/src/styles/_icons.scss @@ -14,6 +14,7 @@ &.icon-shifts{ background-image: url('../img/icons/shifts_color.png'); } &.icon-talents{ background-image: url('../img/icons/talents_color.png'); } &.icon-logout{ background-image: url('../img/icons/logout_color.png'); } + &.icon-metrics{ background-image: url('../img/icons/metrics_color.png'); } } .svg_img{ From d2b1ebe2914f417591d79b7a953f047ce59bffd1 Mon Sep 17 00:00:00 2001 From: paola9896 Date: Fri, 23 Sep 2022 14:00:55 +0000 Subject: [PATCH 02/10] fixed data for hours section --- .../general-stats/Hours/DummyDataShifts.js | 166 +++++++++++------- .../metrics/general-stats/Hours/HoursData.js | 2 +- 2 files changed, 106 insertions(+), 62 deletions(-) diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js index 8cd4362..bc80c2b 100644 --- a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js +++ b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js @@ -24,21 +24,21 @@ export const DummyDataShifts = [ { clockin: [ { - id: 18, - started_at: "2022-09-20T09:20:09Z", + id: 16, + started_at: "2022-09-19T10:20:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", - distance_in_miles: "0.009", + distance_in_miles: "0.7", latitude_out: "0.00000000000", longitude_out: "0.00000000000", - distance_out_miles: "0.5", - ended_at: "2022-09-20T20:45:09Z", + distance_out_miles: "0.000", + ended_at: "2022-09-19T19:10:09Z", automatically_closed: false, created_at: "2022-09-10T19:28:08.811375Z", updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", employee: 5, - shift: 49, + shift: 45, author: 95 } ] @@ -46,21 +46,21 @@ export const DummyDataShifts = [ { clockin: [ { - id: 17, - started_at: "2022-09-21T10:00:09Z", + id: 16, + started_at: "2022-09-19T10:20:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", - distance_in_miles: "0.4", + distance_in_miles: "0.7", latitude_out: "0.00000000000", longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", + ended_at: "2022-09-19T19:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", - employee: 4, - shift: 46, + employee: 5, + shift: 45, author: 95 } ] @@ -68,21 +68,21 @@ export const DummyDataShifts = [ { clockin: [ { - id: 19, - started_at: "2022-09-21T10:36:09Z", + id: 16, + started_at: "2022-09-19T10:20:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", - distance_in_miles: "0.4", + distance_in_miles: "0.7", latitude_out: "0.00000000000", longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", + ended_at: "2022-09-19T19:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", employee: 5, - shift: 47, + shift: 45, author: 95 } ] @@ -90,21 +90,21 @@ export const DummyDataShifts = [ { clockin: [ { - id: 15, - started_at: "2022-09-10T09:00:09Z", + id: 16, + started_at: "2022-09-19T10:20:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", - distance_in_miles: "0.009", + distance_in_miles: "0.7", latitude_out: "0.00000000000", longitude_out: "0.00000000000", - distance_out_miles: "0.8", - ended_at: "2022-09-10T20:05:09Z", + distance_out_miles: "0.000", + ended_at: "2022-09-19T19:10:09Z", automatically_closed: false, created_at: "2022-09-10T19:28:08.811375Z", updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", - employee: 4, - shift: 50, + employee: 5, + shift: 45, author: 95 } ] @@ -112,43 +112,87 @@ export const DummyDataShifts = [ { clockin: [ { - id: 15, - started_at: "2022-09-10T10:05:09Z", + id: 16, + started_at: "2022-09-19T10:20:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", - distance_in_miles: "0.009", + distance_in_miles: "0.7", latitude_out: "0.00000000000", longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:05:09Z", + ended_at: "2022-09-19T19:10:09Z", automatically_closed: false, created_at: "2022-09-10T19:28:08.811375Z", updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", employee: 5, - shift: 50, + shift: 45, author: 95 } ] - }, + }, { clockin: [ { - id: 15, - started_at: "2022-09-10T10:05:09Z", + id: 18, + started_at: "2022-09-20T09:20:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", distance_in_miles: "0.009", latitude_out: "0.00000000000", longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-01-10T20:05:09Z", + distance_out_miles: "0.5", + ended_at: "2022-09-20T20:45:09Z", automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", - employee: 3, - shift: 50, + employee: 5, + shift: 49, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 17, + started_at: "2022-09-21T10:00:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 4, + shift: 46, + author: 95 + } + ] + }, + { + clockin: [ + { + id: 19, + started_at: "2022-09-21T10:36:09Z", + latitude_in: "25.74446444000", + longitude_in: "-80.25952809000", + distance_in_miles: "0.4", + latitude_out: "0.00000000000", + longitude_out: "0.00000000000", + distance_out_miles: "0.000", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + status: "PENDING", + employee: 5, + shift: 47, author: 95 } ] @@ -157,19 +201,19 @@ export const DummyDataShifts = [ clockin: [ { id: 15, - started_at: "2022-09-10T10:05:09Z", + started_at: "2022-09-10T09:00:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", distance_in_miles: "0.009", latitude_out: "0.00000000000", longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", + distance_out_miles: "0.8", + ended_at: "2022-09-10T20:05:09Z", automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", - employee: 3, + employee: 4, shift: 50, author: 95 } @@ -186,12 +230,12 @@ export const DummyDataShifts = [ latitude_out: "0.00000000000", longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", + ended_at: "2022-09-10T20:05:09Z", automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", status: "PENDING", - employee: 2, + employee: 5, shift: 50, author: 95 } @@ -201,19 +245,19 @@ export const DummyDataShifts = [ clockin: [ { id: 15, - started_at: "2022-08-10T10:05:09Z", + started_at: "2022-09-10T10:05:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", distance_in_miles: "0.009", latitude_out: "0.00000000000", longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", + ended_at: "2022-09-10T20:05:09Z", automatically_closed: false, created_at: "2022-01-10T19:28:08.811375Z", updated_at: "2022-01-10T19:28:08.818207Z", status: "PENDING", - employee: 2, + employee: 3, shift: 50, author: 95 } @@ -223,22 +267,22 @@ export const DummyDataShifts = [ clockin: [ { id: 15, - started_at: "2022-08-10T10:05:09Z", + started_at: "2022-09-10T10:05:09Z", latitude_in: "25.74446444000", longitude_in: "-80.25952809000", distance_in_miles: "0.009", latitude_out: "0.00000000000", longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", + ended_at: "2022-09-10T20:05:09Z", automatically_closed: false, created_at: "2022-01-10T19:28:08.811375Z", updated_at: "2022-01-10T19:28:08.818207Z", status: "PENDING", - employee: 1, + employee: 2, shift: 50, author: 95 } ] - }, + } ]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/HoursData.js b/src/js/views/metrics/general-stats/Hours/HoursData.js index 25beca7..678bb0f 100644 --- a/src/js/views/metrics/general-stats/Hours/HoursData.js +++ b/src/js/views/metrics/general-stats/Hours/HoursData.js @@ -22,7 +22,7 @@ const HoursDataGenerator = () => { // Adding all the hours worked from each shift let hoursWorked = clockInsList.reduce( (total, { start, end }) => - total + moment.duration(moment(start).diff(moment(end))).asHours(), + total + moment.duration(moment(end).diff(moment(start))).asHours(), 0 ); From ab023f807b5c534eb8ac73df8edd42cecd683cd4 Mon Sep 17 00:00:00 2001 From: paola9896 Date: Fri, 23 Sep 2022 20:14:37 +0000 Subject: [PATCH 03/10] Advances in 'punctuality' and 'hours' --- src/js/views/metrics/charts.js | 29 +- .../general-stats/Hours/DummyDataShifts.js | 285 +++------- .../metrics/general-stats/Hours/Hours.js | 28 +- .../metrics/general-stats/Hours/HoursData.js | 154 +++-- .../general-stats/Shifts/ShiftsData.js | 5 +- .../metrics/punctuality/DummyDataShifts.js | 537 +++++------------- .../views/metrics/punctuality/Punctuality.js | 118 +++- .../metrics/punctuality/PunctualityData.js | 219 +++++-- 8 files changed, 633 insertions(+), 742 deletions(-) diff --git a/src/js/views/metrics/charts.js b/src/js/views/metrics/charts.js index 8a6e843..5ea7cb5 100644 --- a/src/js/views/metrics/charts.js +++ b/src/js/views/metrics/charts.js @@ -3,14 +3,15 @@ import { Pie, Bar } from 'react-chartjs-2'; import { Chart as ChartJS } from 'chart.js/auto'; export const PieChart = ({ pieData }) => { - return ( { } export const BarChart = ({ barData }) => { - + + let delayed; return ( { responsive: true, maintainAspectRatio: false, animation: { - animateRotate: true, + onComplete: () => { + delayed = true; + }, + delay: (context) => { + let delay = 0; + if (context.type === 'data' && context.mode === 'default' && !delayed) { + delay = context.dataIndex * 150 + context.datasetIndex * 100; + } + return delay; + }, } }} /> ) -} - -/* - rotation: (-0.5*Math.PI) - (25/180 * Math.PI), - animation: { - animateRotate: true, - render: false, - easing="linear" - duration={500} - maxPointCountSupported={100} - },*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js index bc80c2b..69e3010 100644 --- a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js +++ b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js @@ -2,287 +2,160 @@ export const DummyDataShifts = [ { clockin: [ { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T10:45:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 + ended_at: "2022-09-10T20:00:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 + ended_at: "2022-09-10T19:25:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T09:15:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 + ended_at: "2022-09-10T20:17:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 + ended_at: "2022-09-10T20:31:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T10:55:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 + ended_at: "2022-09-10T20:19:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T10:40:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 + ended_at: "2022-09-10T19:25:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 18, - started_at: "2022-09-20T09:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", + started_at: "2022-09-10T11:00:09Z", distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.5", - ended_at: "2022-09-20T20:45:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 49, - author: 95 + distance_out_miles: "0.000", + ended_at: "2022-09-10T19:20:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 17, - started_at: "2022-09-21T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.4", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T10:14:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 4, - shift: 46, - author: 95 + ended_at: "2022-09-10T21:35:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 19, - started_at: "2022-09-21T10:36:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.4", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", + started_at: "2022-09-10T10:08:09Z", + distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 5, - shift: 47, - author: 95 + ended_at: "2022-09-10T20:15:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 15, - started_at: "2022-09-10T09:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", + started_at: "2022-09-10T10:40:09Z", distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.8", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 4, - shift: 50, - author: 95 + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:40:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 15, - started_at: "2022-09-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", + started_at: "2022-09-10T08:00:09Z", distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 50, - author: 95 + ended_at: "2022-09-10T20:45:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 15, - started_at: "2022-09-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", + started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 3, - shift: 50, - author: 95 + ended_at: "2022-09-10T20:00:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { clockin: [ { - id: 15, - started_at: "2022-09-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", + started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 2, - shift: 50, - author: 95 + ended_at: "2022-09-10T22:00:09Z" } - ] + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" } -]; \ No newline at end of file +]; + + +// You may need to add employees for available hours; add more clock-ins for long breaks, and check distance in and out \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/Hours.js b/src/js/views/metrics/general-stats/Hours/Hours.js index 1afd8d6..f930aa6 100644 --- a/src/js/views/metrics/general-stats/Hours/Hours.js +++ b/src/js/views/metrics/general-stats/Hours/Hours.js @@ -45,17 +45,21 @@ export const Hours = () => { - -

    {HoursData[0].description}

    -

    {HoursData[0].qty}

    -

    {`${HoursData[0].pct}%`}

    - - - -

    {HoursData[1].description}

    -

    {HoursData[1].qty}

    -

    {`${HoursData[1].pct}%`}

    - + {HoursData.map((item, i) => { + return item.description === "Available Hours" ? ( + +

    {item.description}

    +

    {item.qty}

    +

    {`${item.pct}%`}

    + + ) : ( + +

    {item.description}

    +

    {item.qty}

    +

    {`${item.pct}%`}

    + + ) + })} @@ -71,7 +75,7 @@ export const Hours = () => {

    Hours Chart

    -
    +
    diff --git a/src/js/views/metrics/general-stats/Hours/HoursData.js b/src/js/views/metrics/general-stats/Hours/HoursData.js index 678bb0f..802907b 100644 --- a/src/js/views/metrics/general-stats/Hours/HoursData.js +++ b/src/js/views/metrics/general-stats/Hours/HoursData.js @@ -4,68 +4,100 @@ import { DummyDataShifts } from "./DummyDataShifts" const HoursDataGenerator = () => { - // First array - let clockInsList = []; - - // Gathering the clock-ins of all the shifts - DummyDataShifts.forEach((shift) => { - shift.clockin.forEach((clockIn) => { - if (shift.clockin.length > 0) { - clockInsList.push({ - start: clockIn.started_at, - end: clockIn.ended_at - }); - } - }); + // First array + let completeList = []; + + // Gathering both clock-ins and clock-outs + DummyDataShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + completeList.push({ + starting_at: shift.starting_at, + started_at: clockIn.started_at, + ending_at: shift.ending_at, + ended_at: clockIn.ended_at + }); + } }); - - // Adding all the hours worked from each shift - let hoursWorked = clockInsList.reduce( - (total, { start, end }) => - total + moment.duration(moment(end).diff(moment(start))).asHours(), - 0 - ); - - // Formatting hours worked - let hoursWorkedFormatted = (Math.round(hoursWorked * 4) / 4).toFixed(0); - - // THIS IS A PLACEHOLDER, we double the hours worked to mimic available hours - let availableHours = (hoursWorkedFormatted * 2).toString() - - // Generate object of worked hours - let workedHoursObj = { - id: 1, - description: "Hours Worked", - qty: hoursWorkedFormatted - }; - - // Generate semi-final list - let semiFinalList = []; - - // Adding object of worked hours to semi-final list - semiFinalList.push(workedHoursObj); - - // Generating final array with percentages as new properties - let finalList = semiFinalList.map(({ id, description, qty }) => ({ - id, - description, - qty, - pct: ((qty * 100) / availableHours).toFixed(0) - })); - - // Generating object of available hours - let availableHoursObj = { - id: 2, - description: "Available Hours", - qty: availableHours, - pct: "100" - }; - - // Adding object of available hours to final list - finalList.push(availableHoursObj); - - // Returning the final array - return finalList + }); + + // Adding all the scheduled hours from each shift + let scheduledHours = completeList.reduce( + (total, { starting_at, ending_at }) => + total + + moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(), + 0 + ); + + // Formatting scheduled hours + let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(0); + + // Adding all the worked hours from each shift + let workedHours = completeList.reduce( + (total, { started_at, ended_at }) => + total + + moment.duration(moment(ended_at).diff(moment(started_at))).asHours(), + 0 + ); + + // Formatting worked hours + let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(0); + + // THIS IS A PLACEHOLDER, we double the hours worked to mimic available hours + let availableHours = (workedHoursFormatted * 2).toString() + + // Creating object for worked hours + let workedHoursObj = { + description: "Hours Worked", + qty: workedHoursFormatted + }; + + // Calculating extra worked hours + let extraHours = workedHoursFormatted - scheduledHoursFormatted; + + // Creating object for extra worked hours + let extraHoursObj = { + description: "Extra Hours Worked", + qty: extraHours + }; + + // Creating object for long breaks + let longBreaksObj = { + description: "Long Breaks", + qty: "10" + }; + + // Generate semi-final list + let semiFinalList = []; + + // Adding object of worked hours to semi-final list + semiFinalList.push(workedHoursObj); + semiFinalList.push(extraHoursObj); + semiFinalList.push(longBreaksObj); + + // Generating final array with percentages as new properties + let finalList = semiFinalList.map(({ description, qty }) => ({ + description, + qty, + pct: ((qty * 100) / availableHours).toFixed(0) + })); + + // Generating object of available hours + let availableHoursObj = { + description: "Available Hours", + qty: availableHours, + pct: "100" + }; + + // Adding object of available hours to final list + finalList.push(availableHoursObj); + + finalList.forEach((item, i) => { + item.id = i + 1; + }); + + // Returning the final array + return finalList } // Exporting the final array diff --git a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js index b63fceb..a8c5537 100644 --- a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js +++ b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js @@ -39,17 +39,14 @@ const ShiftsDataGenerator = () => { description: "Open Shifts", qty: open } - let filledShifts = { description: "Filled Shifts", qty: filled } - let workedShifts = { description: "Worked Shifts", qty: completed } - let rejectedShifts = { description: "Rejected Shifts", qty: rejected @@ -58,7 +55,7 @@ const ShiftsDataGenerator = () => { // Setting up base array for all shift objects let cleanedArray = [] - // Pushing shift object to base array + // Pushing shift objects to base array cleanedArray.push(openShifts) cleanedArray.push(filledShifts) cleanedArray.push(workedShifts) diff --git a/src/js/views/metrics/punctuality/DummyDataShifts.js b/src/js/views/metrics/punctuality/DummyDataShifts.js index cf74350..0c3b5d0 100644 --- a/src/js/views/metrics/punctuality/DummyDataShifts.js +++ b/src/js/views/metrics/punctuality/DummyDataShifts.js @@ -1,381 +1,158 @@ export const DummyDataShifts = [ - { - id: 45, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 - }, - position: { - title: "Server", - id: 1 - }, - status: "COMPLETED", - clockin: [ - { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-19T10:00:00Z", - ending_at: "2022-09-19T20:00:00Z", - created_at: "2022-09-10T19:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - }, - { - id: 49, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 - }, - position: { - title: "Server", - id: 1 - }, - status: "COMPLETED", - clockin: [ - { - id: 18, - started_at: "2022-09-20T09:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.5", - ended_at: "2022-09-20T20:45:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 49, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-20T10:00:00Z", - ending_at: "2022-09-20T20:00:00Z", - created_at: "2022-09-10T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - }, - { - id: 46, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 - }, - position: { - title: "Server", - id: 1 - }, - status: "COMPLETED", - clockin: [ - { - id: 17, - started_at: "2022-09-21T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.4", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 4, - shift: 46, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-21T10:00:00Z", - ending_at: "2022-09-21T20:00:00Z", - created_at: "2022-09-10T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [4] - }, - { - id: 47, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 - }, - position: { - title: "Server", - id: 1 - }, - status: "COMPLETED", - clockin: [ - { - id: 19, - started_at: "2022-09-21T10:36:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.4", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 5, - shift: 47, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-22T10:00:00Z", - ending_at: "2022-09-22T20:30:00Z", - created_at: "2022-09-15T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - }, - { - id: 48, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 - }, - position: { - title: "Server", - id: 1 - }, - status: "COMPLETED", - clockin: [ - { - id: 15, - started_at: "2022-09-10T09:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.8", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 4, - shift: 50, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-23T10:00:00Z", - ending_at: "2022-09-23T20:30:00Z", - created_at: "2022-09-15T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [4] - }, - { - id: 50, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 - }, - position: { - title: "Server", - id: 1 - }, - status: "COMPLETED", - clockin: [ - { - id: 15, - started_at: "2022-09-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 50, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z", - created_at: "2022-09-10T19:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - } - ]; - - /* - FILLED + CLOCK-IN // NO CLOCK-OUT - - { - "id": 45, - "venue": { - "title": "250 Catalonia Avenue", - "id": 30, - "latitude": "25.744481", - "longitude": "-80.259618", - "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Server", - "id": 1 - }, - "status": "FILLED", - "clockin": [ - { - "id": 16, - "started_at": "2022-09-94T19:28:09Z", - "latitude_in": "25.74446444000", - "longitude_in": "-80.25952809000", - "distance_in_miles": "0.009", - "latitude_out": "0.00000000000", - "longitude_out": "0.00000000000", - "distance_out_miles": "0.000", - "ended_at": null, - "automatically_closed": false, - "created_at": "2022-09-19T19:28:08.811375Z", - "updated_at": "2022-09-19T19:28:08.818207Z", - "status": "PENDING", - "employee": 19, - "shift": 45, - "author": 95 - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "8.0", - "starting_at": "2022-09-28T19:30:00Z", - "ending_at": "2022-09208T21:30:00Z", - "created_at": "2022-09-19T19:21:34.638331Z", - "description": "", - "employer": 80, - "author": null, - "candidates": [], - "employees": [ - 19 - ] - } - - COMPLETED - - { - "id": 4335, - "venue": { - "title": "The Club of Knights", - "id": 47, - "latitude": "25.744510", - "longitude": "-80.260054", - "street_address": "270 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Cleaning Specialist", - "id": 41 - }, - "status": "COMPLETED", - "clockin": [ - { - "id": 3382, - "started_at": "2021-08-99T11:00:00Z", - "latitude_in": "25.74440950000", - "longitude_in": "-80.25995216000", - "distance_in_miles": "0.150", - "latitude_out": "25.74440950000", - "longitude_out": "-80.25995216000", - "distance_out_miles": "0.150", - "ended_at": "2021-08-09T17:00:00Z", - "automatically_closed": false, - "created_at": "2021-08-12T14:15:29.510326Z", - "updated_at": "2021-08-12T14:15:29.510343Z", - "status": "PENDING", - "employee": 179, - "shift": 4335, - "author": null - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "12.5", - "starting_at": "2021-08-09T11:00:00Z", - "ending_at": "2021-08209T19:00:04Z", - "created_at": "2021-08-12T14:09:11.474735Z", - "description": "", - "employer": 1, - "author": null, - "candidates": [], - "employees": [] - } - */ - \ No newline at end of file + { + clockin: [ + { + started_at: "2022-09-10T10:45:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T19:25:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T09:15:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:17:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:31:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T10:55:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:19:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T10:40:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T19:25:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T11:00:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T19:20:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T10:14:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T21:35:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T10:08:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:15:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T10:40:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:40:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T08:00:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:45:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z" + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + } +]; diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index 0a4d4fd..4db7c3e 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -1,27 +1,43 @@ import React from "react"; import { PieChart } from '../charts'; -import { PunctualityData } from "./PunctualityData"; +import { ClockInsData, ClockOutsData } from "./PunctualityData"; // Colors const purple = "#5c00b8"; const lightTeal = "#00ebeb"; const darkTeal = "#009e9e"; const green = "#06ff05"; -const lightPink = "#eb00eb"; -const darkPink = "#b200b2"; export const Punctuality = () => { - // Data for pie chart ------------------------------------------------------------------------------------- + // Data for pie charts ------------------------------------------------------------------------------------- - const punctualityData = { - labels: PunctualityData.map((data) => data.reason), + // Clock-Ins + + let dataCI = ClockInsData.filter((item) => { return item.description !== "Total Clock-Ins" }) + + const clockInsData = { + labels: dataCI.map((data) => data.description), + datasets: [{ + label: "Clock-Ins", + data: dataCI.map((data) => data.qty), + backgroundColor: [ + purple, lightTeal + ], + }] + } + + // Clock-Outs + + let dataCO = ClockOutsData.filter((item) => { return item.description !== "Total Clock-Outs" }) + + const clockOutsData = { + labels: dataCO.map((data) => data.description), datasets: [{ - label: "Punctuality", - data: PunctualityData.map((data) => data.qty), + label: "Clock-Outs", + data: dataCO.map((data) => data.qty), backgroundColor: [ - purple, darkPink, lightPink, - green, lightTeal, darkTeal + green, darkTeal ], }] } @@ -32,48 +48,104 @@ export const Punctuality = () => {
    {/* Left Column Starts */}
    +
    + {/* Clock-Ins Table Starts */} +
    +

    Clock-Ins Table

    + + + + + + + + + + + + {ClockInsData.map((item, i) => { + return item.description === "Total Clock-Ins" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Clock-Ins Table Ends */} +
    +
    - {/* Punctuality Table Starts */} + {/* Clock-Outs Table Starts */}
    -

    Punctuality Table

    +

    Clock-Outs Table

    - + + - {PunctualityData.map((item, i) => { - return ( + {ClockOutsData.map((item, i) => { + return item.description === "Total Clock-Outs" ? ( + + + + + + ) : ( - + + ) })}

    Reason

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.reason}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    - {/* Punctuality Table Ends */} + {/* Clock-Outs Table Ends */}
    {/* Left Column Ends */} {/* Right Column Starts */}
    -
    - {/* Punctuality Chart Starts */} +
    + {/* Clock-Ins Chart Starts */} +
    +

    Clock-Ins Chart

    + +
    + +
    +
    + {/* Clock-Ins Chart Ends */} +
    + +
    + {/* Clock-Outs Chart Starts */}
    -

    Punctuality Chart

    +

    Clock-Outs Chart

    -
    - +
    +
    - {/* Punctuality Chart Ends */} + {/* Clock-Outs Chart Ends */}
    {/* Right Column Ends */} diff --git a/src/js/views/metrics/punctuality/PunctualityData.js b/src/js/views/metrics/punctuality/PunctualityData.js index 57dc628..3e939cb 100644 --- a/src/js/views/metrics/punctuality/PunctualityData.js +++ b/src/js/views/metrics/punctuality/PunctualityData.js @@ -1,42 +1,177 @@ -export const PunctualityData = [ - { - id: 1, - reason: "Forgot to Clock-In", - qty: 101, - }, - { - id: 2, - reason: "Forgot to Clock-Out", - qty: 212, - }, - { - id: 3, - reason: "Early Clock-In", - qty: 354, - }, - { - id: 4, - reason: "Early Clock-Out", - qty: 478, - }, - { - id: 5, - reason: "Late Clock-In", - qty: 564, - }, - { - id: 6, - reason: "Late Clock-Out", - qty: 654, - }, - { - id: 7, - reason: "Long Break", - qty: 751, - }, - { - id: 8, - reason: "Extra Hours", - qty: 894, - } -] +import { DummyDataShifts } from "./DummyDataShifts" +import moment from "moment"; + +// Clock-Ins Data ------------------------------------------------------------------ + +const ClockInsDataGenerator = () => { + + // Clock-ins array + let clockIns = []; + + // Sorting the shifts + DummyDataShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + // Gathering the clock-ins + clockIns.push({ + starting_at: shift.starting_at, + started_at: clockIn.started_at + }); + } + }); + }); + + // Setting up counters + let earlyClockins = 0; + let lateClockins = 0; + + // Increasing clock-in counters + clockIns.forEach((shift) => { + let start1 = moment(shift.starting_at); + let start2 = moment(shift.started_at); + + let startDiff = moment.duration(start2.diff(start1)).asMinutes(); + + if (startDiff >= 15) { + lateClockins++; + } else if (startDiff <= -30) { + earlyClockins++; + } + }); + + // Creating clock-in objects + let earlyClockinsObj = { + description: "Early Clock-Ins", + qty: earlyClockins + }; + let lateClockinsObj = { + description: "Late Clock-Ins", + qty: lateClockins + }; + + // Setting up base array for all objects + let cleanedClockIns = []; + + // Pushing objects to base array + cleanedClockIns.push(earlyClockinsObj); + cleanedClockIns.push(lateClockinsObj); + + // Setting up totals + let totalClockIns = clockIns.length; + + // Generating percentages as new properties + let pctClockIns = cleanedClockIns.map(({ description, qty }) => ({ + description, + qty, + pct: ((qty * 100) / totalClockIns).toFixed(0) + })); + + // Setting up object for totals + let totalClockInsObj = { + description: "Total Clock-Ins", + qty: totalClockIns, + pct: "100" + }; + + // Adding totals to the array with percentages + pctClockIns.push(totalClockInsObj); + + // Addind IDs to each object + pctClockIns.forEach((item, i) => { + item.id = i + 1; + }); + + // Returning clock-ins array + return pctClockIns +} + +// Exporting clock-ins array +export const ClockInsData = ClockInsDataGenerator() + + +// Clock-Outs Data ----------------------------------------------------------------- + +const ClockOutsDataGenerator = () => { + + // Clock-outs array + let clockOuts = []; + + // Sorting the shifts + DummyDataShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + // Gathering the clock-outs + clockOuts.push({ + ending_at: shift.ending_at, + ended_at: clockIn.ended_at + }); + } + }); + }); + + // Setting up counters + let earlyClockouts = 0; + let lateClockouts = 0; + + // Increasing clock-out counters + clockOuts.forEach((shift) => { + let end1 = moment(shift.ending_at); + let end2 = moment(shift.ended_at); + + let endDiff = moment.duration(end2.diff(end1)).asMinutes(); + + if (endDiff >= 30) { + lateClockouts++; + } else if (endDiff <= -30) { + earlyClockouts++; + } + }); + + // Creating clock-out objects + let earlyClockoutsObj = { + description: "Early Clock-Outs", + qty: earlyClockouts + }; + let lateClockoutsObj = { + description: "Late Clock-Outs", + qty: lateClockouts + }; + + // Setting up base array for all objects + let cleanedClockOuts = []; + + // Pushing objects to base array + cleanedClockOuts.push(earlyClockoutsObj); + cleanedClockOuts.push(lateClockoutsObj); + + // Setting up totals + let totalClockOuts = clockOuts.length; + + // Generating percentages as new properties + let pctClockOuts = cleanedClockOuts.map(({ description, qty }) => ({ + description, + qty, + pct: ((qty * 100) / totalClockOuts).toFixed(0) + })); + + // Setting up object for totals + let totalClockOutsObj = { + description: "Total Clock-Outs", + qty: totalClockOuts, + pct: "100" + }; + + // Adding totals to the array with percentages + pctClockOuts.push(totalClockOutsObj); + + // Addind IDs to each object + pctClockOuts.forEach((item, i) => { + item.id = i + 1; + }); + + // Returning clock-outs array + return pctClockOuts +} + +// Exporting clock-outs array +export const ClockOutsData = ClockOutsDataGenerator() \ No newline at end of file From 491c53165240c4bc8d73c39fcccd911b4a7b9957 Mon Sep 17 00:00:00 2001 From: paola9896 Date: Wed, 28 Sep 2022 21:26:58 +0000 Subject: [PATCH 04/10] Forgotten Clock-Outs in Punctuality were added --- .../metrics/punctuality/DummyDataShifts.js | 65 +++++++++++++++---- .../views/metrics/punctuality/Punctuality.js | 2 +- .../metrics/punctuality/PunctualityData.js | 16 ++++- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/js/views/metrics/punctuality/DummyDataShifts.js b/src/js/views/metrics/punctuality/DummyDataShifts.js index 0c3b5d0..ef05c49 100644 --- a/src/js/views/metrics/punctuality/DummyDataShifts.js +++ b/src/js/views/metrics/punctuality/DummyDataShifts.js @@ -5,7 +5,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T10:45:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z" + ended_at: "2022-09-10T20:00:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -17,7 +18,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z" + ended_at: "2022-09-10T19:25:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -29,7 +31,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T09:15:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:17:09Z" + ended_at: "2022-09-10T20:17:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -41,7 +44,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:31:09Z" + ended_at: "2022-09-10T20:31:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -53,7 +57,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T10:55:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:19:09Z" + ended_at: "2022-09-10T20:19:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -65,7 +70,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T10:40:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z" + ended_at: "2022-09-10T19:25:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -77,7 +83,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T11:00:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:20:09Z" + ended_at: "2022-09-10T19:20:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -89,7 +96,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T10:14:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T21:35:09Z" + ended_at: "2022-09-10T21:35:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -101,7 +109,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T10:08:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:15:09Z" + ended_at: "2022-09-10T20:15:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -113,7 +122,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T10:40:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:40:09Z" + ended_at: "2022-09-10T20:40:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -125,7 +135,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T08:00:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:45:09Z" + ended_at: "2022-09-10T20:45:09Z", + automatically_closed: false } ], starting_at: "2022-09-10T10:00:00Z", @@ -137,7 +148,34 @@ export const DummyDataShifts = [ started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z" + ended_at: "2022-09-10T20:00:09Z", + automatically_closed: false + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z", + }, + { + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z", + automatically_closed: false + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z", + automatically_closed: true, } ], starting_at: "2022-09-10T10:00:00Z", @@ -149,7 +187,8 @@ export const DummyDataShifts = [ started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z" + ended_at: "2022-09-10T20:00:09Z", + automatically_closed: true } ], starting_at: "2022-09-10T10:00:00Z", diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index 4db7c3e..23be3fc 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -37,7 +37,7 @@ export const Punctuality = () => { label: "Clock-Outs", data: dataCO.map((data) => data.qty), backgroundColor: [ - green, darkTeal + green, darkTeal, purple ], }] } diff --git a/src/js/views/metrics/punctuality/PunctualityData.js b/src/js/views/metrics/punctuality/PunctualityData.js index 3e939cb..71f8339 100644 --- a/src/js/views/metrics/punctuality/PunctualityData.js +++ b/src/js/views/metrics/punctuality/PunctualityData.js @@ -103,7 +103,8 @@ const ClockOutsDataGenerator = () => { // Gathering the clock-outs clockOuts.push({ ending_at: shift.ending_at, - ended_at: clockIn.ended_at + ended_at: clockIn.ended_at, + automatically_closed: clockIn.automatically_closed }); } }); @@ -112,6 +113,7 @@ const ClockOutsDataGenerator = () => { // Setting up counters let earlyClockouts = 0; let lateClockouts = 0; + let forgotClockOut= 0; // Increasing clock-out counters clockOuts.forEach((shift) => { @@ -124,6 +126,13 @@ const ClockOutsDataGenerator = () => { lateClockouts++; } else if (endDiff <= -30) { earlyClockouts++; + } + }); + + // Increasing forgotClockOut counter + clockOuts.forEach((shift) => { + if (shift.automatically_closed === true) { + forgotClockOut++; } }); @@ -136,6 +145,10 @@ const ClockOutsDataGenerator = () => { description: "Late Clock-Outs", qty: lateClockouts }; + let forgotClockOutObj = { + description: "Forgotten Clock-Outs", + qty: forgotClockOut + }; // Setting up base array for all objects let cleanedClockOuts = []; @@ -143,6 +156,7 @@ const ClockOutsDataGenerator = () => { // Pushing objects to base array cleanedClockOuts.push(earlyClockoutsObj); cleanedClockOuts.push(lateClockoutsObj); + cleanedClockOuts.push(forgotClockOutObj); // Setting up totals let totalClockOuts = clockOuts.length; From 73f0376f9689f5c65e4420e8ed8632fa165899cd Mon Sep 17 00:00:00 2001 From: paola9896 Date: Tue, 4 Oct 2022 13:38:17 +0000 Subject: [PATCH 05/10] Improved 'Queue', 'Hours' and 'Punctuality to handle multiple clock-ins, I also changed 'Employees' to 'Job Seekers' (still improving Job Seekers, not finished) --- objeto | 372 ++++ out/Employees.js.html | 151 ++ .../EmployeesData.js.html | 67 +- out/GeneralStats.js.html | 110 + out/Hours.js.html | 150 ++ out/HoursData.js.html | 163 ++ out/Metrics.html | 187 ++ out/Punctuality.js.html | 222 ++ out/PunctualityData.js.html | 259 +++ out/Queue.js.html | 196 ++ out/Ratings.js.html | 162 ++ out/RatingsData.js.html | 127 ++ out/Shifts.js.html | 153 ++ out/ShiftsData.js.html | 149 ++ out/WorkerStats.js.html | 175 ++ out/charts.js.html | 112 + out/fonts/OpenSans-Bold-webfont.eot | Bin 0 -> 19544 bytes out/fonts/OpenSans-Bold-webfont.svg | 1830 ++++++++++++++++ out/fonts/OpenSans-Bold-webfont.woff | Bin 0 -> 22432 bytes out/fonts/OpenSans-BoldItalic-webfont.eot | Bin 0 -> 20133 bytes out/fonts/OpenSans-BoldItalic-webfont.svg | 1830 ++++++++++++++++ out/fonts/OpenSans-BoldItalic-webfont.woff | Bin 0 -> 23048 bytes out/fonts/OpenSans-Italic-webfont.eot | Bin 0 -> 20265 bytes out/fonts/OpenSans-Italic-webfont.svg | 1830 ++++++++++++++++ out/fonts/OpenSans-Italic-webfont.woff | Bin 0 -> 23188 bytes out/fonts/OpenSans-Light-webfont.eot | Bin 0 -> 19514 bytes out/fonts/OpenSans-Light-webfont.svg | 1831 ++++++++++++++++ out/fonts/OpenSans-Light-webfont.woff | Bin 0 -> 22248 bytes out/fonts/OpenSans-LightItalic-webfont.eot | Bin 0 -> 20535 bytes out/fonts/OpenSans-LightItalic-webfont.svg | 1835 +++++++++++++++++ out/fonts/OpenSans-LightItalic-webfont.woff | Bin 0 -> 23400 bytes out/fonts/OpenSans-Regular-webfont.eot | Bin 0 -> 19836 bytes out/fonts/OpenSans-Regular-webfont.svg | 1831 ++++++++++++++++ out/fonts/OpenSans-Regular-webfont.woff | Bin 0 -> 22660 bytes out/global.html | 238 +++ out/index.html | 63 + out/metrics.js.html | 226 ++ out/scripts/linenumber.js | 25 + out/scripts/prettify/Apache-License-2.0.txt | 202 ++ out/scripts/prettify/lang-css.js | 2 + out/scripts/prettify/prettify.js | 28 + out/styles/jsdoc-default.css | 358 ++++ out/styles/prettify-jsdoc.css | 111 + out/styles/prettify-tomorrow.css | 132 ++ package-lock.json | 164 +- package.json | 1 + src/js/PrivateLayout.js | 7 + src/js/views/metrics/charts.js | 16 + .../Employees/DummyDataShifts.js | 253 --- .../Employees/DummyDataWorkers.js | 1 - .../general-stats/Employees/Employees.js | 88 - .../metrics/general-stats/GeneralStats.js | 21 +- .../general-stats/Hours/DummyDataShifts.js | 219 +- .../general-stats/Hours/DummyDataWorkers.js | 1 - .../metrics/general-stats/Hours/Hours.js | 13 +- .../metrics/general-stats/Hours/HoursData.js | 320 ++- .../JobSeekers/DummyDataShifts.js | 145 ++ .../JobSeekers/DummyDataWorkers.js | 70 + .../general-stats/JobSeekers/JobSeekers.js | 170 ++ .../JobSeekers/JobSeekersData.js | 176 ++ .../metrics/general-stats/Shifts/Shifts.js | 14 +- .../general-stats/Shifts/ShiftsData.js | 8 + src/js/views/metrics/metrics.js | 35 +- .../metrics/punctuality/DummyDataShifts.js | 284 ++- .../views/metrics/punctuality/Punctuality.js | 62 +- .../metrics/punctuality/PunctualityData.js | 57 +- src/js/views/metrics/queue/DummyDataShifts.js | 645 +++--- src/js/views/metrics/queue/Queue.js | 23 +- .../queue/{WorkerStats.js => QueueData.js} | 99 +- src/js/views/metrics/ratings/Ratings.js | 15 +- src/js/views/metrics/ratings/RatingsData.js | 8 + 71 files changed, 17189 insertions(+), 853 deletions(-) create mode 100644 objeto create mode 100644 out/Employees.js.html rename src/js/views/metrics/general-stats/Employees/EmployeesData.js => out/EmployeesData.js.html (59%) create mode 100644 out/GeneralStats.js.html create mode 100644 out/Hours.js.html create mode 100644 out/HoursData.js.html create mode 100644 out/Metrics.html create mode 100644 out/Punctuality.js.html create mode 100644 out/PunctualityData.js.html create mode 100644 out/Queue.js.html create mode 100644 out/Ratings.js.html create mode 100644 out/RatingsData.js.html create mode 100644 out/Shifts.js.html create mode 100644 out/ShiftsData.js.html create mode 100644 out/WorkerStats.js.html create mode 100644 out/charts.js.html create mode 100644 out/fonts/OpenSans-Bold-webfont.eot create mode 100644 out/fonts/OpenSans-Bold-webfont.svg create mode 100644 out/fonts/OpenSans-Bold-webfont.woff create mode 100644 out/fonts/OpenSans-BoldItalic-webfont.eot create mode 100644 out/fonts/OpenSans-BoldItalic-webfont.svg create mode 100644 out/fonts/OpenSans-BoldItalic-webfont.woff create mode 100644 out/fonts/OpenSans-Italic-webfont.eot create mode 100644 out/fonts/OpenSans-Italic-webfont.svg create mode 100644 out/fonts/OpenSans-Italic-webfont.woff create mode 100644 out/fonts/OpenSans-Light-webfont.eot create mode 100644 out/fonts/OpenSans-Light-webfont.svg create mode 100644 out/fonts/OpenSans-Light-webfont.woff create mode 100644 out/fonts/OpenSans-LightItalic-webfont.eot create mode 100644 out/fonts/OpenSans-LightItalic-webfont.svg create mode 100644 out/fonts/OpenSans-LightItalic-webfont.woff create mode 100644 out/fonts/OpenSans-Regular-webfont.eot create mode 100644 out/fonts/OpenSans-Regular-webfont.svg create mode 100644 out/fonts/OpenSans-Regular-webfont.woff create mode 100644 out/global.html create mode 100644 out/index.html create mode 100644 out/metrics.js.html create mode 100644 out/scripts/linenumber.js create mode 100644 out/scripts/prettify/Apache-License-2.0.txt create mode 100644 out/scripts/prettify/lang-css.js create mode 100644 out/scripts/prettify/prettify.js create mode 100644 out/styles/jsdoc-default.css create mode 100644 out/styles/prettify-jsdoc.css create mode 100644 out/styles/prettify-tomorrow.css delete mode 100644 src/js/views/metrics/general-stats/Employees/DummyDataShifts.js delete mode 100644 src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js delete mode 100644 src/js/views/metrics/general-stats/Employees/Employees.js delete mode 100644 src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js create mode 100644 src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js create mode 100644 src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js create mode 100644 src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js create mode 100644 src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js rename src/js/views/metrics/queue/{WorkerStats.js => QueueData.js} (60%) diff --git a/objeto b/objeto new file mode 100644 index 0000000..6ba0b06 --- /dev/null +++ b/objeto @@ -0,0 +1,372 @@ +// This is the object of an employee, let's call him Person_1 + +{ + "id": 18, + "positions": [ + { + "id": 1, + "picture": "", + "title": "Server", + "description": "", + "meta_description": "", + "meta_keywords": "", + "created_at": "2018-09-13T19:45:00Z", + "updated_at": "2018-09-13T19:45:00Z", + "status": "ACTIVE", + "pay_rate": [] + }, + { + "id": 2, + "picture": "", + "title": "Kitchen Assistant", + "description": "", + "meta_description": "", + "meta_keywords": "", + "created_at": "2018-09-13T19:45:00Z", + "updated_at": "2018-09-13T19:45:00Z", + "status": "ACTIVE", + "pay_rate": [] + }, + { + "id": 3, + "picture": "", + "title": "Floor Manager", + "description": "", + "meta_description": "", + "meta_keywords": "", + "created_at": "2018-09-13T19:45:00Z", + "updated_at": "2018-09-13T19:45:00Z", + "status": "ACTIVE", + "pay_rate": [] + }, + { + "id": 4, + "picture": "", + "title": "Executive Chef", + "description": "", + "meta_description": "", + "meta_keywords": "", + "created_at": "2018-09-13T19:45:00Z", + "updated_at": "2018-09-13T19:45:00Z", + "status": "ACTIVE", + "pay_rate": [] + }, + { + "id": 5, + "picture": "", + "title": "Runner", + "description": "", + "meta_description": "", + "meta_keywords": "", + "created_at": "2018-09-13T19:45:00Z", + "updated_at": "2018-09-13T19:45:00Z", + "status": "ACTIVE", + "pay_rate": [] + }, + { + "id": 6, + "picture": "", + "title": "Warehouse Utility", + "description": "", + "meta_description": "", + "meta_keywords": "", + "created_at": "2018-09-13T19:45:00Z", + "updated_at": "2018-09-13T19:45:00Z", + "status": "ACTIVE", + "pay_rate": [] + } + ], + "badges": [], + "favoritelist_set": [], + "user": { + "first_name": "Dalai", + "last_name": "Lama", + "email": "dalailama@jobcore.co", + "profile": { + "picture": "https://res.cloudinary.com/hplilzdkc/image/upload/v1650054127/57/profile57.jpg", + "bio": "", + "phone_number": "7863299422" + } + }, + "minimum_hourly_rate": "18.0", + "stop_receiving_invites": false, + "rating": null, + "total_ratings": 0, + "total_pending_payments": 0, + "maximum_job_distance_miles": 50, + "job_count": 0, + "created_at": "2022-04-14T17:25:07.821760Z", + "updated_at": "2022-05-26T18:56:37.236311Z", + "response_time": 0, + "total_invites": 0, + "employability_expired_at": "2022-06-03T00:00:00Z", + "employment_verification_status": "APPROVED", + "filing_status": "SINGLE", + "allowances": 0, + "w4_year": 0, + "step2c_checked": false, + "dependants_deduction": "0.00", + "other_income": "0.00", + "additional_deductions": "0.00", + "extra_withholding": "0.00" +} + + +// This is the object of a shift that has one clock-in registered by Person_1 + +{ + "id": 49, + "venue": { + "title": "250 Catalonia Avenue", + "id": 32, + "latitude": "25.744481", + "longitude": "-80.259618", + "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Server", + "id": 1 + }, + "status": "FILLED", + "clockin": [ + { + "id": 17, + "started_at": "2022-09-29T16:34:14Z", + "latitude_in": "25.74446770000", + "longitude_in": "-80.25945610000", + "distance_in_miles": "0.016", + "latitude_out": "0.00000000000", + "longitude_out": "0.00000000000", + "distance_out_miles": "0.000", + "ended_at": null, + "automatically_closed": false, + "created_at": "2022-09-29T16:34:15.071796Z", + "updated_at": "2022-09-29T16:34:15.074379Z", + "status": "PENDING", + "employee": 18, + "shift": 49, + "author": 57 + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "8.0", + "starting_at": "2022-09-29T16:45:00Z", + "ending_at": "2022-09-29T18:30:25.064000Z", + "created_at": "2022-09-29T16:28:43.344518Z", + "description": "", + "employer": 84, + "author": null, + "candidates": [], + "employees": [ + 18 + ] +} + +// Now, Person_1 has clocked-out to eat his lunch. He will be back + +{ + "count": 1, + "first": null, + "next": null, + "previous": null, + "last": null, + "results": [ + { + "id": 49, + "venue": { + "title": "250 Catalonia Avenue", + "id": 32, + "latitude": "25.744481", + "longitude": "-80.259618", + "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Server", + "id": 1 + }, + "status": "FILLED", + "clockin": [ + { + "id": 17, + "started_at": "2022-09-29T16:34:14Z", + "latitude_in": "25.74446770000", + "longitude_in": "-80.25945610000", + "distance_in_miles": "0.016", + "latitude_out": "25.74446770000", + "longitude_out": "-80.25945610000", + "distance_out_miles": "0.016", + "ended_at": "2022-09-29T16:36:36Z", + "automatically_closed": false, + "created_at": "2022-09-29T16:34:15.071796Z", + "updated_at": "2022-09-29T16:36:37.171524Z", + "status": "PENDING", + "employee": 18, + "shift": 49, + "author": 57 + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "8.0", + "starting_at": "2022-09-29T16:45:00Z", + "ending_at": "2022-09-29T18:30:25.064000Z", + "created_at": "2022-09-29T16:28:43.344518Z", + "description": "", + "employer": 84, + "author": null, + "candidates": [], + "employees": [ + 18 + ] + } + ] +} + + +// Person_1 has finished his lunch, so now he has clocked-in back to keep working + +{ + "count": 1, + "first": null, + "next": null, + "previous": null, + "last": null, + "results": [ + { + "id": 49, + "venue": { + "title": "250 Catalonia Avenue", + "id": 32, + "latitude": "25.744481", + "longitude": "-80.259618", + "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Server", + "id": 1 + }, + "status": "FILLED", + "clockin": [ + { + "id": 17, + "started_at": "2022-09-29T16:34:14Z", + "latitude_in": "25.74446770000", + "longitude_in": "-80.25945610000", + "distance_in_miles": "0.016", + "latitude_out": "25.74446770000", + "longitude_out": "-80.25945610000", + "distance_out_miles": "0.016", + "ended_at": "2022-09-29T16:36:36Z", + "automatically_closed": false, + "created_at": "2022-09-29T16:34:15.071796Z", + "updated_at": "2022-09-29T16:36:37.171524Z", + "status": "PENDING", + "employee": 18, + "shift": 49, + "author": 57 + }, + { + "id": 18, + "started_at": "2022-09-29T16:39:02Z", + "latitude_in": "25.74446770000", + "longitude_in": "-80.25945610000", + "distance_in_miles": "0.016", + "latitude_out": "0.00000000000", + "longitude_out": "0.00000000000", + "distance_out_miles": "0.000", + "ended_at": null, + "automatically_closed": false, + "created_at": "2022-09-29T16:39:03.074089Z", + "updated_at": "2022-09-29T16:39:03.077391Z", + "status": "PENDING", + "employee": 18, + "shift": 49, + "author": 57 + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "8.0", + "starting_at": "2022-09-29T16:45:00Z", + "ending_at": "2022-09-29T18:30:25.064000Z", + "created_at": "2022-09-29T16:28:43.344518Z", + "description": "", + "employer": 84, + "author": null, + "candidates": [], + "employees": [ + 18 + ] + } + ] +} + + +// Person_1 has finished working for the day, now he is ready to clock-out and go home! +{ + "id": 49, + "venue": { + "title": "250 Catalonia Avenue", + "id": 32, + "latitude": "25.744481", + "longitude": "-80.259618", + "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", + "zip_code": 33134 + }, + "position": { + "title": "Server", + "id": 1 + }, + "status": "FILLED", + "clockin": [ + { + "id": 17, + "started_at": "2022-09-29T16:34:14Z", + "latitude_in": "25.74446770000", + "longitude_in": "-80.25945610000", + "distance_in_miles": "0.016", + "latitude_out": "25.74446770000", + "longitude_out": "-80.25945610000", + "distance_out_miles": "0.016", + "ended_at": "2022-09-29T16:36:36Z", + "automatically_closed": false, + "created_at": "2022-09-29T16:34:15.071796Z", + "updated_at": "2022-09-29T16:36:37.171524Z", + "status": "PENDING", + "employee": 18, + "shift": 49, + "author": 57 + }, + { + "id": 18, + "started_at": "2022-09-29T16:39:02Z", + "latitude_in": "25.74446770000", + "longitude_in": "-80.25945610000", + "distance_in_miles": "0.016", + "latitude_out": "25.74446770000", + "longitude_out": "-80.25945610000", + "distance_out_miles": "0.016", + "ended_at": "2022-09-29T16:43:50Z", + "automatically_closed": false, + "created_at": "2022-09-29T16:39:03.074089Z", + "updated_at": "2022-09-29T16:43:51.131201Z", + "status": "PENDING", + "employee": 18, + "shift": 49, + "author": 57 + } + ], + "maximum_allowed_employees": 1, + "minimum_hourly_rate": "8.0", + "starting_at": "2022-09-29T16:45:00Z", + "ending_at": "2022-09-29T18:30:25.064000Z", + "created_at": "2022-09-29T16:28:43.344518Z", + "description": "", + "employer": 84, + "author": null, + "candidates": [], + "employees": [ + 18 + ] +} \ No newline at end of file diff --git a/out/Employees.js.html b/out/Employees.js.html new file mode 100644 index 0000000..d11b395 --- /dev/null +++ b/out/Employees.js.html @@ -0,0 +1,151 @@ + + + + + JSDoc: Source: Employees.js + + + + + + + + + + +
    + +

    Source: Employees.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { PieChart } from '../../charts';
    +import { EmployeesData } from "./EmployeesData";
    +
    +// Colors
    +const purple = "#5c00b8";
    +const lightPink = "#eb00eb";
    +
    +/**
    + * @function
    + * @description Creates a page with a table and a graph of all the active/inactive employees.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires PieChart
    + * @requires EmployeesData
    + */
    +export const Employees = () => {
    +
    +    // Data for pie chart -------------------------------------------------------------------------------------
    +
    +    // Taking out the "Totals" from the chart view
    +    let pieData = EmployeesData.filter((item) => { return item.description !== "Total Employees" }) // Taking out the "Totals" from the chart view
    +
    +    // Preparing data to be passed to the chart component
    +    const employeesData = {
    +        labels: pieData.map((data) => data.description),
    +        datasets: [{
    +            label: "Employees",
    +            data: pieData.map((data) => data.qty),
    +            backgroundColor: [
    +                purple, lightPink
    +            ],
    +        }]
    +    }
    +
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <div className="row d-flex d-inline-flex justify-content-between mb-4 w-100">
    +            {/* Left Column Starts */}
    +            <div className="col">
    +                <div className="row d-flex flex-column justify-content-between">
    +                    {/* Employees Table Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-4">Employees Table</h2>
    +
    +                        <table className="table table-bordered text-center">
    +                            <thead className="thead-dark">
    +                                {/* Table columns */}
    +                                <tr>
    +                                    <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                </tr>
    +                            </thead>
    +
    +                            <tbody>
    +                                {/* Mapping the data to diplay it as table rows */}
    +                                {EmployeesData.map((item, i) => {
    +                                    return item.description === "Total Employees" ? (
    +                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) :
    +                                        (
    +                                            <tr key={i}>
    +                                                <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                                <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                            </tr>
    +                                        )
    +                                })}
    +                            </tbody>
    +                        </table>
    +                    </div>
    +                    {/* Employees Table Ends */}
    +                </div>
    +            </div>
    +            {/* Left Column Ends */}
    +
    +            {/* Right Column Starts */}
    +            <div className="col">
    +                <div className="row">
    +                    {/* Employees Chart Starts*/}
    +                    <div className="col text-center">
    +                        <h2 className="mb-3">Employees Chart</h2>
    +
    +                        <div style={{ height: '13.90rem' }} className="mx-auto">
    +                            <PieChart pieData={employeesData} />
    +                        </div>
    +                    </div>
    +                    {/* Employees Chart Ends*/}
    +                </div>
    +            </div>
    +            {/* Right Column Ends */}
    +        </div>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:26:24 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/src/js/views/metrics/general-stats/Employees/EmployeesData.js b/out/EmployeesData.js.html similarity index 59% rename from src/js/views/metrics/general-stats/Employees/EmployeesData.js rename to out/EmployeesData.js.html index dddc4c5..85b3e00 100644 --- a/src/js/views/metrics/general-stats/Employees/EmployeesData.js +++ b/out/EmployeesData.js.html @@ -1,4 +1,32 @@ -import { DummyDataShifts } from "./DummyDataShifts" + + + + + JSDoc: Source: EmployeesData.js + + + + + + + + + + +
    + +

    Source: EmployeesData.js

    + + + + + + +
    +
    +
    import { DummyDataShifts } from "./DummyDataShifts"
     import { DummyDataWorkers } from "./DummyDataWorkers"
     import moment from "moment";
     
    @@ -8,6 +36,16 @@
     // Today, four weeks in the past
     const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD")
     
    +/**
    + * @function
    + * @description Takes in list a of shifts and generates data of inactive/active employees for Employees.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @requires DummyDataShifts
    + * @requires DummyDataWorkers
    + * @returns Array of objects
    + */
     const EmployeesDataGenerator = () => {
     
         // First array
    @@ -29,7 +67,7 @@
         clockInsList.forEach((clockIn) => {
             let clockInStart = moment(clockIn.started_at).format("YYYY-MM-DD");
     
    -        if (clockInStart > fourWeeksBack && clockInStart < now) {
    +        if (clockInStart > fourWeeksBack && clockInStart < now) {
                 recentClockIns.push(clockIn)
             }
         })
    @@ -56,7 +94,6 @@
             description: "Active Employees",
             qty: totalActiveWorkers
         }
    -
         let inactiveWorkers = {
             id: 2,
             description: "Inactive Employees",
    @@ -94,4 +131,26 @@
     }
     
     // Exporting the final array
    -export const EmployeesData = EmployeesDataGenerator()
    \ No newline at end of file
    +export const EmployeesData = EmployeesDataGenerator()
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:26:21 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/GeneralStats.js.html b/out/GeneralStats.js.html new file mode 100644 index 0000000..7879c53 --- /dev/null +++ b/out/GeneralStats.js.html @@ -0,0 +1,110 @@ + + + + + JSDoc: Source: GeneralStats.js + + + + + + + + + + +
    + +

    Source: GeneralStats.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { Employees } from "./Employees/Employees";
    +import { Hours } from "./Hours/Hours";
    +import { Shifts } from "./Shifts/Shifts";
    +
    +/**
    + * @function
    + * @description Creates a page with 3 tabs that show metrics about Shifts, Punctuality, and Hours.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires Hours
    + * @requires Shifts
    + * @requires Employees
    + */
    +export const GeneralStats = () => {
    +
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <>
    +          <div className="row d-flex flex-column mx-2">
    +                    {/* Tabs Controller Starts */}
    +                    <nav>
    +                        <div className="nav nav-tabs nav-fill" id="nav-tab" role="tablist">
    +                            <a className="nav-item nav-link active" id="nav-shifts-tab" data-toggle="tab" href="#nav-shifts" role="tab" aria-controls="nav-shifts" aria-selected="true"><h2>Shifts</h2></a>
    +                            <a className="nav-item nav-link" id="nav-hours-tab" data-toggle="tab" href="#nav-hours" role="tab" aria-controls="nav-hours" aria-selected="false"><h2>Hours</h2></a>
    +                            <a className="nav-item nav-link" id="nav-employees-tab" data-toggle="tab" href="#nav-employees" role="tab" aria-controls="nav-employees" aria-selected="false"><h2>Employees</h2></a>
    +                        </div>
    +                    </nav>
    +                    {/* Tabs Controller Ends */}
    +
    +                    {/* Tabs Content Starts */}
    +                    <div
    +                        className="tab-content mt-5"
    +                        id="nav-tabContent"
    +                    >
    +                        {/* Shifts Tab Starts */}
    +                        <div className="tab-pane fade show active" id="nav-shifts" role="tabpanel" aria-labelledby="nav-shifts-tab">
    +                            <Shifts />
    +                        </div>
    +                        {/* Shifts Tab Ends */}
    +
    +                        {/* Hours Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-hours" role="tabpanel" aria-labelledby="nav-hours-tab">
    +                            <Hours />
    +                        </div>
    +                        {/* Hours Tab Ends */}
    +
    +                        {/* Employees Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-employees" role="tabpanel" aria-labelledby="nav-employees-tab">
    +                            <Employees />
    +                        </div>
    +                        {/* Employees Tab Ends */}
    +                    </div>
    +                    {/* Tabs Content Ends */}
    +                </div>
    +        </>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:43:55 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Hours.js.html b/out/Hours.js.html new file mode 100644 index 0000000..d2bbe7c --- /dev/null +++ b/out/Hours.js.html @@ -0,0 +1,150 @@ + + + + + JSDoc: Source: Hours.js + + + + + + + + + + +
    + +

    Source: Hours.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { PieChart } from "../../charts";
    +import { HoursData } from "./HoursData";
    +
    +// Colors
    +const purple = "#5c00b8";
    +const lightTeal = "#00ebeb";
    +const darkTeal = "#009e9e";
    +const lightPink = "#eb00eb";
    +const darkPink = "#b200b2";
    +
    +/**
    + * @function
    + * @description Creates a page with a table and a graph of the hours worked and their trends.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires PieChart
    + * @requires HoursData
    + */
    +export const Hours = () => {
    +
    +    // Data for pie chart -------------------------------------------------------------------------------------
    +
    +    // Preparing data to be passed to the chart component
    +    const hoursData = {
    +        labels: HoursData.map((data) => data.description),
    +        datasets: [{
    +            label: "Hours",
    +            data: HoursData.map((data) => data.qty),
    +            backgroundColor: [
    +                purple, darkPink, lightPink, lightTeal, darkTeal
    +            ],
    +        }]
    +    }
    +
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <div className="row d-flex d-inline-flex justify-content-between w-100">
    +            {/* Left Column Starts */}
    +            <div className="col">
    +                <div className="row d-flex flex-column justify-content-between">
    +                    {/* Hours Table Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-4">Hours Table</h2>
    +
    +                        <table className="table table-bordered text-center">
    +                            <thead className="thead-dark">
    +                                {/* Table columns */}
    +                                <tr>
    +                                    <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                </tr>
    +                            </thead>
    +
    +                            <tbody>
    +                                {/* Mapping the data to diplay it as table rows */}
    +                                {HoursData.map((item, i) => {
    +                                    return item.description === "Available Hours" ? (
    +                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) : (
    +                                        <tr key={i}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    )
    +                                })}
    +                            </tbody>
    +                        </table>
    +                    </div>
    +                    {/* Hours Table Ends */}
    +                </div>
    +            </div>
    +            {/* Left Column Ends */}
    +
    +            {/* Right Column Starts */}
    +            <div className="col">
    +                <div className="row">
    +                    {/* Hours Chart Starts*/}
    +                    <div className="col text-center">
    +                        <h2 className="mb-3">Hours Chart</h2>
    +
    +                        <div style={{ height: '16rem' }} className="mx-auto">
    +                            <PieChart pieData={hoursData} />
    +                        </div>
    +                    </div>
    +                    {/* Hours Chart Ends*/}
    +                </div>
    +            </div>
    +            {/* Right Column Ends */}
    +        </div>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:28:27 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/HoursData.js.html b/out/HoursData.js.html new file mode 100644 index 0000000..f967ff7 --- /dev/null +++ b/out/HoursData.js.html @@ -0,0 +1,163 @@ + + + + + JSDoc: Source: HoursData.js + + + + + + + + + + +
    + +

    Source: HoursData.js

    + + + + + + +
    +
    +
    import moment from "moment"
    +import { DummyDataShifts } from "./DummyDataShifts"
    +//import { DummyDataWorkers } from "./DummyDataWorkers"
    +
    +/**
    + * @function
    + * @description Takes in list a of shifts and generates data of the hours worked for Hours.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires DummyDataShifts
    + * @returns Array of objects
    + */
    +const HoursDataGenerator = () => {
    +
    +  // First array
    +  let completeList = [];
    +
    +  // Gathering both clock-ins and clock-outs
    +  DummyDataShifts.forEach((shift) => {
    +    shift.clockin.forEach((clockIn) => {
    +      if (shift.clockin.length > 0) {
    +        completeList.push({
    +          starting_at: shift.starting_at,
    +          started_at: clockIn.started_at,
    +          ending_at: shift.ending_at,
    +          ended_at: clockIn.ended_at
    +        });
    +      }
    +    });
    +  });
    +
    +  // Adding all the scheduled hours from each shift
    +  let scheduledHours = completeList.reduce(
    +    (total, { starting_at, ending_at }) =>
    +      total +
    +      moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(),
    +    0
    +  );
    +
    +  // Formatting scheduled hours
    +  let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(0);
    +
    +  // Adding all the worked hours from each shift
    +  let workedHours = completeList.reduce(
    +    (total, { started_at, ended_at }) =>
    +      total +
    +      moment.duration(moment(ended_at).diff(moment(started_at))).asHours(),
    +    0
    +  );
    +
    +  // Formatting worked hours
    +  let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(0);
    +
    +  // THIS IS A PLACEHOLDER, we double the hours worked to mimic available hours
    +  let availableHours = (workedHoursFormatted * 2).toString()
    +
    +  // Creating object for worked hours
    +  let workedHoursObj = {
    +    description: "Hours Worked",
    +    qty: workedHoursFormatted
    +  };
    +
    +  // Calculating extra worked hours
    +  let extraHours = workedHoursFormatted - scheduledHoursFormatted;
    +
    +  // Creating object for extra worked hours
    +  let extraHoursObj = {
    +    description: "Extra Hours Worked",
    +    qty: extraHours
    +  };
    +
    +  // Creating object for long breaks
    +  let longBreaksObj = {
    +    description: "Long Breaks",
    +    qty: "10"
    +  };
    +
    +  // Generate semi-final list
    +  let semiFinalList = [];
    +
    +  // Adding object of worked hours to semi-final list
    +  semiFinalList.push(workedHoursObj);
    +  semiFinalList.push(extraHoursObj);
    +  semiFinalList.push(longBreaksObj);
    +
    +  // Generating final array with percentages as new properties
    +  let finalList = semiFinalList.map(({ description, qty }) => ({
    +    description,
    +    qty,
    +    pct: ((qty * 100) / availableHours).toFixed(0)
    +  }));
    +
    +  // Generating object of available hours
    +  let availableHoursObj = {
    +    description: "Available Hours",
    +    qty: availableHours,
    +    pct: "100"
    +  };
    +
    +  // Adding object of available hours to final list
    +  finalList.push(availableHoursObj);
    +
    +  // Adding IDs to each object in the array
    +  finalList.forEach((item, i) => {
    +    item.id = i + 1;
    +  });
    +
    +  // Returning the final array
    +  return finalList
    +}
    +
    +// Exporting the final array
    +export const HoursData = HoursDataGenerator()
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:32:39 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Metrics.html b/out/Metrics.html new file mode 100644 index 0000000..9e96fbf --- /dev/null +++ b/out/Metrics.html @@ -0,0 +1,187 @@ + + + + + JSDoc: Class: Metrics + + + + + + + + + + +
    + +

    Class: Metrics

    + + + + + + +
    + +
    + +

    Metrics()

    + + +
    + +
    +
    + + + + + + +

    new Metrics()

    + + + + + + +
    + Creates the view for Metrics page, which renders tabs with different modules being called inside each one. +
    + + + + + + + + + + + + + +
    + + + + +
    Since:
    +
    • 09.28.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:Punctuality
    • + +
    • module:Ratings
    • + +
    • module:GeneralStats
    • + +
    • module:Queue
    • +
    + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:00:24 GMT+0000 (Coordinated Universal Time) +
    + + + + + \ No newline at end of file diff --git a/out/Punctuality.js.html b/out/Punctuality.js.html new file mode 100644 index 0000000..cdc4a5a --- /dev/null +++ b/out/Punctuality.js.html @@ -0,0 +1,222 @@ + + + + + JSDoc: Source: Punctuality.js + + + + + + + + + + +
    + +

    Source: Punctuality.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { PieChart } from '../charts';
    +import { ClockInsData, ClockOutsData } from "./PunctualityData";
    +
    +// Colors
    +const purple = "#5c00b8";
    +const lightTeal = "#00ebeb";
    +const darkTeal = "#009e9e";
    +const green = "#06ff05";
    +
    +/**
    + * @function
    + * @description Creates a page with 2 tables and 2 graphs of all the clock-in and clock-out trends.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires PieChart
    + * @requires ClockInsData
    + * @requires ClockOutsData
    + */
    +export const Punctuality = () => {
    +
    +    // Data for pie charts -------------------------------------------------------------------------------------
    +
    +    // Clock-Ins
    +
    +    // Taking out the "Totals" from the chart view
    +    let dataCI = ClockInsData.filter((item) => { return item.description !== "Total Clock-Ins" }) // Taking out the "Totals" from the chart view
    +
    +    // Preparing data to be passed to the chart component
    +    const clockInsData = {
    +        labels: dataCI.map((data) => data.description),
    +        datasets: [{
    +            label: "Clock-Ins",
    +            data: dataCI.map((data) => data.qty),
    +            backgroundColor: [
    +                purple, lightTeal
    +            ],
    +        }]
    +    }
    +
    +    // Clock-Outs
    +
    +    // Taking out the "Totals" from the chart view
    +    let dataCO = ClockOutsData.filter((item) => { return item.description !== "Total Clock-Outs" })
    +
    +    // Preparing data to be passed to the chart component
    +    const clockOutsData = {
    +        labels: dataCO.map((data) => data.description),
    +        datasets: [{
    +            label: "Clock-Outs",
    +            data: dataCO.map((data) => data.qty),
    +            backgroundColor: [
    +                green, darkTeal, purple
    +            ],
    +        }]
    +    }
    +
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <div className="row d-flex d-inline-flex justify-content-between w-100">
    +            {/* Left Column Starts */}
    +            <div className="col">
    +                <div className="row d-flex flex-column justify-content-between mb-5">
    +                    {/* Clock-Ins Table Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-4">Clock-Ins Table</h2>
    +
    +                        <table className="table table-bordered text-center">
    +                            <thead className="thead-dark">
    +                                {/* Table columns */}
    +                                <tr>
    +                                    <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                </tr>
    +                            </thead>
    +
    +                            <tbody>
    +                                 {/* Mapping the data to diplay it as table rows */}
    +                                {ClockInsData.map((item, i) => {
    +                                    return item.description === "Total Clock-Ins" ? (
    +                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) : (
    +                                        <tr key={i}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    )
    +                                })}
    +                            </tbody>
    +                        </table>
    +                    </div>
    +                    {/* Clock-Ins Table Ends */}
    +                </div>
    +
    +                <div className="row d-flex flex-column justify-content-between">
    +                    {/* Clock-Outs Table Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-4">Clock-Outs Table</h2>
    +
    +                        <table className="table table-bordered text-center">
    +                            <thead className="thead-dark">
    +                                {/* Table columns */}
    +                                <tr>
    +                                    <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                </tr>
    +                            </thead>
    +
    +                            <tbody>
    +                                {/* Mapping the data to diplay it as table rows */}
    +                                {ClockOutsData.map((item, i) => {
    +                                    return item.description === "Total Clock-Outs" ? (
    +                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) : (
    +                                        <tr key={i}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    )
    +                                })}
    +                            </tbody>
    +                        </table>
    +                    </div>
    +                    {/* Clock-Outs Table Ends */}
    +                </div>
    +            </div>
    +            {/* Left Column Ends */}
    +
    +            {/* Right Column Starts */}
    +            <div className="col">
    +                <div className="row mb-5">
    +                    {/* Clock-Ins Chart Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-3">Clock-Ins Chart</h2>
    +
    +                        <div style={{ height: '13.905rem' }}>
    +                            <PieChart pieData={clockInsData} />
    +                        </div>
    +                    </div>
    +                    {/* Clock-Ins Chart Ends */}
    +                </div>
    +
    +                <div className="row mt-5">
    +                    {/* Clock-Outs Chart Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-3">Clock-Outs Chart</h2>
    +
    +                        <div style={{ height: '13.905rem' }}>
    +                            <PieChart pieData={clockOutsData} />
    +                        </div>
    +                    </div>
    +                    {/* Clock-Outs Chart Ends */}
    +                </div>
    +            </div>
    +            {/* Right Column Ends */}
    +        </div>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:37:24 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/PunctualityData.js.html b/out/PunctualityData.js.html new file mode 100644 index 0000000..c7303dd --- /dev/null +++ b/out/PunctualityData.js.html @@ -0,0 +1,259 @@ + + + + + JSDoc: Source: PunctualityData.js + + + + + + + + + + +
    + +

    Source: PunctualityData.js

    + + + + + + +
    +
    +
    import { DummyDataShifts } from "./DummyDataShifts"
    +import moment from "moment";
    +
    +// Clock-Ins Data ------------------------------------------------------------------
    +
    +/**
    + * @function
    + * @description Takes in list a of shifts and generates data of clock-in trends for Punctuality.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @requires DummyDataShifts
    + * @returns Array of objects
    + */
    +const ClockInsDataGenerator = () => {
    +
    +    // Clock-ins array
    +    let clockIns = [];
    +
    +    // Sorting the shifts
    +    DummyDataShifts.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            if (shift.clockin.length > 0) {
    +                // Gathering the clock-ins
    +                clockIns.push({
    +                    starting_at: shift.starting_at,
    +                    started_at: clockIn.started_at
    +                });
    +            }
    +        });
    +    });
    +
    +    // Setting up counters
    +    let earlyClockins = 0;
    +    let lateClockins = 0;
    +
    +    // Increasing clock-in counters
    +    clockIns.forEach((shift) => {
    +        let start1 = moment(shift.starting_at);
    +        let start2 = moment(shift.started_at);
    +
    +        let startDiff = moment.duration(start2.diff(start1)).asMinutes();
    +
    +        if (startDiff >= 15) {
    +            lateClockins++;
    +        } else if (startDiff <= -30) {
    +            earlyClockins++;
    +        }
    +    });
    +
    +    // Creating clock-in objects
    +    let earlyClockinsObj = {
    +        description: "Early Clock-Ins",
    +        qty: earlyClockins
    +    };
    +    let lateClockinsObj = {
    +        description: "Late Clock-Ins",
    +        qty: lateClockins
    +    };
    +
    +    // Setting up base array for all objects
    +    let cleanedClockIns = [];
    +
    +    // Pushing objects to base array
    +    cleanedClockIns.push(earlyClockinsObj);
    +    cleanedClockIns.push(lateClockinsObj);
    +
    +    // Setting up totals
    +    let totalClockIns = clockIns.length;
    +
    +    // Generating percentages as new properties
    +    let pctClockIns = cleanedClockIns.map(({ description, qty }) => ({
    +        description,
    +        qty,
    +        pct: ((qty * 100) / totalClockIns).toFixed(0)
    +    }));
    +
    +    // Setting up object for totals
    +    let totalClockInsObj = {
    +        description: "Total Clock-Ins",
    +        qty: totalClockIns,
    +        pct: "100"
    +    };
    +
    +    // Adding totals to the array with percentages
    +    pctClockIns.push(totalClockInsObj);
    +
    +    // Addind IDs to each object
    +    pctClockIns.forEach((item, i) => {
    +        item.id = i + 1;
    +    });
    +
    +    // Returning clock-ins array
    +    return pctClockIns
    +}
    +
    +// Exporting clock-ins array
    +export const ClockInsData = ClockInsDataGenerator()
    +
    +
    +// Clock-Outs Data -----------------------------------------------------------------
    +
    +/**
    + * @function
    + * @description Takes in list a of shifts and generates data of clock-out trends for Punctuality.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @requires DummyDataShifts
    + * @returns Array of objects
    + */
    +const ClockOutsDataGenerator = () => {
    +
    +    // Clock-outs array
    +    let clockOuts = [];
    +
    +    // Sorting the shifts
    +    DummyDataShifts.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            if (shift.clockin.length > 0) {
    +                // Gathering the clock-outs
    +                clockOuts.push({
    +                    ending_at: shift.ending_at,
    +                    ended_at: clockIn.ended_at,
    +                    automatically_closed: clockIn.automatically_closed
    +                });
    +            }
    +        });
    +    });
    +
    +    // Setting up counters
    +    let earlyClockouts = 0;
    +    let lateClockouts = 0;
    +    let forgotClockOut= 0;
    +
    +    // Increasing clock-out counters
    +    clockOuts.forEach((shift) => {
    +        let end1 = moment(shift.ending_at);
    +        let end2 = moment(shift.ended_at);
    +
    +        let endDiff = moment.duration(end2.diff(end1)).asMinutes();
    +
    +        if (endDiff >= 30) {
    +            lateClockouts++;
    +        } else if (endDiff <= -30) {
    +            earlyClockouts++;
    +        } 
    +    });
    +
    +    // Increasing forgotClockOut counter
    +    clockOuts.forEach((shift) => {
    +        if (shift.automatically_closed === true) {
    +            forgotClockOut++;
    +        }
    +    });
    +
    +    // Creating clock-out objects
    +    let earlyClockoutsObj = {
    +        description: "Early Clock-Outs",
    +        qty: earlyClockouts
    +    };
    +    let lateClockoutsObj = {
    +        description: "Late Clock-Outs",
    +        qty: lateClockouts
    +    };
    +    let forgotClockOutObj = {
    +        description: "Forgotten Clock-Outs",
    +        qty: forgotClockOut
    +    };
    +
    +    // Setting up base array for all objects
    +    let cleanedClockOuts = [];
    +
    +    // Pushing objects to base array
    +    cleanedClockOuts.push(earlyClockoutsObj);
    +    cleanedClockOuts.push(lateClockoutsObj);
    +    cleanedClockOuts.push(forgotClockOutObj);
    +
    +    // Setting up totals
    +    let totalClockOuts = clockOuts.length;
    +
    +    // Generating percentages as new properties 
    +    let pctClockOuts = cleanedClockOuts.map(({ description, qty }) => ({
    +        description,
    +        qty,
    +        pct: ((qty * 100) / totalClockOuts).toFixed(0)
    +    }));
    +
    +    // Setting up object for totals
    +    let totalClockOutsObj = {
    +        description: "Total Clock-Outs",
    +        qty: totalClockOuts,
    +        pct: "100"
    +    };
    +
    +    // Adding totals to the array with percentages
    +    pctClockOuts.push(totalClockOutsObj);
    +
    +    // Addind IDs to each object
    +    pctClockOuts.forEach((item, i) => {
    +        item.id = i + 1;
    +    });
    +
    +    // Returning clock-outs array
    +    return pctClockOuts
    +}
    +
    +// Exporting clock-outs array
    +export const ClockOutsData = ClockOutsDataGenerator()
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:33:32 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Queue.js.html b/out/Queue.js.html new file mode 100644 index 0000000..18ca1d8 --- /dev/null +++ b/out/Queue.js.html @@ -0,0 +1,196 @@ + + + + + JSDoc: Source: Queue.js + + + + + + + + + + +
    + +

    Source: Queue.js

    + + + + + + +
    +
    +
    import React, { useState, useEffect } from "react";
    +import "react-datepicker/dist/react-datepicker.css";
    +import DatePicker from "react-datepicker";
    +import moment from "moment";
    +
    +import { WorkerStats } from "./WorkerStats";
    +import { Button } from "../../../components/index";
    +import { DummyDataShifts } from "./DummyDataShifts";
    +import { DummyDataWorkers } from "./DummyDataWorkers";
    +
    +/**
    + * @function
    + * @description Creates a page with a DatePicker and table of all employees with their worked/scheduled hours.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @requires DatePicker
    + * @requires WorkerStats
    + * @requires Button
    + * @requires DummyDataShifts
    + * @requires DummyDataWorkers
    + * @param {object} props - Contains an array of all shifts, and an array of all workers
    + */
    +export const Queue = (props) => {
    +
    +  // Setting up my variables ---------------------------------------------------------------------------------
    +
    +  // Date selected through the DatePicker
    +  const [selectedDate, setSelectedDate] = useState(new Date());
    +
    +  // Monday of X week (default: current week)
    +  const [start, setStart] = useState(
    +    moment().startOf("isoWeek").format("YYYY-MM-DD")
    +  );
    +  
    +  // Sunday of X week (default: current week)
    +  const [end, setEnd] = useState(
    +    moment().endOf("isoWeek").format("YYYY-MM-DD")
    +  );
    +
    +  //const allShifts = props.shifts;
    +  const allShifts = DummyDataShifts;
    +
    +  //const workers = props.workers;
    +  const workers = DummyDataWorkers
    +
    +  // Function that filters shifts based on the Monday and Sunday of the selected date --------------------------
    +
    +  const filterShifts = () => {
    +    let filteredShifts = [];
    +
    +    allShifts.forEach((shift) => {
    +      let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD");
    +      let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD");
    +
    +      if (
    +        shiftStart >= start &&
    +        shiftStart <= end &&
    +        shiftEnd >= start &&
    +        shiftEnd <= end
    +      ) {
    +        filteredShifts.push(shift);
    +      }
    +    });
    +
    +    return filteredShifts;
    +  };
    +
    +  // UseEffect to update Mondays and Sundays when a new date is selected --------------------------------------
    +
    +  useEffect(() => {
    +    let formattedStart = moment(selectedDate)
    +      .startOf("isoWeek")
    +      .format("YYYY-MM-DD");
    +
    +    setStart(formattedStart);
    +
    +    let formattedEnd = moment(selectedDate)
    +      .endOf("isoWeek")
    +      .format("YYYY-MM-DD");
    +
    +    setEnd(formattedEnd);
    +  }, [selectedDate]);
    +
    +  // Return ----------------------------------------------------------------------------------------------------
    +
    +  return (
    +    <div className="row d-flex flex-column justify-content-center mx-auto w-100">
    +      <h2 className="mx-auto">Table of Employee Hours</h2>
    +      {/* Top Column Starts */}
    +      <div className="col d-flex d-inline-flex justify-content-center my-4 p-3 px-4" style={{ background: "rgba(107, 107, 107, 0.15)" }}>
    +        {/* Controls for the Week Starts */}
    +        {/* Col 1 */}
    +        <div className="col-4 p-0 pt-2">
    +          <h3 className="m-0">{`Week of ${start} - ${end}`}</h3>
    +        </div>
    +
    +        {/* Col 2 */}
    +        <div className="col-4 p-0 pt-2 d-flex d-inline-flex justify-content-center">
    +          <div className="mr-3">
    +            <h3 className="m-0">Select a day of the desired week: </h3>
    +          </div>
    +
    +          <div>
    +            <DatePicker
    +              selected={selectedDate}
    +              onChange={(date) => setSelectedDate(date)}
    +            />
    +          </div>
    +        </div>
    +
    +        {/* Col 3 */}
    +        <div className="col-4 p-0 d-flex justify-content-end">
    +          <div>
    +            <Button className="btn btn-dark bg-dark mr-3" onClick={() => alert("No functionality yet")}>
    +              <h6 className="m-0">Placeholder 1</h6>
    +            </Button>
    +          </div>
    +
    +          <div>
    +            <Button className="btn btn-dark bg-dark" onClick={() => alert("No functionality yet")}>
    +              <h6 className="m-0">Placeholder 2</h6>
    +            </Button>
    +          </div>
    +        </div>
    +        {/* Controls for the Week Ends */}
    +      </div>
    +      {/* Top Column Ends */}
    +
    +      {/* Bottom Column Starts */}
    +      {/* Table of Employees Starts */}
    +      <div className="col rounded mt-2 m-0 p-0 text-center mx-auto">
    +        {workers?.map((singleWorker, i) => {
    +          return (
    +            <WorkerStats
    +              key={i}
    +              worker={singleWorker}
    +              shifts={filterShifts()}
    +            />)
    +        })}
    +      </div>
    +      {/* Table of Employees Ends */}
    +      {/* Bottom Column Ends */}
    +    </div>
    +  );
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:58:02 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Ratings.js.html b/out/Ratings.js.html new file mode 100644 index 0000000..b1e5155 --- /dev/null +++ b/out/Ratings.js.html @@ -0,0 +1,162 @@ + + + + + JSDoc: Source: Ratings.js + + + + + + + + + + +
    + +

    Source: Ratings.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { PieChart } from '../charts';
    +import { RatingsData } from "./RatingsData";
    +
    +// Colors
    +const purple = "#5c00b8";
    +const lightTeal = "#00ebeb";
    +const darkTeal = "#009e9e";
    +const green = "#06ff05";
    +const lightPink = "#eb00eb";
    +const darkPink = "#b200b2";
    +
    +/**
    + * @function
    + * @description Creates a view of the number of ratings per worker.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires PieChart
    + * @requires RatingsData
    + * @returns A table and a chart displaying the ratings data
    + */
    +export const Ratings = () => {
    +
    +    // Data for pie chart -------------------------------------------------------------------------------------
    +
    +    // Taking out the "Totals" from the chart view
    +    let pieData = RatingsData.filter((item) => { return item.rating !== "Total Employees" })
    +
    +    // Preparing data to be passed to the chart component
    +    const ratingsData = {
    +        labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }),
    +        datasets: [{
    +            label: "Employee Ratings",
    +            data: pieData.map((data) => data.qty),
    +            backgroundColor: [
    +                green, darkTeal, lightPink,
    +                purple, lightTeal, darkPink
    +            ],
    +        }]
    +    }
    +
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <div className="row d-flex d-inline-flex justify-content-between w-100">
    +            {/* Left Column Starts */}
    +            <div className="col">
    +                <div className="row d-flex flex-column justify-content-between">
    +                    {/* Ratings Table Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-4">Employee Ratings Table</h2>
    +
    +                        <table className="table table-bordered text-center">
    +                            <thead className="thead-dark">
    +                                {/* Table columns */}
    +                                <tr>
    +                                    <th scope="col"><h3 className="m-0">Star Rating</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                </tr>
    +                            </thead>
    +
    +                            <tbody>
    +                                {/* Mapping the data to diplay it as table rows */}
    +                                {RatingsData.map((item, i) => {
    +                                    return item.rating === null ? (
    +                                        <tr key={i}>
    +                                            <th scope="row"><h3 className="m-0">Unavailable Rating</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) : item.rating === "Total Employees" ? (
    +                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                            <th scope="row"><h3 className="m-0">{item.rating}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) : (
    +                                        <tr key={i}>
    +                                            <th scope="row"><h3 className="m-0">{`${item.rating} Star Employees`}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    )
    +                                })}
    +                            </tbody>
    +                        </table>
    +                    </div>
    +                    {/* Ratings Table Ends */}
    +                </div>
    +            </div>
    +            {/* Left Column Ends */}
    +
    +            {/* Right Column Starts */}
    +            <div className="col">
    +                <div className="row">
    +                    {/* Ratings Chart Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-3">Employee Ratings Chart</h2>
    +
    +                        <div style={{ height: '26.05rem' }}>
    +                            <PieChart pieData={ratingsData} />
    +                        </div>
    +                    </div>
    +                    {/* Ratings Chart Ends */}
    +                </div>
    +            </div>
    +            {/* Right Column Ends */}
    +        </div>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:25:21 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/RatingsData.js.html b/out/RatingsData.js.html new file mode 100644 index 0000000..26a84fe --- /dev/null +++ b/out/RatingsData.js.html @@ -0,0 +1,127 @@ + + + + + JSDoc: Source: RatingsData.js + + + + + + + + + + +
    + +

    Source: RatingsData.js

    + + + + + + +
    +
    +
    import { DummyDataWorkers } from "./DummyDataWorkers";
    +
    +/**
    + * @function
    + * @description Takes in list a of employees and generates data of rating trends for Ratings.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires DummyDataWorkers
    + * @returns Array of objects
    + */
    +const RatingDataGenerator = () => {
    +
    +  // First array
    +  let ratings = [];
    +
    +  // Gathering all the ratings from each worker
    +  DummyDataWorkers.forEach((worker) => {
    +    ratings.push(worker.rating);
    +  });
    +
    +  // Function to make an array of objects with all the ratings
    +  const findOccurrences = (arr = []) => {
    +    const results = [];
    +
    +    arr.forEach((item) => {
    +      const index = results.findIndex((obj) => {
    +        return obj["rating"] === item;
    +      });
    +      if (index === -1) {
    +        results.push({
    +          rating: item,
    +          qty: 1
    +        });
    +      } else {
    +        results[index]["qty"]++;
    +      }
    +    });
    +
    +    return results;
    +  };
    +
    +  // Generating array
    +  let array = findOccurrences(ratings);
    +
    +  // Calculating total of the quantities
    +  let total = array.reduce((s, { qty }) => s + qty, 0);
    +
    +  // Generating and adding percentages as new properties
    +  let ratingsArray = array.map(({ rating, qty }) => ({
    +    rating,
    +    qty,
    +    pct: ((qty * 100) / total).toFixed(0)
    +  }));
    +
    +  // Organizing objects by numerical order of the "rating" property
    +  let sortedArray = ratingsArray.sort((a, b) => (a.rating - b.rating))
    +
    +  // Moving the first object ("Unavailable Rating") to the last position of the array
    +  sortedArray.push(sortedArray.shift());
    +
    +  // Generating an object with the totals
    +  let totalsObj = { rating: "Total Employees", qty: total, pct: "100"}
    +
    +  // Adding object with the totals to array
    +  sortedArray.push(totalsObj)
    +
    +  // Adding id's to each object in the array
    +  sortedArray.forEach((item, i) => {
    +    item.id = i + 1;
    +  });
    +
    +  // Returning the array
    +  return sortedArray
    +};
    +
    +// Exporting the array
    +export const RatingsData = RatingDataGenerator()
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:25:53 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Shifts.js.html b/out/Shifts.js.html new file mode 100644 index 0000000..5ef4a2e --- /dev/null +++ b/out/Shifts.js.html @@ -0,0 +1,153 @@ + + + + + JSDoc: Source: Shifts.js + + + + + + + + + + +
    + +

    Source: Shifts.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { BarChart } from "../../charts";
    +import { ShiftsData } from "./ShiftsData";
    +
    +// Colors
    +const purple = "#5c00b8";
    +const lightTeal = "#00ebeb";
    +const darkTeal = "#009e9e";
    +const lightPink = "#eb00eb";
    +const darkPink = "#b200b2";
    +
    +/**
    + * @function
    + * @description Creates a page with a table and a graph of all the shift statuses.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires ShiftsData
    + * @requires BarChart
    + */
    +export const Shifts = () => {
    +
    +    // Data for bar chart -------------------------------------------------------------------------------------
    +
    +    // Taking out the "Totals" from the chart vie
    +    let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted "}) // Taking out the "Totals" from the chart view
    +
    +    // Preparing data to be passed to the chart component
    +    const shiftsData = {
    +        labels: barData.map((data) => data.description),
    +        datasets: [{
    +            label: "Shifts",
    +            data: barData.map((data) => data.qty),
    +            backgroundColor: [
    +                purple, darkPink, lightPink, lightTeal, darkTeal
    +            ],
    +        }]
    +    }
    +
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <div className="row d-flex d-inline-flex justify-content-between" style={{ width: "100%" }}>
    +            {/* Left Column Starts */}
    +            <div className="col">
    +                <div className="row d-flex flex-column justify-content-between">
    +                    {/* Shifts Table Starts */}
    +                    <div className="col text-center">
    +                        <h2 className="mb-4">Shifts Table</h2>
    +
    +                        <table className="table table-bordered text-center">
    +                            <thead className="thead-dark">
    +                                {/* Table columns */}
    +                                <tr>
    +                                    <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                </tr>
    +                            </thead>
    +
    +                            <tbody>
    +                                {/* Mapping the data to diplay it as table rows */}
    +                                {ShiftsData.map((item, i) => {
    +                                    return item.description === "Total Shifts Posted" ? (
    +                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    ) : (
    +                                        <tr key={i}>
    +                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                            <td><h3 className="m-0">{item.qty}</h3></td>
    +                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                        </tr>
    +                                    )
    +                                })}
    +                            </tbody>
    +                        </table>
    +                    </div>
    +                    {/* Shifts Table Ends */}
    +                </div>
    +            </div>
    +            {/* Left Column Ends */}
    +
    +            {/* Right Column Starts */}
    +            <div className="col">
    +                <div className="row">
    +                    {/* Shifts Chart Starts*/}
    +                    <div className="col text-center">
    +                        <h2 className="mb-3">Shifts Chart</h2>
    +
    +                        <div style={{ height: '20rem' }}>
    +                            <BarChart barData={shiftsData} />
    +                        </div>
    +                    </div>
    +                    {/* Shifts Chart Ends*/}
    +                </div>
    +            </div>
    +            {/* Right Column Ends */}
    +        </div>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:41:46 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/ShiftsData.js.html b/out/ShiftsData.js.html new file mode 100644 index 0000000..9e5ed4a --- /dev/null +++ b/out/ShiftsData.js.html @@ -0,0 +1,149 @@ + + + + + JSDoc: Source: ShiftsData.js + + + + + + + + + + +
    + +

    Source: ShiftsData.js

    + + + + + + +
    +
    +
    import { DummyData } from "./DummyData";
    +
    +/**
    + * @function
    + * @description Takes in list a of shifts and generates data of shift statuses for Shifts.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires DummyData
    + * @returns Array of objects
    + */
    +const ShiftsDataGenerator = () => {
    +
    +    // First array
    +    let shiftsList = [];
    +
    +    // Gathering all the existing shifts
    +    DummyData.forEach((shift) => {
    +        shiftsList.push({
    +            status: shift.status,
    +            clockin: shift.clockin,
    +            employees: shift.employees
    +        });
    +    });
    +
    +    // Setting up counters
    +    let open = 0
    +    let filled = 0
    +    let completed = 0
    +    let rejected = 0
    +    let total = shiftsList.length
    +
    +    // Adding values to each counter based on certain shift conditions
    +    shiftsList.forEach((item) => {
    +        if (item.status === "EXPIRED" && item.clockin.length === 0 && item.employees.length === 0) {
    +            rejected++
    +        } else if (item.status === "FILLED") {
    +            filled++
    +        } else if (item.status === "COMPLETED") {
    +            completed++
    +        } else if (item.status === "OPEN") {
    +            open++
    +        }
    +    })
    +
    +    // Creating shift objects
    +    let openShifts = {
    +        description: "Open Shifts",
    +        qty: open
    +    }
    +    let filledShifts = {
    +        description: "Filled Shifts",
    +        qty: filled
    +    }
    +    let workedShifts = {
    +        description: "Worked Shifts",
    +        qty: completed
    +    }
    +    let rejectedShifts = {
    +        description: "Rejected Shifts",
    +        qty: rejected
    +    }
    +
    +    // Setting up base array for all shift objects
    +    let cleanedArray = []
    +
    +    // Pushing shift objects to base array
    +    cleanedArray.push(openShifts)
    +    cleanedArray.push(filledShifts)
    +    cleanedArray.push(workedShifts)
    +    cleanedArray.push(rejectedShifts)
    +
    +    // Generating final array with percentages as new properties
    +    let percentagesArray = cleanedArray.map(({ description, qty }) => ({
    +        description,
    +        qty,
    +        pct: ((qty * 100) / total).toFixed(0)
    +    }));
    +
    +    // Generating the object of total shifts
    +    let totalShifs = {
    +        description: "Total Shifts Posted",
    +        qty: total,
    +        pct: "100"
    +    }
    +
    +    // Adding the object of total shifts to the final array
    +    percentagesArray.push(totalShifs)
    +
    +    // Adding id's to each object in the final array
    +    percentagesArray.forEach((item, i) => {
    +        item.id = i + 1;
    +    });
    +
    +    // Returning the final array
    +    return percentagesArray
    +};
    +
    +// Exporting the final array
    +export const ShiftsData = ShiftsDataGenerator()
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:39:39 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/WorkerStats.js.html b/out/WorkerStats.js.html new file mode 100644 index 0000000..d53f457 --- /dev/null +++ b/out/WorkerStats.js.html @@ -0,0 +1,175 @@ + + + + + JSDoc: Source: WorkerStats.js + + + + + + + + + + +
    + +

    Source: WorkerStats.js

    + + + + + + +
    +
    +
    import React from "react";
    +import moment from "moment";
    +import Avatar from "../../../components/avatar/Avatar";
    +import { Button, Theme } from "../../../components/index";
    +
    +// This is needed to render the button "Invite to Shift"
    +const allowLevels = window.location.search != "";
    +
    +/**
    + * @function
    + * @description Creates a table of all employees with their worked/scheduled hours for Queue.js
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @requires Avatar
    + * @requires Button
    + * @requires Theme
    + */
    +export const WorkerStats = (props) => {
    +    
    +    // Setting up my variables ---------------------------------------------------------------------------------
    +
    +    const worker = props.worker;
    +    const shifts = props.shifts;
    +
    +    // Scheduled Hours -----------------------------------------------------------------------------------------
    +
    +    // Filtering scheduled Shifts
    +    const workerShiftsScheduled = [];
    +
    +    shifts.forEach((shift) => {
    +        shift.employees.forEach((employee) => {
    +            if (employee === worker.id) {
    +                workerShiftsScheduled.push(shift);
    +            }
    +        });
    +    });
    +
    +    // Calculating total scheduled hours
    +    let scheduledHours = workerShiftsScheduled.reduce(
    +        (total, { starting_at, ending_at }) =>
    +            total +
    +            moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(),
    +        0
    +    );
    +
    +    let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(2);
    +
    +    // Worked Hours ------------------------------------------------------------------------------------------------
    +
    +    // Filtering worked Shifts
    +    const workerShiftsClockedIn = [];
    +
    +    workerShiftsScheduled.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            if (shift.clockin.length > 0) {
    +                workerShiftsClockedIn.push(clockIn);
    +            }
    +        });
    +    });
    +
    +    // Calculating total worked hours
    +    let workedHours = workerShiftsClockedIn.reduce(
    +        (total, { started_at, ended_at }) =>
    +            total +
    +            moment.duration(moment(ended_at).diff(moment(started_at))).asHours(),
    +        0
    +    );
    +
    +    let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(2);
    +
    +    // Return ------------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <>
    +            <Theme.Consumer>
    +                {({ bar }) => (
    +                    <div className="row d-flex border d-inline-flex justify-content-between py-4 w-100">
    +                        {/* Employee Image/Name/Rating Starts */}
    +                        <div className="col p-0 d-flex justify-content-center">
    +                            <div className="my-auto mr-2">
    +                                <Avatar url={worker.user.profile.picture} />
    +                            </div>
    +                            <div className="ms-2 text-start my-auto d-flex flex-column">
    +                                <h5 className="m-0 p-0" align="left">{`${worker.user.first_name} ${worker.user.last_name}`}</h5>
    +                                <h5 className="m-0 p-0" align="left">{worker.rating == null ?  "No rating available" : worker.rating > 1 ? `Rating: ${worker.rating} stars` : `Rating: ${worker.rating} star`}</h5>
    +                            </div>
    +                        </div>
    +                        {/* Employee Image/Name/Rating Ends */}
    +
    +                        {/* Scheduled Hours Starts */}
    +                        <div className="col p-0 my-auto d-flex justify-content-center">
    +                            <h3 className="m-0 p-0">{`Scheduled Hours: ${scheduledHoursFormatted}`}</h3>
    +                        </div>
    +                        {/* Scheduled Hours Ends */}
    +
    +                        {/* Worked Hours Starts */}
    +                        <div className="col p-0 my-auto d-flex justify-content-center">
    +
    +                            <h3 className="m-0 p-0">{`Worked Hours: ${workedHoursFormatted}`}</h3>
    +                        </div>
    +                        {/* Worked Hours Ends */}
    +
    +                        {/* Invite Button Starts */}
    +                        <div className="col p-0 my-auto d-flex justify-content-center">
    +                            <Button
    +                                className="btn btn-dark bg-dark"
    +                                onClick={() =>
    +                                    bar.show({
    +                                        slug: "invite_talent_to_shift",
    +                                        data: worker,
    +                                        allowLevels,
    +                                    })
    +                                }
    +                            >
    +                                <h5 className="m-0">Invite to Shift</h5>
    +                            </Button>
    +                        </div>
    +                        {/* Invite Button Ends */}
    +                    </div>
    +                )}
    +            </Theme.Consumer>
    +        </>
    +    );
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:59:18 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/charts.js.html b/out/charts.js.html new file mode 100644 index 0000000..146895d --- /dev/null +++ b/out/charts.js.html @@ -0,0 +1,112 @@ + + + + + JSDoc: Source: charts.js + + + + + + + + + + +
    + +

    Source: charts.js

    + + + + + + +
    +
    +
    import React from 'react';
    +import { Pie, Bar } from 'react-chartjs-2';
    +import { Chart as ChartJS } from 'chart.js/auto';
    +
    +/**
    + * @function
    + * @description Creates a pie chart with the data passed as an argument
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires Pie
    + * @param {object} pieData - Object with data like colors, labels, and values for the chart.
    + */
    +export const PieChart = ({ pieData }) => {
    +  return (
    +    <Pie
    +      data={pieData}
    +      options={{
    +        responsive: true,
    +        maintainAspectRatio: false,
    +        cutout: "0%",
    +        animation: {
    +          animateScale: true,
    +          animateRotate: true,
    +        }
    +      }}
    +    />
    +  )
    +}
    +
    +/**
    + * @function
    + * @description Creates a bar chart with the data passed as an argument
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires Bar
    + * @param {object} barData - Object with data like colors, labels, and values for the chart.
    + */
    +export const BarChart = ({ barData }) => {
    +  
    +  let delayed;
    +  return (
    +    <Bar
    +      data={barData}
    +      options={{
    +        responsive: true,
    +        maintainAspectRatio: false,
    +        animation: {
    +          onComplete: () => {
    +            delayed = true;
    +          },
    +          delay: (context) => {
    +            let delay = 0;
    +            if (context.type === 'data' && context.mode === 'default' && !delayed) {
    +              delay = context.dataIndex * 150 + context.datasetIndex * 100;
    +            }
    +            return delay;
    +          },
    +        }
    +      }}
    +    />
    +  )
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:01:22 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/fonts/OpenSans-Bold-webfont.eot b/out/fonts/OpenSans-Bold-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..5d20d916338a5890a033952e2e07ba7380f5a7d3 GIT binary patch literal 19544 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 literal 0 HcmV?d00001 diff --git a/out/fonts/OpenSans-Bold-webfont.svg b/out/fonts/OpenSans-Bold-webfont.svg new file mode 100644 index 0000000..3ed7be4 --- /dev/null +++ b/out/fonts/OpenSans-Bold-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/out/fonts/OpenSans-Bold-webfont.woff b/out/fonts/OpenSans-Bold-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..1205787b0ed50db71ebd4f8a7f85d106721ff258 GIT binary patch literal 22432 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 ig3W + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/out/fonts/OpenSans-BoldItalic-webfont.woff b/out/fonts/OpenSans-BoldItalic-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..ed760c0628b6a0026041f5b8bba466a0471fd2e0 GIT binary patch literal 23048 zcmZsC18^o?(C!;28{4*R+s4MWZQHh;Y;4=c#x^##ar4z*x9Z-izo(w+)6aCD(=$_Z zX6j6jo4lA900{6SnvekG|8#os|JeVv|9=q^Q;`J#fXaVZod00t3i={0A}aR74gJ`7 zKOg|Y0f34t$SePFhX4R*5dZ*{OY4X(B(AI~1OR}C|M&#_pgi9&JXc8RP9o zCqzMe3Yr->{lvnt{P_Im`yUX@tUXMBI355%Xb=E!j7Ku=7Be?7Fa`h=e|7`@^JN2q zNM$nrA%D34Y{DOqz)gX6ncFzK|8VL*d58l5AYC78bV=5BMn8Va`9JwB|6sTJe)7h~ z!2M@j)gNB~!G8cD1g^0)urc}J(tmu`e{wXneoxZ2w{vm^0Dk`f==G;RK#AwolD(tJ zPprld0P+9fUWDkv&BX90XU!iI0RA7$qZDg@G|+#<6mQ||e|p?V^1t&9m|nvC<-TsD zZ>+Ds3t|Wbj-YR-4?5r`Fa>K0Vs)C0=rl@wBnb6$3m7g`Wx>q@OwcRc|qNB1RiTqRPjk40m`>okPgoi z7dS*Y4q2`g!l>hOy06fc+9v6Eoc^Bant68A?-*ANQPSjW&McCZwRfceo&USTE3TsF zV!K(Z*^BSfvX+f9H15vBW5@3vXRW)^s}|{t5QwH~yqMk*{YrFU zo<>IWq;M^9Y2JAp2qWSXsT02we>!!h_J!7wsndeI5Sm`s_viR)r`-V&s`T zaj5gTFFZ8_Oq$<%2v&_t&yiq=QvIEAXe6SdA zWvRE^^lP+cKI-}%@;a~<;qcC7G;VZG^acTJ_Yfy!7y(Gw9^?bE9bkufhzI(F06NGX zkM716l5T($BNVX>xX2!LL?5Rn;e>0`Kg&L=U2+TRD|Ek8iX0sHwP&%i&9L8uvvQ!+#oM76!r_a=e)O7m(xw&MRA z3C&UC|JhItHxRrsT^etqCp0vGQV7>U=W*t}$JGv>uMT!NT2}bGWJBnUA27}AGDFZ8NTF9aqncC&d0JZP%Y@>QrB?5Q z_K@$PWQY2GpsQpGl+dZ1{Y|3!K5$bNAoV&((NGvxC@K&WjtRwrWyPA_Wrvt9s9X}< z5i)y^JU8iyz?tr{3Q#i-q7_;HMVY&S$&JB{*@{R#-ImjgKOjB_#yxi5MsL{u1>x=& z`eC+*V{CvhGYGZ~+b`M%I>-S0TOXxn03&*k)v^PQeV1%gb8~N_t8tMHEM!Y7f(cEP zCej@jSCzZMRpqjLU9p*870u2S!7iv(W04^&6b=>_i;Kni)NFpXFi(^}$`|ev=Z*8B z@$_WwhY;ou^X0ROt>SDr9?K;DuhHaael#~xkRnVSrUqAyqp8uFFZN-VzM$+%KCc-ZuK_eIE<7>q+f4dbi+fD&ZB( zj+r@^&>CjvoYyd9!_)P-<^n6>mCzbk9qbM^XPf_pK-nsRE*qrDiBuJR@7UCJpEleC zj@9bBE#c}>$xSnj?1e|4G44-lHrE1QV1V{54a>kY^-TXazYv#A<(J46i1%&N`Z-fW z=o-2Drm_T0+G2kC+-QFEZqkUBT6(ZH zJ7sg>s6ruvN~2TA?o`&bQVsh7<#~l{o5f+HJ72B4DD9E1MJ%hndA-oJyHKu5317d~ zva_x6kx{Kk*Qavj5m&9uh^xjE^KpQSy9mSZ+NcPl&2sj)9bhJjFCq@8KG>oTy zCYX66LJ&$2@SqmBDY!hiUnsl&de|N-2y*=MFNrsRDif1CFrW|-3-xC%{VxYo2gCKj zzKOm8uBfH-fB;22A!a>e2_r*&ef|AoeIrv714BcPzP^X;06{`5igKVKn9$h%8JI|z zu3nARzh5Pc4E7I9tP~6kGZ5qTL-n>GO21&H0R9VbSpU<%zP_oyJ|?&rIKm6aA!Fbx z4Gg@06I2jzJSnj8Ez=_7hZ&18jA@lV*NAh}zgXb3!0^E2!0f=pz|6p&z?8r!p)R3_ z0W8rH2$)`tuWyK~QRu~9KshyJO_ZRZfS`~dc*P`=C_1qM`oVYYH~u&OgWvx5z<19# z##hhh`*Hs`gg73KxBYJaHbf_$wP)R3e;|Ynd?cRw4u9!Q;v?ze5ebMG8+eK2H}Fug z5wcR#W3*JYWwsXAC%9O-8M+$VE4*CYZN47gFQ5Rye!>ESJ;VgXdB%E&Tc`*ao6DT7 zB(o{4F7xq*lF8pSy3MASZ!Xwuw%Z*h8?l#OuGd?m3dxC?9=(PJf=^KmG@-E?FvBn~ z|Bm!mjusiJR+rMVAq-EJ`6MhYb9`UM9_IBsVXYqM`A2SQ?o_Ir3bC0)c zzMzobOXZBxnar*(gh%C2m>6(sfh|D+hfpbd|6O|lu;@1!J;8JrY!HwvNNF69L4L&8 z?Oxa_v+rJ@yQuHpfE!G0bub{NWOyC-^&C|Tw*@hjlrECkq&ZS(Fc(Z_hy3}mU|I|Y z3#wsPLLD5)YEYeG8s{T!{CADsW6GwJ2V(x}=h(F1)Z7I&a`Ee#tjbpHZpRY|vw2$f}2 zv&^KAg4qK_ZNJIa3DzaLStOCve68I~}-g8XzRAkS}a_qwDwT-xMnZsKiQ% zzgHxPe7D4z{#1c6nV?Wpxxf!yUX^XMg#Rm8xOGviWKmw4b`hJm zj*At?74aBjlOsPWooNZ9Uy)I)b{(E>0m)#rrzB;b_dx=3PM653giv3q|5a?eh>vQP z7Y9O;xJIGs@#|92j-b)hjGnG^>(W^CIPT$I;CO1rw(H*h^a1OJUj4g^GQ0g$QG04y zR03aWOMWP#co8NFlkdzuyb}g-Vp>qUO#wWQXsUqv?@Sddi!Qd2UEAz$DcN($IWhd< zXXR5jB8@!`Xsl}SeQUhV8ml9|AkB)c?$rcN+zJ#2zq~xR91U`q`=<2Tx4Wrly8Ksm z0iFYhyHZN+^;Q|hLZ1y3lXWm<6?60gs>?*mQu8!fMp>_A6xMY&8Af5R8HwrdwDwuz zXU?tzLiWqfG1+%K$AzA_%_e*T_G%&9b#TW8T>)Fon9U|?F_#NS7TCWtWmJLr7RHZ* zZPit*z#6Q7A4(#|JHrXjE0J+smY1pgP`;NU=yAqMB66=9w6&4lEVf#1_Wrr*ZD}%} zg;tNS$0mo}GWfM?gfG`u0)SIkK_I0sugMWquUza;;`=*b z?sHDcE-CrsGP3y4&%SrWB_UsX@oaHS(yr)eiln*(ZKm^nXhq7nd=_<;q?{dwyBry7 zHHR`54@4E7Q%icpwzwXkld7t1NBy;Y^+vigUa=Q8pIqjJaSf)F^#~7JQK6KAZ%!_{ zKnQC^F~PH+2!hrO9cqJffw#08`d8qIfelR)>sVWZn<`^P{kY9w@xI-t)c;bCju9#Re_#nObA9moX}WoqcxA-!1}z;W9`uP zc{qW%j*xt$VY|$Zwm{x;aQ*0q2ry%WtE4AzeISmIc!|Pw;&A=Mj%+|ZBw@SMj*y0q zkVuZUAUtGYyHK2! zp2ml7!EedX(x2NzN`7_Wi}*2{=?Z@P14@1^;fs1SM2{J_C9Wh#Dg92{^Zj{O2G!<2 z4@w{a(Dye0-hI8q2g+M{c==^&lU8fN+NPt`BC)ijX|B|ULK?e6fRdZG1X~@Y01c>~ zhUiBEi5iHn%1?zK2n`+jQ9)5rJ^1kM2(Q|@%1(ukUh~^O^D?}WN}*4mzh4xw61mNe zvpL_hnFT>p2t`VvkP*X3l0Rw0KEbaOUV`zR@=!zM!LRoqyF_LkA8Z18y2X)@Hz2P2 zAAD-p3|zUVVwn<&I&ak4HPYSp{xE&{fD$NLk770`nS-kclU+>*Q8VOSp1y>5; zpbw|CXPYA1O%KUcf}EhbI~5gK7c#TL)_y#Lv~kt>9xpaPHJ*#f^qI98q3izXbyayS zwh~uby|(9WOT(~+;{2opRo(?2bpqh0-0}!@4M`UQ;O$N4lOs6OfqcWg&inU_Pf`a{ zgtT_e3=8>Dbisv$`1+#6$Ia7w7xRfTC6qzQ31d|3P@s@F0-*+6Jgb(lq&#FKK!G|) z$w|rj(qGzEF}P{AEa5&Q#)lGx3zfP4#m(*o;a8^J|HYTQdCTr9z(KC`Hryt^-?8Rp ze69i$hqY?eA00@#ho9wUye5|x@UHwIU_b7JKQxun?0O8kj@_fZV|_STb=v{rZoOHc+!qCfjV;Zkb_qA=-_6S zKAQpGcT^$5h1sRecx*c>mk+PqMA~`HO}P2a;d;@;Q9w&EnRiSgRKg@^v=neAAyAEL zHrzabSS;$g3IabN4k30G3x@MfPz@9%Ld^!uB{EPf2qEF5>KS04U5z4%q*v0OT^18D-B&>}xj)vtyT4!)G9l!j6#^TK$yv>mia47tLAiRPM2xD% zU~ryzJ=g8NooRN`)$FoF=JdI(&hzjqC?ncPQ=GqUwR)!SFw>c=WUpQy(u?P2V>P(V zE!E&YoL%8}xYo1Z=Y`+#01_$e{_F@+E}P-wX|`BLzWWmczj;sNYU>Snsj51FFlfBt zn_CNcD?;mCswU3fl?sn*fZ{Ph$)#2dzXrGxsuJuA0L2QcVo)FnMilgj2y`FT%tni! z5x4z%5Jmyly)Pa$F3$8{VX6}sZ0r;NF2EWfQID#d1yU(n41YR);}~(AQ9=BoHXh%g z{(5_?pT*-~IMWOJzANq86WBrYvEMfNZGFY zs1H4Eht{uE_sedtLE~-@{f6Uuic#1KJfS@(69V0nJZ{XkxFhNeXWx{Id<1{E3A0~j zi$U^mD!b4$JyNj=+VFtt=u;akdVx5KUkQ;RSYJIkC7rpN48a4JEvrgS=@onI&+6^Q zho9|0eOn}oQTNAeU*jG1o!4EOIz%0p>G-=Obl+b_b$~V5QhD2yn1KQE9?qEceiz!` zJFhTrpl_z@cUkT3F6Nue550W?>UwnY$=<;_o#J3U%8mrYh*?b0Y&dE+Y1_);(OjAf z6H+#Y75GDXv?h5*zy>(Jjz6??sPb z%`S2C_ya~8noV}eC85{gypkb*!JUSPLAb&1-OWrlzTqf|@i87Akkf1XJLvb`7;2Ya zVMi;pFQoixdJ55~T+Pq0gw>$vc)|s|ddKTwR3;OV0dkZr>p`4OHsr_1+hGb~qzG0E z6JzmTu;N*HBTE*GM?z(*f1yOj3Yj2+XAL7@Bc98lo{kVhjD?Ty-<3lCAu>=>1W=L0 z)FymW`MIBdk~>ULyH{&7U(Jy1)ZMzt;SGFJJwtiloYQlF_U zE?`ct>qnSj`U+bqs~ z|1p!Xb*J;8G^tYWGhNT|dk6WoO&qQIW#gk>J?~tH%WdUfmT8)roR{6l+zBOoLabeY z>%l6Yx+1@yo`?=kfL*G{fb#iNk!OBR038c(+P_E7%55x@7XN4q{Svtu1DBV&pnERw ze8!wY&|@pJdhZI3x-xzWo1K6h#~Fb^K+$P775>QQp;6loe>=o_?W@o3PR=m&VJFI3 zEW|qNAQqCspB;RBSq_vEh=G6p_Sz8=uy}$vk4P`K0$j)2V4`5eXP9d=VnJdeP#l85 z?<2+F=Hgpna+v{c$GgAAvVHvYsPlY`z7hy$FV>!9&a3`8WyU4yc{g;o1a3U_L(6Nc zXIu^;{@&_#pFkPKaMbJ}$crrg(xR<$z#NmIkrF2TGK6B23&Ko7lsgPxg~_7+mA#6v zsigG>6g;ao5LG-tFwTi&v}Cxf9T%-k+Gw)rc-SC~9i0bj!cSLpF{2xG5tVsC+3Ubz z^Z7K9x_gOv=i^VX9q&t@vfKB=?hgM5y-ss+llM(kqQlEer#okCFZq}E#VG%kyVJAY z;p|mv$)_899>+(h1?+TmkCA@d4&W_Pr`wqB)L04CjP3qdhCcK&`3B=obaw`5b3WQX zVkhX8ogNEefr2l;-#I@3ms1gK;`zjMNSy>vq*|m;#lfEqylK#N^m1S<G3?Aw%$&3zL*kWi-?brROGT&FMbs;JioU-C7UJyB{c;t>*teO^7=z5UzcS zp~2=c8neIhdga#m`2A}&i8{~guD{5JyUu6HL&<0MMbd>hRabEfDbmC7MQv`&wI%E9 z?}d&bUK%y3N;d0MpuItD+)RcNo3EOWsH)anm3=3cSu9;`yQ_%6j)gvCbBr||qJ}~j ze<R2=eQnzxh7*Pp_9EwiMQLJOh;M~#tw@s4Dt>zE(4$|$i+7b)~a1;%8I!@ z{LN7Eu)jSP_@o10^_5_BnoH)99~2f=08KKPEa1%~AhaMkv^;u=sCn1Y3{0E=j&GOK zX0RkoDE_1sjs{0lTb-?rX8OprtX-K_4kWlC^6H)gHK&hcY{q4TC?DR#o(tg=LJx)K zAJHPZLven5vWAbvzE-PubE#{M9f0#gZ*1OKh)DvsdMWQ0?-}W&@2v8daUh)ww$t8M$X4Bj<7G z=n;NC5PM}b_zq$E8(c=yJMS`hd8Z^welnP?*WV)+$R{BN^2t}X2`mGxMRy}&u8)V? zTo9`8fh;&}>S(AP%{yTTJd6`TENrTL%ku&gT`hwiw1M|w!+k%C`z)tL;YW}Mojv;c z&PJ=*6p>`Ny<28MT_QtD- zasNV79|0HKtUMS#%1qUbHnQ){Iu(*P{XrdvdM;koh117$)f-Zv4}LnPMS3k=%Vk5n zwQ9ZV>v8aU?2a9Oe}q1*i_=VS((-G}^|ksWZEa+JKM@fnA@QJaR3OqyB|!51w|-9HFGAl{3p zzK~6lbs>Ty3nstVI|YtM_me=3;lVnX=GxsF^{YkKn#o2*DK@YSUW2;+h~@)_$w z#8=Q-Cofe38R8AhB0CJ6d$S92nz+U|_qTlCGqeuHXG`x$YJA{a(|F8`_;B=ov7I&ZYbk=|c;`t0=1pFG$|K za&BUxEP|uv7ysIIM)BNw`(?UDm8N~!=UEH7IKvWx9P@-ZbzKOQQVL3o?% z7o;eYt;BX%Ism(ZY#ModCy)<8SVyHoFVIbWUfwf!!!F)ovjm4ClP*RvCs$;^SFTln zvS$y~mDs<&-ZA6TW|Zi6J_>r%_mJJdV6xKy3XJj(eLk)QGJvy+x+u%}h@4)>gXQoQ z1%&3rLHk}&)FH-{0_I%n8$iIGg&Tlis3&gCf@lJWNR%4Er7Jg8|cUkWE#{QR4-_nKH|J_ z?xS~6K2jIltSd|HY3yHD!)U%j6QkT92#h*BOut4GiWXaxFxP%DAqDKyhk~SOUAltA~h@O`$T*nTXn(z%?#p z0A~U!v2^PQ!;%sS*fUSTH$P7Ur1sPDQoj|8Zf1g=dY$&qJiOdKwZ0eunqM4QR*b8p zk)2Sa^Ezgn8Az$@g~?ZPy+2VGsDINM4`tjQtl>Tz32u8OPj>iz1w#dh1{4Wxc>TOUrO?*}98%mR z^xx5mn?D?0BZG9XsDUC=%#pZDrW0L8vt|3_EGCS$=tl!lkB{JGB9>7CNIgLv*OC}o z#lJZ0J&&;C^xT}huT(2*JO53UCV81{`Dv+2OP&{E-&`5>E*ecXBU3Yn!IgKNO`oUY zW_T?>f~yc8CwMKV;lDVTc|8n! z=}sSG3aJM_)W`0tQ}mHZYMD@ksZgsc5M*p|rPe+8Vfvn*&NKvtOCv?Fyr;FLm<=!uciogELSZrm%?FfNUpXNE^- zNN3b>>DhQ`=Co{z*a!Na0j}&UT0eqC84SX&4Ek3g5nSnZqC(=DW%JsU+MHFoL)73e z?E^4B{H9FU0Us0CTpoNkwodJBdj6!4B+(cOu@&+C_En4$RAws&(iwP~L^l!S+|IhM zZ2`Ed)5$KU*RN}2PP_NiM|S%6U}*rD`^C(dDLDSXl=lxK{<3m*7@VSPDx zAQ?EWnk9be`0RD!$vAh!H_g*dl-d4zpBV|~4VVQvJs2GVV>}d#JCr^;GiIQKg2-Y+ zO7Oy}A)^x-=@w+rD;zj(lGd1 zHM61_qgG%9S89sAz19Zv0*B3Rl=szm^pjKZ8}5~O^tMf_qI=olr#9Sy9@ZbnMFn}7 zc0Q7^zT}HUWUpJ@wV<@!Bn|Sz1@gns{g61i3nk+R7K&(gx;*8Q8qlwOr`OgbOR*x+NcSvi=3kf3{M-HV5QEUY-AlL#7bC0#nRDbx!7w_1sl7DU)=@UWWd=P^gzzjmT1^w0nIs7xG!xVhWnTFDgSwu02 z;N5US5YR2BM9d)yLL*m?9-L*fl%9cvq|msx$FP3wCwXqNItTM8zHU#^3BBD-AE}H* zQIlwK6wSDPp9s0PYL9Kr=&iM0A88x2RoHy5x%kIR%T%t*viGS(r!0p8tzq^dyhuZ) zo~Go8Ft!kOFj}=ad&;ti5Jni+vrt~SN#@7-qxbriDS~J7Dg1O?zlw%lC?L`)m=gIuG*}f+t_3S=fkJ?I?zH@uC?%*!y-Qb?mh8;EMf?aX(5Ec(ve8!3jb&;dS+`U|%|yMWMwmY4^!5hfk7>zg2U3iu7V z5AqBxrY(VHjI7aPiaHx{)7c=#x);KI_Nv4=?JoIOWYp7Z2@73NW)e62 zKSOs;C^VQX4;6O#H~6IRlw65^l}3fGaM79&cqMZxozHQC!dcXb4GvgGykc;) ziTBBL4N``*gm)=;`N=H%$WQiuTy~B+Z04H5k9!@ubsLK<6nEBc58HUPxmYftULyB= z>{8^uY!Ztt~E@3*HqNkT3%(Yk0acX-^?ICTIk@MtMRTL0jeLH5{>!z zo0leHM)!UrXEuGthl8Tq^Cn+4&Ngu;mH+eRUG<#$ycC|cYGtA5Ex$N-(W`W+Xe{YS{2AoZA*RK{9*x%LxUj| zJ;t7-HlsW7N|_Zl+nFwUh2_tSCtO?E@F zrO|wp<-QLtW0=_(Y-v>Cfo!kFjH8i3rK-h}Vbb3+Sd0}d4pEX{r{dY9GFd9WS?o7e z(JwzxL=JaMuz_44eN|boc4y(EE`)KQ`&4yN1G}(nm@x$z?UYIJJfW*4kmLxW}-0fuq?70&{BH%2f5T;75!P~6r?4+%8kV+n9?f&&kI8L zJgY!*8JTeTO8qv&%?*g;6P?dn3V#q>i^!+~PRhnI``A9zLq5{Yp;b(ym1Zm`Wv|0H zIZIjq*g=Q^j(pH?OQ2woJVku;cn}$q!nBc8a?8M~`U(1!jMejV2)N>xnIcvu1ixaQ zx%Z%8YYP~;%nOu`7z>H_$0<-sg$Ze?X$X7HP^=TYua=)I4JLsO&I^Cl6g8{SKRmPc|2c(cD2P_!cm`Dy|{-z z^d00=qpl1InE@ZwfTS0ahKE&&j_n?mNr|Jy%Q=!e^4Zpo4XJ$2rzL44~~m zH_$)lL8F6k){%h}a;?wIK^(4F%g%>AovQ0t(1s&}m{Ayy+Yp;=2+YiLs>N-$KRixg zPu};nI=p{}^X^5%&f|Y!_1LS%_EW#x-&daGOVsnc(u0USn1Aah;>_`~1C zWE_tAO*XZ@J_ysmYiwRro}9@!jBrnck5$wmSb-XQ!I&QFi>?0=o-K*b$7uX`0>i@+`naTD%f&K7w6037<<-<9QDEj;`ME#HzREV;^pb z5Lgpr2A+w}-sR0dcqClOX$@#Hm*dgU-TB zw6o9HDy{dOmhabp!<0q7?dJ;{8Tb7-`eY!Ra(%o=)4v&30;B?Wv-~Zi%f9y(zZXM9 zL{!yO6di@)(FJIqiHIVpVEGhI*bRy~I`fr?9Z0yPTbwNR?sPcEbP|uUo`1VV5s_fO zsC9q*vDi^=5KPdHzS!;MgRzn;;l$tuUqS71b_Lzc2*?|)E)0q2fU)`qpz4I*Rb z0b@Sw&71Kq{|LA|DE%#`vFQBv>DHp>vJyC8@U=eNc)R&|O~UC{i_b;SNKjaQer=ZWC7yHO7VvmsHFX(?QK zmek=hW{5o(x|9!F6l~8M&b=T6ht^DKHB2<4^hhvMsMU34SGh8JqYPXvgS=ma-irTu zcKc4gBd`LF7Oe+uwV+4DkFu75|CiWj_5*?M!s!4;8_QkB*M#-SSd!y>+rW5W_>w_y zBa#~POS*5nxgRHO99GnI5_YXhaarFsyofnKm5#{2Y>n(se_+t$y+gC8a8KH^mjlhL zbeDO>Ue7Qp7o&m51LXy5cFKkb?n;}P>@IcP<}rD0gNg58QhJ}8+YbBHp!UbY@TG{; zPLvegu5bRJQ8e867ijeuA=Y}Dz8DZ|zg@lhRPrRJI8VMjG7enV3p7vD<8SYh?8nNF zzeqQMElGq!gxCE>z~UhJWJfuGPSl4Tu9j~Cd9oV`BEj$!K=8VE%2Z$XQe=y3XyQ*wmGKaRLph%}V{R-jNOWPfAGiP(Ub&CjSAI`jmEYsvK#u&^5bV6WnoNm(IwX(U z$CL2V%9Jk4QN}spFauZ}N6Cb=3DQ?{x`>ZC-x0~kBQ<)?EKGOw>kaAcm#<3!)S&0i zuDmR=CPMgXraH}J9>~%o@N%FzBzFTP1yzhTCUHll!ZjPVsHXjae?>T2!4L*e-Wqbe z@-agyqV7c)@aPADZm}j?ZDgJj>(aAoCyQ}$G~;ishN{KVRJiHiLknW^By>IJGD|Ai zZTBUhnr0AQkON`}$!o#)6ARpU)5* z6vT2E=19pho$_bUc{$`15g(*fP_Z4zX2N_*NSj`Nbu6B}2n?!$*rME*6FpDPn#$J1 z&_r}w%_Jq*It+!w6kI+7nb4=3h6D@O)|$sawMWL zVTP8tv_jc|kjzy>sjg)I=<}6|^_~2+jU6`C<~G;#$E9d&khI6njI?bZITYs0HI&i}WM}>hg!CLjLJkIPUnEigK41yjH%zvgDU@?#hL_@+$jRJfs`-()Vl4T| zS4iVvN^y{ErlObu4-}A(LZVkVMON@8N=G3a??~tWdct+nPjoq5}$hg!pS45LCtF) zv(pMojCI4~V1~w>gLEGGn5LeW<4ph8e63k`ZjytXd+%{)Lw(Y$w~~*3@uqLj_vm!q z$4Pb36u+$~)AgZSL*|!|A5fcIewiTc$nbi#DY7hI@~MF6n-LADax5?n8JPSXQ9ILb z&m9&u-J|=Li$#c=H4Dxx<1};9cJaHHzuqkhM+GmI{SC0v*qSvK>Kz^$zF&!t(zR_J z&7R{OC1B!aG1&ZOSF4OpW8w?7>Kz6aJ$7sBCN7O;Y;+o}L+3hOw&RD#^G>F5nC$Od zs|q)5ptxg{Q38mQunToi3o$im+grR*=#isn(`c-=X@2@)b*r%z14F5uM$hDbgCCj{vJ&>Gc`%xw{}B4 z)zf9Kw9Im++;*JiwyCSRcgf?iPh1!0^_6w-7jMa02)2W-wXk6S(8VG3+pM7jvhLvb z41CciCIYAEdo_!aKLCT-vORl7p(l`bZYzVk&x$Nom(g@Us;kFyYObOF;PkKweCa~LLG*mauLL%P$?};u>>-OqG8_dgB2}y=SW!wZ6j8KN zF-64b$xG;1d!g(KQNq7-Ote@^*n*efBEvL+hqQ_``Ob)W(*s^kI;kH#`-LIen?_EV zCoE=k_)Xrg{qo;RY4#YHg48@+4{hP=WHp~(V1%f#q9e_fD3lr{o1Dml9^ag!W(IOiQ|2wR z#l&CU!+5I>6FoE`*>Ohz8D5x55Cz$&ANT5=r2U!sc)D}WJ(yV*51E;zc#p2UUHXg= zx!ebDBQ^`R7&M+Oylt|=BS*$Df)e(dFmfhFz^wI9l&2for{FzkH8g-ELdmKP&H^-Lmk5e~1Ir`yjaA@$OFcI}G&6CE#je3kV{2939#MSegRv>2Vb* zlb@U&H1Ie-4>|#FwFjy~JUpRC_%GaV`k@OI0jxgp(ot% z!9=pYP#g;Ef|Ik&VrHMZEX(Any{=viW52OgYlLD;9K|Zbih>}$70bKV+22enhc#>S ze*WTeBc?oT2zHCdMtz0g?DH=J^%6@Csmn!FbLOS2GAUl@cJ9ET`|Vk0B0`G+hgm0s zv&<-D1D?j(?XtoD6s?`qX}nfWeIJ=xy8K&yda@#eZ||ziwmXfV-@+H^TD|k*>u`02 zIuyp)3m;D*Jy*A(-2o1Dy!Iuji_)EKiu&ZcUya$5&AI?bW!FhWaP?qFFGeS7)YMPg zDVqPc*8tCM3=x{u+{bR^F8!!MR^p08!P4Jdd=}~S(D7s-GDx0)@MJ9fMhTZXyj&;6 zd68@cZ@5kDCwtb))qmd0H{=FlpY-}8Oi=}VQRc%48QV}D=L`BYo<8xsz|lIg(EUqc z=co9+GuF*>+2R!=aGe-itUH2}1u0#;z71`DpB*%r_Z&uuCw6zSEfJY7j<3SnL5*se z_6NHKqj3iZ=&jd$r;-#J^t}{n;Arqg*^Pp>C(m`vLC(F{oAy}S4paM$s~?&AiWn}e zN+}ZxGAlOa(Lkf4NfN0XA^e1o(G z9XPsKq;)N{#nBd66~-eKM>ml0Zk&=rWJe)5YoVedaZ=j8VU)l;+(hL*80k%Oic1#@ zOpuxV!H|SI(H*9IkXm(ZM$)p94)YI%^|JJy%i8H~jh~Y5!HYDPEs;3smY9D?^1$9F z2`Y9`LRGsIG~)|`2eTJ6cY_cHg=NI`xb$$7tncXa=$e}ChOA6=Ff&-c94eApg5VQ? z_=16~W0f?Z{m5NXUlW*&Kwm`XN6gWwuavp9?vmN!cNuZg7$3*aZF>&}%hIY7dvD~i zerr!(cO9*=W?j3VufQIkn9h2fiFt;GD1cob%(ykrYhLtc&r(tJy65qnuv$Y9(~eFw z>J7VE7GFBf__)L5G6_Fva_JGZ@GB!CQHQW8Q*m*lX7HR^-JuDUvNXLofqFf{reUmx zk-dzHVLfICBQuis(+Nlfkk)9_l43#9#)p>q=<6rCRIN%Xz_aZ$#>z*?7x1bp(hQd; zhy-L$wURQ;1CMr^i3jQOo> z@gtZPnDwU29-FtDj1|W2Op2FHR z^Z#uIegliC+GeadJ!dZ&Q6FrR?b}Jx@l-5fZ{#C~7 z$|spyp7Oph3CBn=CiEjHh7b{1^MrkMKi8ghk+{?IU2vi%WysV2kt9FK^R;1$4n*-I$1~r38X-l0?G~NP2G|am^2P~N~s>muuWkb^+ z7z<+k_1(Z)xa!qceVdeOI7xf^Yz{`j-f5IZkx;_5xa79SI_wu?p*KY=LFAdb8`WFp zztAG@4I`bficVsJD|R|R>RrRzj7~FR@uE1GxB8(-z#s|B!?^Jflof|$mDI_jDH1I+ zTk~z9l5|}a(&h3*)UCgY#Lqw20^g0>l#-AwE>qM797yDlA>NA~@+rEqYjf}Td1g!tP_GoXd+zFY?SK%EG`yPdAmTZLeC+Ij!Ywh7K60tA!+sXNYJK**Gznb|@)s*T7(w6b{07+ZW-B{79Ihsl59`en&e6Hd{KLlamAnw_xId{v{ zH*xno|0~!?M-QjK_(-!uD2f4~6F3*>HT+ou(It#a4AA{4qpK7Ic}h=B^EV20cX1Iy zz^isqULkj_v6IGtMRljeJpj_h?+q)v!nKL9*7qMGAjotufsqoFw05Y94SO`3_l@-S zs|kmCna@u;3nc6+P#KIAK^YLoTD#<^>IC+-C|j<0veL-mt8JE^MXQE_ezKv}IOufp zSXr)4;D4Ke`@PXB(JWKy;%Yy>VeF9>SZ1#5%sR*{zO>W}lAH3ix78v0ke^DT2%TND zfDu0SZ)l_jmLip8BiwxQp6LGpWu@mChO+#$R~@J^(Zt%&|Lp#R*8Nyu(+<}F2H)ebZno`MP} zuDWr@@h+ueFM~^s6H=tDNJq(de`k-b z58VegjfB3Hv)~nwos5Bv4F1Yw4_`2f0_Q+F;(BnWyUV3Cuw3=8<2VzqPHQd+z`e3V zAN}qLv`(Ib_1U%?*c_3Zr*R$Hv7Lr7)n8$v3&ZgK#vIKx;MC*{G(Uw7zZ@j)E$!|F z0qTYp6`zfHMz1yYhG0W6eXVj|8YAIwf|V==$2KL|Sp0`Zxa28Sa$7%<1^FKOsO&J# zDl&O_Nc*IH2V}w9jn5%J@&1G8TZ@mhDTkBJOO0kTs%{gG@8^$nF_3wCKMj;24z_UA zZh>%Z0x&%!OD8thZGOZnL<5!hw1rxEPno8rXz=}j9N5_jOnLe;{-!!MXJMF2BUm(h zw6-=z{M=s0weX9c5N7eO6MXvFo}=Z;vP1cFrYc|G@zZ+bEZguDW`6Gu-_`g)RNHoZ zw#acWc0E5ole`a5um2MZ8T96UX4T57oo^5Mc}z)u`mmykd1ci%mbk|h7LAy3!^I(o zo{v2jwTIvL`Fo5PSTBX>pn9mD?phi1rAuE!XnR|qG>BM(OfEI>!0D~ zG`b)nc|DJoG#cG_2=%+5VNlS}2hkYZefiIup@o3{}WrFodHLsi0yEqEgXgCoTb^7qk>u#vodK z=;18E1^M2b?7o?O($i9XPG4^bn!D^1-wi+N3U62N%kPdKy~;uZ+|Z59A{3+yL8OLs zN2<%XUNBJr7=oB6c;xlZrfxxR7#PFkWly*DAN~!Yoyz(Pd+ra?>9x8Ba49rcuW7gp z4nuoxOt-Or5|04|x&3K&>JoT>H2^%s!+a~m00SX{epp$%DF#e;A16qCCP!c`CGjJ7 zr>O6X!T0HfPw}C*biudk>PGIiGCd*idS1|jxNDJ?=C~q|MjN4NG#Q9q&sWh~t9al^ z9noqL(80(l$SW%t3Zo6YVCXp-8w{br=<-Alu}~B5p_U}%!OLF*f}SNqmk8rhc|I)l_oB| zj^K=Rmoq5=Vn>rMRi7&Iz(QKxW#(Lvg;1Tp#^WTC7(S;Ya^T}Mhs}N2X*2tzxqF#5 zsDnrMnD@|+2-W*1<@8D8L`^TqN}y*nbgy-@0`+?pVO~zA5RZ#4MCeq`(sKKeBE^3H`N@^1Mo3DQC4$2 zYE2X?&WtSW%%AZ|op88uJ>V?p@WaRHes?gx!}K9_cSu)IRt5^-xB!kye^)1*L-LOb zoM2vu3)YHv1w)qvUcR~>pF+>D^|Z+Uh9^_~$;#ypG_>pjz{OHvVu}(cRKT9B5Iqp3 z_NBSSq{IYziUHbRhpDFlqj|=19PEd3gPan^q$GRX$$eA$THM+6j)*jmFPa6UYB5Ep zjsm^qv35~Nq$Ra}!R=T6IO_HB{yXJgU-|gUW#4V8T9qx@rhZ#HyJYUr(ZfbuUpz)g zOwE32$e86@TV{5kE&r9*9scBl$FXT^QStGq%Qv(;=Daj*bVJMDnd2MOz2SE$eiNg` zc*So5B<~7#xdeL`BuQIEodXab185js75H#080ygyl>bL#dhZnS$Hd0;&CKw)QXMJ4 zlv%M^tYkivGh)3zVe&UY(KSyXTA%JrR^n*2_LB8-^=u8YS=?!^RJw^OyyhP87Stk? z=g&!wSK?;~|9C;|UG5#EEeJ9Qb7Bvehkj!)Gg6aS>P2R~!cBv>eZJ?z;X# zd7D0myg=K{@>gEFapor4ayFoL_BAsLmi*&p1AZ$eFb?ZpG|6R}NX84SCq?0}Idq?D zLo#q}TS@{u;85h&6>LZ8G`78Ut)yS_vF`mVew{5!kw=zUSc=f~Z3!{#Ktx%K z2aGThCGbi+C+mGVnU{OAmlfGVE4t)*4%rd9ZeLn*JUc{D7UT|s4>QiaEhppB&-GZ0 z-WH^f))`J8zT0|Qj0nvP*50V#!!34i>*#Zt2YW0eqHiCk)1xefp4PB)QP#_%(1vBn z8kN0*wG8za!Dfkq8H|>Rrub=Uj|O4Q!A2LRPJ48_*rI8_ig& zdDQR)BT6gEZx}g}Z#{nCu)J~qqqNmggXH&@Z`%3mtv`YLed~|QYHK@b#CM}n%U=*Z zX%CX8v;T+gf>1?uV=vSJjhM#h!5of_8NWFJUS}eQ| z^mO3t=VNKRx!RJSN@*(zVx1QBF{z^7j;&OuA(GU2NxZ^deY-x%ZeY@Oo+0-bLkmQF ze`btw=RA8IYSdH0$Nb=Mh}t?Y$oj*hJEagb+r9Bp@etMksN2Fy^M)P|zdVHewu< zV0wV*4n^C~%zGib_{qgDpI(i{J;$22{l+fhIN~MK=|voqUko%4zpi}5h*@`4k~?be zi_N-kmu+-e+30`1{V^V~_u+@bZsy2N=hiLy?&gLoam2e#S0_HOK#i}JGlQBQX9g{> z_zAS1k{uVYo1bZY7{@n+9~aO#z+$m5y@#=nKgl zhuwwj@F#_}Jt1zade+6E;p%nB;WbTC@XH*4oV@O?>u0ZCHD~rc5BU1@Dd^w7k54!} zbH&m*vu?R{W|r5Rm6eyrdgbsSm~WYAge}ejYZLV8L9vOj@5y@b0mXQY3SBRR+T?4VC`MwbjsPVFDPtAs!4@Hhr|alXTo z;`PZ#x_!R@>iQJ||EJIPa?g-$f9^XAa=7Xoy!V@LlyTCEKRr&$432B%-XQht4s!Kg ztzaQ$=Qk`^JwOXEiGmuIc{AFE> z&<2A)z@Go_?|6VE)V7?pf7O1J0U>n#d@Nf-1pPiB<(q(%@*+S2Gy#$#qzJu^fui3B zq#)x^evv}DuBlfB++oOlC7)GM1o(g>Z({I`y?oyggKw0KVepluI_R$=973F&q7&Hr zEeTQp{>`6I` zXN1$Zkop_3v}V=J>N(9ssk<=qv=NGMLJRIu1sTU`aMkD4`dc!tw{ly?V}T!l^X-51T^vr#*)Jaai7yUb97j+; zQpsfr`;iWr(AeiAz<;Ga3^i_c<%^U=q02WhaB71mp4sCA@M`sXy-9Ck-_Jm=u5?QD zd!g9(GZbUmkE~gka@HZ=nT$_ie$hht{(;dEgP$i~Y}xV*$qKyxZKZA0G4-Cx)8JR7 zp~?PwCq{Y~Y@Z3-D>D`azC?$?+EYzir@@@0^c~V80#?n+`fOO+Oq2+^(2<--i(6RM zIWmH^HVHgOJBK5bCS344*gwJBom0$CpSOT^CKjOJ9nZ_BJ~#k3dgQHoBhGZo-_^}n zvH9lrfNd1_uR0!SeA?NZ+lAn?{3HO*@d6w zBq}~*3ppdSvwQkt&=Qsme%^#>gLgdr4Gv_T+D4$|IeO90cu6GmJX^2R2t2h|%Kxc@ z;L+0F6rg{za$n}9o~-j*H5yHf2B-i#W1&TeCVJ<&)9i!*9(clOr;U*DtRK?nYj_?u zn`75=#j`i1u5Z>Uk9*loND{M#5C8^WD))HlFuTZ0tBp|Z)zB+9B+-jcI`2kbG z&S51co_@tjL_g4cZ1wDe$Q~c47!0IGM_g5;NEo?IrqFAHme3^{HH0lPB7z>0(^cxs zL`BM{3>L9EHnIvuM*fMBb^dgWhL;a59z1AZp>mGfCnMd%N>n=UaT|aKST1vq8~tjT zZnwHQLU(D=vZpTJJaNej-|(Hvf5(;&Ei8{PoXRLk7h(H0NZq%?-F8jrZP$!FK2UcpOCh|m%T8%< zcXCIPkVF}c#?tWJ`lB&*eh5?kXnRcmm+irh|J$D65wI!$tIc3nktsS+{UhxWuu$Gq z242Je1EyXT^8k3-V_;-pU|^J-l@}a%J)Ym@D}y`-0|=bGD#-<-|GxPr!ePx`%)rdR z!N3F(1prZ<3$%FJV_;-p;OPC^03;dyzWMu-!J5oks=Z-l#&KQ4xxAmp@@VY#FG~hky1hs z5sx7)QYaoIr_w_S(uPt(@ghBxQY6?+-|QL);^E`%{xkpV&wD%S0<%K^WE4=Ad5q~d zXu1s}&#Cvw z6S6?2$fDh^(q_k=(MKPm#&0dVo~g)Rgz^(5H%DD0DTHo??>h+jy-?M9ALN|%0HHsO z&?9aOC8=KPcdjKle+v8VYivpb4SyUBIWrrwj`uQePE^f&)fu#@t1^vIJ!$5o;9SW^ zEXfH1-KN^-msnC)CXmNwQ@$WjE0*4+Y{bug5`nGDk?k|bwuk2ix{13wjSSZcGKS~g z0?LvyyE1Nyx@tbFmbsLyb4uNfyo|gz^bS?}_J>-GeREEA2cw*A)7wW`3%2DI(oqk+ zw>5$3>b&ivk3*Ot%iQ0QALiIiVvBySJ5}?L^)>YyZ`lw34xV09(TChe-*3ZDFb`%C z1+Pm#+i?zq#5qLVw<>$|q@Tl0>_2vd zi71Ofm_?KsHOewX$sgf}cdP6t`<0AsdSZ6i(K;NOKkn^`^J+zGdboU8zD+60y%#Lyf3 z2g0oWod9^+V_;y=fx;+;CWd>AF-$^CQClgI(W z84_P4JtP-NzL1iTnjp1L+D`h2^cxv288w+hGIwOfWc_4&WFN_~$nBH+AkQUlC7&Qa zP5yxVKLrzoRfsr+ z3vj@7#(RuU89y^&GEp#bFiA3*WOBshm#Lho0}w`-7Mb<|;SDo4vrT3v%q`64SX5Zr zSb6{e;z*U&000010002*07w7@06YK%00IDd0EYl>0003y0iXZ`00DT~om0t5!%!4G zX&i9^7sX|8AtE-WtwM2E2Sh2luv8E?X*yW#AZdyyF8vDEZu|ikeu4gsAK=RK?t87) z)`b%8%X#EIU4IagUwP5fVmMqWU zaXeZDgD0?TeHc82Ol;BMX`IDQ4W1!>Hh30!d*0wz#O;c~Z}99p?4X7!C8FG-j1nA* z&$~|)poJ^kum|OJPOXC{N(vs5l!QS^tWvv2?-u>)jN@RNI3!!0zQk{#2^UAym5Cf2 zQ{O}zTeQ?A^SFktmOwm9JVRO<H%h3t#CwMB1XN_5Q#vNY1vYTJc?p(T&jM zCwlzv>|uFoa;m9DG7;5PgYOWR)U{9#?;m$YB#aQ=UN_@_I`F?xUQfEJ^#y#*z1*aRhIcz>8p3) zO3VhQlap@B(uwZB^R17Feri%##_{Q=Z~Ywgz5d*BiW$6L>;8)6O3hVT>wPiX)a3Xb zY-1OP-2ATmA1dYvtwnBF<%!JKq_wK{1F7EOvmv$=bEmP+Gl@*^Z%cmyEa0)H004N} zZO~P0({T{M@$YS2+qt{rPXGV5>xQ?i#oe93R)MjNjsn98u7Qy72Ekr{;2QJ+2yVei z;2DR9!7Ft1#~YViKDl3Vm-`)2@VhyjUcCG-zJo+bG|?D{!H5YnvBVKi0*NG%ObV%_ zkxmAgWRXn{x#W>g0fiJ%ObMm5qBU)3OFP=rfsS;dGhOIPH@ag%L&u5@J7qX1r-B~z zq!+#ELtpyg#6^E9apPeC0~y3%hA@<23}*x*8O3PEFqUzQX95$M#AK#0m1#_81~aJ= z0|!~lI-d}1+6XksbLS;j^7vyv68Vl`j*#wA{Hl2csfHSc&MaS|^Hk|;@%EGd#IX_77( zk||k|&1ueXo(tUMEa$kz298P&*SO9V$(20GXR8!Qp%h86lt`)3SKHL!*G!?hfW=~| zjOer|RqfK1R;688(V`x1RBB3HX;s>kc4e8;p)6Pao9B$EskxdK=MDHm!J6u-Mt|f< z_e8WS9X5kI6s&J4+-e_>E3!{mU1?R?%zwYF>-rx~rl?c^002w40LW5Uu>k>&S-A)R z2moUsumK}PumdA-uop!jAWOIa4pB?622)yCurwR6C|O`;Ac|F3umUAvumMG5BVw=u zBSf+b0R}3v3>5!4z)b(~ z|6^a^095~jQsFgz|AYVAZ~$4#;V(s&5ljxnc*2xDtwc4s6GDa;XMPT3|!!;Uj-vEAnuW1cvvLO z$7e!_1a-StfkUTdp!c$}k zLY}scD3DW7SdC}jKIma3c^NHw5i-v1s0)e5ubx3#?$GUzsu+QR)zw>{+TE_c`G7y) zc(eBl+=n(*hCTWB@^f^ja(+9M3Z zaQfWK!YL_=AB8@r0ehkiuv+$P#z)&OIAg|wY_8_1<^$0=KIr{1fVlv_Pg|nyj&ElH zDvcm-guj^pN+X(wMVYKLxY8A4bSLTCebS653qv0e0-{iZYw9nFX!SpU8oE1HC>t-nm;{_v%YU!F%sw8xqR1=oWZv4p6fYyi>6{;S z_FW2+4zSp4J!-s|-_GIi_;#5mDoc=@l~W>($BZ^eD&Q0Z$2E}DTB`D;8W>IpWc?c^ zg@R+ErejGHB@Zn=gD!u1?ZkU;yb6b4`}pcvO3=47<~{a1GwT_#Ken=C#WXXFr(AzB z#cbCKXO4Q_iRv&*desLodh{)%E<@^xh@)>uTEY-I23E=($bS3|-FWpDS=*3UAGz48 z`(?^%P@8J31g?X3BXOJ=I)%%%3Z3jmNr9}B&emgx`o=O!ud|#vDXUv9=oWl?d{&It zj}afoT!M|U)^cBFIavom-Q zODu)eTrhnX2Yib9;K>F~V8Sg4yESi)zSHl_Z=>T|Cc0)&(jMc*lbrsyx5?5zWB$iq z)r?-78|T_$0mIBLvkY=SH-q(pfLZZy3rLr~5Jhhv3p#g(Lv1Hx>q~t05Re6buyW=s z(%&FeWdf_B9wKs1gSJa1CXLP6% zgA{Ne-g7l?C12Lma_36ASOvs;Z+*iaeZd@;iuE?7nmWw;mkeYhy* z)}GaYLBwa&00Sh8R{3|XY=D56XirYtX^DnI0D(fo{|z3;a*>?&j5wT{T%8R*Z$hh5 zQ;y{EAg)1)7($tQqV|p0Tz3n8GdSiWDb?U_TYE5Tv!}M2@#x=mw%=jkuAHk5be%Bx zt$pOD7VPzF0S(67y~#>`|57&uv|%5WNiZYkY>LyB&XTa@QfVIrnxIMrk3Y6vOBgd+ z=!z8bRhsTY4jz~;H+9gr&z60PhR=CGqZz6MxI}_c!qs7ZmeB0MAzU=6@sm^q@b=Jt zh;;o1KT8ZX=r`vBX*_*tUwcY=op78;LACGFxf(xA z7Foo}TJ3%4I@Py`LmVs<2|46o?G>(`wY+GtsOL+Y?gGxI6bAjyu|pur7)S_DeQMO1fcpRsn)cl1kkWmkc6s$RLU~tZX@M5 zxUmKapwT(fbfOLNjFJ3^k*Ua5xkk#(e z(Ya`X4)$T=2y+@Nv}!sV{(zJLkmg7J@*(?vt}vR9A9h;T3Ul3&-$P~DwhYYTt!#r=BnBs*L4Ja7G#I-MjllIG3*kG7qU z##;!>C+M!?X^mB64Q{o>5q!mmnmWh|E!d2GI;lY5@Gpe3bSU5Pf<=uA9#p+ce0I2% zlZrvo#hdw6UmilCifx{{30h^-2@hPd^&@OAEoK-)0|QQ|x;h;+gt;V4LSaqPVLW*4 zi<3_K*;+kOj|MgK(B=g=sM~592ELY0>wvqSu1g3uLv&g!Zt@V(u0+`LL3y2Nk3Y_6 z>OoIGgK}=I=XaSBe&%GhoPy-4mN8~h59`(;{RCr5nr|w(&nn}2NLANYDY417Lmm|S z@pBY=v7M}g1UY)|3d5n1Ppl7A(E7=kVdrv7{4WH9yeq?POg2c;c^`zSsXr4TNK+Q1 zQ6vvZm(zaOO1Mo-zs1A)v%%_9tX$KZ55PmG0UnWq*Tf@71cgA$*zUPg(ff1;-|1as z*_RT$YvebO-gf+x@OfLZb!%HD2To)SLfEn`=y-vQm^mQzErF2a!(ujCI~hj6PEr<^ z-BAsD94hIM88!w@?s^V4!fBNzpT>tn zu82asn9`Q{Ln=g-9KrU`qCVErTnxt&-%fMq)VE#ZB@_E8CjB4`v2m674{;cq+;6U;{yBb! zM#l_5X$tAE{-e8;WLcIh&<97Fln2DX-hAmNLh?yrCJHy%mJQ)Ep>!paur%A`x1rqz zIu1A*D(ZdNorkn0+x&yO1A_01IcXSk8jLg^N2f7|bW9^6V1zV>Z<7956=-&4aL?|j zoszFwh|x`0rPFe4UB8sX5at%JG`|Vb*brqL(WuOR1`$b*Gwfh2t153*FGNpSFV0jj zd2t-N|BN*=PKP1FiHaL2&PCPB)7Gp{Oe_iDR*JYnmzaeVjzU{W%vlw3p{2#f#9Q3x z$$#9vas1O1HNJtjft+-!bg5cmalG?L&C#K{A5Yl2;8-o`Q>V%Si%Z>SWS$V!- z(b==6rmD))e`6%(1e~&?3=JIkvS|$3AmuIS(Cud-3{(IspMdtckE_1%wUYfP@|y&L zXj!WOWKAXLC`%?hO+R(HPA~zhyQZcBEBvkIszVN_JSJvI#G@)H` zruJbO%myhwF@KpNl*DYfxdk}-<0heIX<7L-blH-V>k8Ry0u~4MFL*Q0*k%fNYRDjx zJ#~5L?o9L6qLnuj^}lI+WftXVlSz?etp?H&nMM!J3R&|nnFQzV3qQchDM>Aibm6*= zAhoJ-wH7LrCNh)2s_-Pt^>jo($2Azp(qD>HUbm?s#+9V=Su`_D zo(d)ENtMTWpia(=kkD>~OG(3~yM)yz0U5=N^EH(*hroJ*IqyvCs`yAw+Idxp|O%w-g#VA{T?V>wl-;m&@AIo^O#cc zzel#UBw-f;ABNO(NR@}+5RlmG?h+s6zUVoTaeAzm4tbi8sS`aH=j8O^{K=g~w5%2D zt$nndke4s7-FCocaAsJoK$t;z-p2kbxLH}sWu?tcO;;n;{`1xaO%wA=DVmC%wFGPm z;#W~u2KF9~D!`Mjm3zjNMVzn?QM`=whLVD{&o=^h{OphTaFEAu_OHzMon7#IAfrUX zJeNPy48RZf#mE+(q_$C!I-{8Ur?ho@V@G5k+Vqe1apdedlP0cz zM7`sQ-s}4}+1Rj`;n*-6{B?%WE4lRerghnh#7@^3ZRs6JR|C5{{B>CGH9yN0yqCLT z*MH&lz}-V4sv-kn7)T%Uw z$hsDs#Up1ugbDUiRy}3GO_)Q~hulo^{LDIyQ6aWGhTMX(&Y`E3%IG#G2yDx4w1yQw zfk#(PU0g|rqj=cXqa2$(A_SPUm>-A zh)6h|XQ$mzd8>{WTnVZf=U2D=J{|5hGo=t)IUA@xfnJ-A=t@ZOP3qM!1o=lq%BU zqEIfo>0i*SgAfCdu}2~;VnYAWQc?%7@#OwqjH1@=6(^oXPMnfv=ngJ8o z!~;rmY!a`q!*50b#W#wGye27jN>8R5>5Q*7k_zUex53cI?RG_V)nz(|9$vg~uCzkj z)k{0PlG*(}+uLz!DDpTSB6(?7hCVq^*!g$_eMG9XZ^tE;kB4{75iP2X_@&-3x21GV zY_b<^bs3X;++D+n9)}H%OI5TfTitr#*7L=L)PRU|eD-F5LWaKzmwJQv^_6?BrQeRZ zXxOUUCn9=T(k`Z!+aElL7W5R35%G8V!Jm)%kpeAN{PQxbXn?QYwi#9Sd(ep^am3e7 zr1vR9u=R;${u+4iUIb>~m%h1lZVjQ#156>13$OTcV;6!@na_+ZaGI2v)9{w+Gq(q#D9XDO+x4lc;F>Li#W+Pveh!sZi!DR+}YTd zCz=hIC3TX94~S|RR_x~cwSHv03%xjl+b>0leVUq_X~yF;Qw*qaRg{V?KGo#3=!w_P zuMn255zV8A5BKuycyE_2J#)Dpntr=~`|+hXQ(A_{Zke_u;J3zwT5&3Yy5o3WftV2Q zzp#n2WGZ;sn@w}4TEW9aaAsqIV}tXl7lj%Yya}$-MuQW-K;D4=bFEsUI!V2@Um1q- z=$rxC1m^TRQ2?bcJ$%G!_m>G3otm5Ybmm2}>hA1vU~5Xt6e^bOiQD4RWkPHP5APp> znBZWS&IW5?>YWl$wU}J=` zK6)?*!ROt!y3X{c+VBQ}*5Q^B>J(&|X0v|NFnKQG=C7FsJZXc9VeRvhwbdOFmIe60 zc%H87CoMhb^1&R^2<*ZT4rk!+c5fuip6y@RC`}aI+V9?P6z#24>zFiHh;21M(DqOq z-5(Kf({ypr7pBv#qOrX5(C}1v6SuU}L!c$8(?M)ohaBRzeRV&8!Qnks!9pWpAqG%2 zkj|DWYo{d1{~P9B4Pc=wlmi_eq8I?MmPxj^2>Iqp7djc(h0-|ahn_J6_M)$1%&(Cl zRIrg$8Ci%m_U7#Arh4-TVOlJKG6QkHC9oJY&#wZtGoHE}ggC@?|BzE#G`IB$M(2}zZu_) zF?u+2$1(@96*ztK9Ko@P99Tn$t`<=ofgugmx32`!qHs!B14&L?mAS&!Lho{D#<}(HJ*sTOP zZRg*dF^Rlr=^llZA6sG^@!(hQNMUlQ36Fy!QdF0hs-)sT{G_6DVt{5%^_kcqqmyz8 zRP3n;_fyUgGww>NWlM!94QEBnS2}j@{su4nCi$hjj7!OMSwUsGybAEoZD}qK;i7Nw zprPb(oNA!39X-NejeK53kwInICbx?I_NnTx|#KXh*;YKru zBn5%Q-`!c=S9URy*~lsk@DqzC{xNmECXdEz&$^>WETmq~1o#=|tRR&Ia=I=fRQZVT zP>?760rF5$fQmxDd!g)Uz{j3O#mL`5oATL3a zI%*foukAIU* zKnY(`iRbPOz91a{R$>L6Xax(RcW#9eQjo4T1?Eitx?XZzcI+1P;@@}WsVoNlW zDK@f%1n>v=j^g2Hl^`ss;6ECCHq7~9DlkL0FM1CoIFxXdJX6zznIjJ73GH{z>7h7F zy#bGm+2owsk1J-E_R`M;i~~0u7ZKQlNf#y2j?XLCHh9?#e7#|BX7H{5T&A4E1Ox;8 zUGmSIOQpyT!;k+OxkFIJD?czU?LFA^%|iL)fCp)Lyt!N|9E>M^g7-mUB!_4^c zT1yzNybJQV-G`6(YH$Fkv03|5w~WWQoiC3WNz=X)HoqR>?wSde*Y}%abz8iU(jp23 zeb3bTsJgY2l_zOKw)p$kf%H>=L!!O>l=Ii!U3+ZwU%@DrrmPu`sqxEL%t?_)4D&aM z*wjspiKZkLL2XzuVavkCdx~Ob`;)0AzG@5`M~TRqXW7D5T^FI za+>CBKBYp?$=SScVy80a23Ajgz;!2)ZD(Jno=Q7GeYwj|G(65z($9oGY0=f9b~jm( z+AWf(Rzj$#)-Y$bkoSc!IT2sg5Bxl|g4kA`Cef{qlmabyEN2Vsic`;Bx?Ue6puZEegVD!FBW>hm>kuE%` z>d1w6Ti3*|UjEw62SBBf^l!FC-;|}j{2e)|L_ABb-USWGb8%l|Thsi?RT(|bq3!xzgyA%vZnz`t)o3SD`@Cjh-#F|p$DGCrCv9>CX1eyE|p#% z=wy1do6BtaU?dE?waTX;k+@N+I-*X{TJL49OTEQWuC})#4#Vd{4p7>vDm;NN%s(>X z3Gly%SPFklFs{BO@=U4)Ya#re)uAfl(@WY)?d2}KnfHj2Z#j_}43Cr)0#uRA`y(@V zY9X*c-#leRS6}9Y3hYpfkF(G~fKk-Tsj7`93yJ-i>T`K0 z`rpVEWYZjtSN#5UlDUt$0qi&&!f#So)c9m;$&Tsvx(tUzW}nx@5F0%Kk=hvKW5{o4 zq_uYB43o2jKZOhVv|!4ce6bP;_n$A z^-be7ZIt{Um0?fWs(0=FN2YtCo$52FCG9q0jwGD%)hS5o2VuNUZz0`<4Nc3n+)Je8 z1RvE9rnJ@zq)LlIHcy5gHN;|S8qM%Bk^+k@i+Lx3Qt3U4XJbf& zr96M*FLQbHP7Vr#je-cHX8WUd?icvuS5!$5L6c|T3smmv$qRnr=~h3~IS6a`U0^pg ze)EcG4Gv$Lz*sVZ!aC*ec7;cU?2hV@5`7vo}tuoGNT1=w4{9_w_ z$hX*wBE^sJt^4O>V#=(x6KIy3Oz{$L`E8+#*5pqo3u~aO=vzIEW^D)D+JQG*v2Y|c zJNDO1j-%`!4AxQ;#k8&Gd9p2Gjn3jKtcc|CSGBMu$<6%koVo=69#bJB+J*=3GbCkT zwv@bY1sr5?5I>tyZ{BB1Bz_cNi$+u!2sAG#TU|571>k8`71O<+PlP@4GvZ&zg9o#GTAa zKbn4U@DfZhybO_C92JPt1$5!}7+kn1;nHq-Mz`casPa@{&C6}E9E8&hPTeRj*w z9$?8(h9R@W&5j3Gc=c|dJR#?I;zfomA+8|HY?6rBc2y!aNrL<*M$CQQL@#{!MzY!c z!ZN*%vL0J8-llLe$iOSNBH>`WYLmDvmVn8h&-W6I#4`N+as{o6yIHuN#+S2NP5+jS ziuJ(S^|qW2E!Ju-ItzsB2j9KDnEC3~xVxD;f|n+SVS)8SZUvF@6BM_w_NLGxH58sK ziXt)(_Q)A%+3H0Ze|zesxE>en5payQ(L039u-~U!p_)Ekggu-@yQKE{p;Q#cj`!;iIoZPL{-EU#D>AEp05$Z= zEG1o~b$=4*AT&k-mg@9|*iRZk=4C0yY_t-5yJM4FMu3J&(-qauPc*0Hs)g}N^YT;M zsshq2Q;I7qJ6#of5~@CQTppTK#Xm!98GVWP`wmM6?`hgD^HRBx%kAXFB*`#f(iUj< zbeb>OO{tQ3S@5IBr0OMb7QUt%Lfqt$A_{(n*{V>yf&#xGEx%9K=JRF#iA%^H;c{B9 z(wgU2MY&f}ZwCU5S=-&8gnPAnw$Ywi5p8LM9>#4!g)1uLo}U0W<~DP$DYz#p@>` zjM67%;c!Vi>6y_-W)`6PxW53!xUgmLFY`w3rlv|h=>c>w;S?C*gQ!zUkd&w6F_9r0 zfxn|^e-+D{9-`j7Ag&?Ok*wU@%kG#=O{iU%f|WM~<=n3gLtoY;T{tFaqMh5|Pl=4C zP2Wp+G6;O5p*(;5iHSS5&eUR_qe$Zxa^K?m{KGP45mk38y<;(%iZCmyDI<9` zszvPqcAAw?Bw*f6olhnfaW+2O;rF!+xdRecB=WU(QAZKBtSLstbwkKdUGf4wS}O2B zr7tA{7v6eQH}^z!l#-Q`8=FyFU%AAxCU$&Y5-!WSn0RU(n2IdqQAC5Q>>3-k2_a|8 z1bEvL?4$a9B%~Vgm&OO7vkN0-Bo?!gLIfUjXe6Z-=tEUHgme+4eyYd*%&v9iIh$lK zh5XDqtzvT8RIc&nL}hh0>HB?7&>=M}MqS*jY*clYK^w`ZtYrB0p!44BK!I3f=JQ`X z^#4w5HAJDAYHPAL_+O7V`L70rq+@AQ|zIP8DMP*^^roWJ-Ki^foM8TbJ8AKr}bu6>*Aw)%PGy4hW(_ zpArQasCn6#7^a8SneH7^QY~9BMHEEi*lx98g(rPM!#+!Wavau|(&2Yl8I2;84S^#H z&`Y|(t@3#cYDE|8imE~tq!{V_i9l(Fow|x|utaRyJ7x7lk7E10%c8u524zR^w8crV zOoa^7VTg5q=#{}Fd^fd_b}Wv9vY%6*K(gkLQnO+hG&9$WR8gBF;m}e`_7jUYod zrQ{AP9*D7!$0>hgUi&$cq+ou(A-tG3%|={t)fY)Dphap05mSph>$D~=6ZB$t>DJmj zz{IuC4p)H`I>-~gY+uu!rQy{B7lAYJ%P;Pk;qif>Oe;#E{+!00Uh<(q`q49_fbXR6 zJCG`Dhz~7ZQIuMn-}q<(ZLf+R{;$!_*uZf4O?_fi4y$5#Tdbs@)euA>6u{%;k}xH$ z7Q4WDmbu(Wv}-~816}<{@RQ81uWD68Sk88l;ll`-fq6E*4kFXE=)bg~-NN5%ebz95 zZ(TxDuvPS)LA6|$ia^cppRvqt59AT++?jf}km?D%z|!afgKohrwCAzKnxa=o zBpy=d`8XrRJ)ZPumGL1Avufak)a?R?2Ab0ruUwipU4Pv&`Q9aNhZ#89oo`tbAUAPz zbQPLue<@(-&))z_F&+;BzAw2kSN|A;bfSewJjA827|WQew`0MS<}ZlfC3ikP<$L4D z-TUQlZ&Q5;AT5&0d4P549oM4He&_Bpa$Q3!vx1~ zBmI%K*5_p5U$7vHbokh_v9`X>LoB_;o)_|nKDYsqx}p?7e@XO_#9~j@q;l?bzEL{x z;K$uK)AVlg@b1Vmf!Ok?Z$Zw|4TjG@rX+exHHd<3pSd1n+@;@KUYB^OYz|%U@bypR z`uh+V=PZp5E9PdA9S2Ajsl3fxF(dC{QJRS zzr7vSER4L0M~F*e1HCjCf5{|GG;dm1XPFwS$(A>cRg~TSO(0Us5?pqJKb$)|Z0SYX&RLZV*>EvM0)9%>oR zgOo^eK^&Q{ESf1q0U^*F>{;u^w9_qn1R6f;WQ-8Vfw$36Vx1vi%kr{JH00Jx37n=sIeg=L(Dvcx^s^EmH%S1pz80+4 zpL2Cz>Z?&=5t=;HhV{FdG;4h_Wfg^=5hYRjE+Izh9m$!c%;<$Aj+;W&jJ%D^^D*v? zzY3%84Lda3?QY?f5EV|KnyPP{ znI=b#~7+Y`wvU%uZm{10ZHFJy!1TLPpLdI&>P*NH-*ZQ zx99h^tjY%}cG^vd5!BTy<#rdG>cqwJ^3~k@Q9XN~?UnqvJFP9hymox{RkMY$1|!pj zHcDeQPG;v0fvbC}7>8M%a34PhuDN!E>7ZzlOCy%wr>Knf7LEPETwI-qr=B&v8L6ul zm#W|16`!}vFweo)^^EUp^El;pYMs{JF0EK!U3k<@N%$Z%HtTR0Y=od7tnL28_OmKs zZa?*?*^(<5Fpqrks82W{_^SeKLna2F>yKE}fa0HS3n^UeS{S=RjM75EYy@BB=hxyL zv)2(xO#U+tabc(WyRsk#nV%WW`*u7Dt%(7TM+#}!Eb1xGYqB_e5)bHI9C+s(cg4xI zJD;=Bqsb+aQp-F`_9mBJXZif1m}cpEc5|CDcIOT#A zq0&vG=usRvO}s^I6Wazc_|cVpUsf@`SW81|V~UOZ=wUzo#i#iV2m6bq2B!=ae5qQ| z_2?~w8~jX?Uo68kmpQ`sw(05iQ{_++A^whSr5|cN;~OmWYvlt0UHC}48#YSa=b-iu zv~b}ulbFnBlGh4hC-n^QeZD7)3!b2=$3OzHZe{_PMfqhs1$tkh{sk0Ns$zt(Rdgz6 zd_|-Y7wdrYfLY#OA^PDAJ`L{FSrO5n4)R;k%^Lf6CUGUIvfwn1+>peVP20xQaoNZI zQ6tDlzLRXEO#=?;|a@lfh*AooX5~K z#VqLumOwgc=G!o{-YhmrTL(!|n&jYQ)VplnK}SmNDiM;Xi9{xJBzo#}F>Z9zn=17k zJPMf`s(fW=?ALmgXVldUKam%%m2DC`34EfxCjU>tF-S#bg>q#*FSmiGF*NO%rQOlM)z?l{$GEdb_HN05*{#8Tj?+CI(#o^qHVv zIf8gocJwUOzLP{k%}K(FfU@lGD00t4^1UDEjTk6Hhh9K`k1g1ZnKDBs=oy)iM|7eQ zK$@EO__b174bMji+Huu}dL90D!QuP*kFT}KqlN1;EB{?q(2-fGC61)^`C{+ zY(i^IG?O$*t6D`S;zf0N(lE@E5@X6RoL#KZ{XLE4U!*-imY`aW2HZQzCUJTej?I(4 z)?1yR(h`ZT%gbv|&BiECi_#iF^eMGJlS&f5U&e8$r0y{c=w%MVM9^m~<(=k%Zk5ta&s@PhKqhBdXUqC@igP9x2O4JEaSm@`Fpwq! zWPrwS2E6T@L*S}qPutLSs}uG^(@8!qEt<5|N|_%f503w|z?}3g2|Iy0;oAR*l3D$d zuFkOrz2u1j5E5aTO_(`i_et#G$+AE^TX zyA)Jh*YNa<#)e5AhRVT)+UKzNXvn58lbn95^to-IT6Mo`bshxyJ1B zahd$2-w)mzusZ3E19CX47Mi^G$(HG(!UvwsVREWFl0^13?C^c;h|&g?wBAp}yv{lo z_hXtk9Ls=l%$1vn7<$g zzv+>3Y%BaQKo|-5_z8PR3ML}7eCK=>EpE3{m&Csu7dQKJ#y?*(m#%R;K<&qF!v>uZ zqv$IHX{#8z7;S!EHI$2oDQ9BiW!!w%DD@z=Une<1G=}lD(QkUfb9OF@yRssLC+z+b zG!xg-MVj*4pyttDAM_xjm|)d&w^hP7q55|-yHes_4mU0>K;xf_g~d>QC9gwIe&UEX z>E;m!FahCy-MJ4XdDAh-Mxy=wtpfF|s_IrWN3P(0Z?Skwio%a(_*U9l;T4?l-Z9(>tvjNJc#}qV(TcX}ej=b1hqM-xq);CW5%1 z!olCTcyj?NBJWz!qWmc$9H4V}mNN8D09jf9pn!bVb(kBQK{Nk~rN4%sAt`>)8a0Hca3Utc|$}o!Jg$PGdCYreR&@q|DB*~`iXHD5kP@Vk-;8vr3R3> zL(+nHV-Ea-6n?U&I&%E7=xg3cr9}&bD4Rw_l5k!>E3aYi!()<1Jh(?$qH&@c2!Usj zA%edP#|5J?FceAkT}u%ygah)1BC!bNyl_51j0*O3xD9=Kos*AN6;pw|=*2kV1oSHn zv55g6dl6{S*9Ys=xcaqTqy<{O2N#i-dC=Qr3SEN zzfP>K_yMeDSvoUc1CU{(2ts)30^m>#c#sxr`~Vh_TE@#iSc6e#i65Hr?7kdh^Hwr? zBu>k7tdXp1NK4kotk)Lhe>Xd;1Y7NxXTC)p?pza=*9!tGwJK4i{b<|$iHQeWK}5`4X&iJ zt3#AVQOep#C2r}kG?Ru#x|}DN(ukC!Xy)pbmrwM+J!oxFSq|&tNGcWyvvvVEm@~SL z%Zr?Na#p+qjECcGmMmFZ?O3H`qSr-}BE4F0JG*`y=v}Eh`nk?r@aNP)UXfj8L(sb2 z#C7$?Z>t*Qptzqj`IWHpdXF=U<#Z27;xckJQud9WslqmJn)L&yFvsOGpUwT8t z$Q1Qo8yBFz7dUQa+PT0vSp!t~FG7Kcn5U@7Js*HK^bqfuI`~gqL^dwBP--(kHh`qE z*D4?*y@G{SNE?9fW7}0WK-$W67aXCe1dj)t2vGCUUaVU#>Ne_A9=;!VzmD<3|sk%HR56y|q92FlM{5UL+ zm)P^+{&9L2rtz9m)dZ9YRH?A?gJa`K?O@RGKIEV|>XC(e1f2-!-fh<+DYr}|w=Tu0 zgq%ru1{YJL=hbAM!}CZR{XiKN-B!njxw4OUhS;y(W>(OcBdJYSatsyzm@g@{T^{Q? zqqeAbmpGfv|X z!(6A#gL@r3JpKom#7`l#5(IB+V8ol1}~b-^7#MhXqh^u;wuJ zmt^TecM|YdY&g1%X|uasq~wD7Xty z>!{U;hUeuH>!buTY-Q7nkZU)+3Wf96ZWuz!^!0ZL_T9iFcM&q+Y0ei66P8if#XoXZ zS~UA(`AtFk)G6G1IWEk`#=*KcEa7dPrm0YW2+lqkPN7IpNzwUVAwfD&Lj6P-Wfwg* zb1gAEXv>zl$H8!%@M&Cr9*RWR-CGPZo|j~H0z|p^ zBM%J#lYCYJLx+Lzv`dLc)J?H)g>%Y$(Nx>QWrAsgCHqxK*ehft0g9{C(FW z?MjpSQL0QvSaLzrr%YCUm;(LT>VvUoMV#{9*E&^|4C$JHN6}gybr|x8>&o#`kCIId z^qv)Y(klPni1cEj0sFbajF1CeVD-on$6KjsSG{H!n4=F>PXtqWGVTkCRO8I>Vn+wv z@YUri;s5YjTqgb2RZZlAhL-j-q9w!A+#qh7x~*T$&}h?i=?FhUi4Q>{Iy(8_;jOa@ zm5?Qflnq|^1ZI0nYSB*TD2pUc1KbWFl!uVV*vMFGz8{cuT{q8|Ze1 zOC0l4VHPhz-rZk`0`7&j?bJ5_KQ{-L*FCmz_62H&^nI!tOiMjJ4Ic-8-J*ft#z8nS z5P6}OgfocBw)Zz!Bw;IT=OSxLvPEVGhW`j~*8F@qWwWKBV7l(b$HW{%_IHf*wFd8| z)i$O>{~Kf7uR~t_hOXc}9kfF5%sCD~JxZCVUkBVVTr_oM>a=>4z@tFGN9Gq}i9L0Q zMEl=d&=Bzz{aiUIwS*2w*DjDwLSqMvroTsGj^dWqP`H${`%jt?+rBd|cvG2axoY>!*`8FTx(#EwwGL!HhPkJ=b0)OR26LVgtC#l7Li5vrI~=_dOM~=4 z-frm@`{VYMI*t$L_Si$psRR0&65(|6_{JT!b@XgV-s>0ayV2@A^4 z{To=cPneX^hf+-~u5Etmx76jcCG9hfWBD5bIexZ?z|MNzsU!7IDE+f>P9N0b7&Y3L zD(Bhd--mAU^hPzZ2l=88WxQUQQ%H}1ajBbOZ&rxzB;{Mj7_`KY*fgUsv71H;c(O{y zRcW$e{@55oWr~Z{#f&@t=o@a3=`4V438Un_%<7n0cfHmOiez{b_x_?pO?tNJk>jQ7 zIS^i=1580|HuW>Wbe~tCrD>*#D@Qa?CGSdTv5zVTzHltuB(?2l3KP4poL=dJn-6ld ze{Vl+ma0DXp6PBs?iPB zQ3cRUwIx%rpl8CN`B?1 z`T{Z*dvEjox<5l4-S4FZheLZGc|U!2IsEGAC(L#0Yttedfcs2iQcYyQcWanx>nHt$j|m>Rjv$DfTrGNCQ}24ujr!M!TNo7wiLE$x?6o3#UikdvvyPbY~FDb`|+ zDLc|~ai(pCgKL!aYk&xVtBo9ACN15;-Hiy%@Ny-D+ucg8e&g70DGE@eqM)6CEMS;J+c>Lp`zk6Pk-hVEZ=`q;>%c+s(aM3zrTEw7m%P@eWWERH%K46@<|RN9Vw!CIc|wX7i=!l1ZHf z%`JppOt+8?hql`5UpXPnZ~@yi=hIFR(Qsd+%WvyWxSd$ch>k;LqTTvLD;1$r8tI%^mRoky-L@ zHZ=3qfn$MRT$mfOMPoF*PziB!t4O{^dPTI1LK7`cY=_fl|Ut8mgkuk`(NK3Kf|zXU;F zm9&OD#Vi=$=-8rzj5H)Ts``fa*v@I9Ax^5+!=U~U+*D1NrwV{z=M0h!{8AvXpyCEXT#);grV;X@ zyNgb$#pmf!NeWiuQa-ep3Li-+Yon=RZj5)31cQ8x`Fp0w)Xgf&#!c1#BQ6yfj0+I3{Vbh#}iR(9El;LO>FE z)ShM?9)bee(Xo&`sIU|xglL0JAh#9+WaKQ5Ab#Q*ef@~)MI9qJhr&!ILokR>7Fdo2 zxa{p_RBcGCzAs9;{rUWwX38q5RhEgA=#^bFQaL_RDpj})%MkMXapo4@OeWZRm@>Nk zA{=Qu52W~NI3}TzQ^j!U=EPXz&5J$_Q*)-54WCug;FQtR@JvYXvOZk~YDA-- zE*h)EaL!IySRcV^4ypZQWpn9?a)E14KouZn9oeuyHN}E&$|prDz3WXi=7(EG8sQd_ zS#W3aat82uui%Qnl?iLFL@*`T=L|*vNkwX{PL+*x2~*YsZ(O7l<}p%5(1=U9pojvb zA?PLAm@e1|yRh`55%9ae!!cexhFq}M#7A?#OAhT46cd}OGXkYO2Z<*J4Kuw8=j8^I zQiwt)0xcscH^<~KYxHmeB?2tD+0+vZ4!w?32^1mN@}G|2#&-xp`Z2~BI3${Z_%?%o zqTesLLKe6~^KD?rOVxJ^K$=#2&f;dJ;;S|f#}mpp5lT0uIkCgPwKiP<$fr|`Y04*v z(Ao~$05Bl>M1%%ng+Z;0uEA|-i-r{HOw3Q>gxv$*I6X%fD|3YsXTAYiE6_HGf`Wx~ z2m~wo5sQdW4 z@CX3mlrkoBtPD{xSR&}g_uM8uMVaNDCuP-XJoJR;co^TO5ES{4L<*W4R-%lnDbFgB zq37Y?1AwdG^&RKY&3%JbS>e4)J(CqNb+jPig#Z~Qcoy$^G5YmSf>s>u3r%_In3JG- zS$q7>ECo|bkD)GEW0VBQxRDU$V|NRm3*~i-HWgxuaQth-;ih@d02E-yDD1J z4y8uc?3F*P0}zz1@HW8uu@v~I^)G7F#yl^d;3dEwan+m!lj4B%2pPd0kpW*OPStB4 zYb}B_Q$U~SEL_U8k$EHVB$YgmK_>_h(@I`A(wCb=foTS7CBTJv<_Ihsrz@}l27RPi&#by#n8F6IX98x1G` z3KlIh?wb~j;f3AJ)^Iq?f}u=k2(0}P9T`Lss)%tQBZTY%79=J_`loHNJKPzJ+R3Ut zD2|sR!;>T5w_OnpxSH*o)^MCK*`ZaG*sX-pwH?m9Tdy|l%6N$tj@aqlx=EB`3~P-Q zYYO0-s)xgv$8_yk&XgGz8pX*`kw{imP34RFMHOl7uLzN*$jKzRqF~mbF$qEPxp`5< zXF5PHWWY3Yjh>bLA9CIO^mffo9Y>wU4TkWu7krUNWN`so<}K7Xd2NY3Tj1D|%r|%7 ztHKJM4EW~hj%K~9e%leyeLX|x-C#ThKB4TiSV$QbA-yEbgYWKT zbz>@J6&hd-s}l^oCzqb@vvDw*cu$IiI)NNdL>F%fShy3Xfs#60MSveLDUv)Q1hMi+ zR(8RHV+c?_9#MX?a*-`E$%s%*E+mWy3~{F}N--dP&;pyIP#>W?sdjkDr6VCy9S~=k zKECdBGu&Dfb5C_(ML2}#R5&dKc^x%u4hkf{4_V~hk8i7+r4!rJHg&jU8J;p|B1>GEhu0A0dV@l~q$zWA zG#@`VFT!889tn6%>dg5Xn|j6>r|zm{nM3zPj2~ql2LrfVOsr{=lvP-NO2AODBPSI! zgVo$bm=g)!HOm&-dS*wJ8oqvBr_rlztm1H0vL*^Os&PQwMF?^_56apEQ;l0N3n`ja zLzUnPPMc>sAg=<5$5!H|JDIK|QbKfquxD~b4gkRb3Ewn{5%Cs8l)l0jxSd1>P`?2m zZPSXD(7;GoMBKD@E$x_msh&<4_lW8gdCYW0Yfig*I zub1hP25d|CL{)&$eM`sMrdn{o9-OvhNg~`1dqw(lEs8G8CC=;RuwVR?i#y+SE7g!F zfs`Pk+Je=uTx1`SlbntW*DMz9;wM^&V*)WUO)hZCIw>h)wx`Un+*^PiH>_$kp2P?S z+9i7=AAK{i6cb;-ML7*lwGqb(IF;=+ffDb1u_0FUSZl_K^-NYwTwQrD+qTNXFfvW% zssXgH4SA(<4HSq$BHkd5XsLg02fqV9L-!ddu*0K@l1e-040xa_FCyDIodPrx61eEt z6qr(pP|QDrpZhT2nFg2!Eu4NY^d`zR9fKjD8)vdv8+qRe#LEdjoJ{?HOzYz)>JO-m~$|RyfK*(8& z8M;XWQ5PVk(SsEVMJkdmYBgbWV@DW}HP&Qc^iiFW43W@-#@TWMstz8t-FDe-LwJrV zi>@(|ig-ru(POv=QIoyk3u3Sj?V1VVCLx!A{JWA6f${oIDN3{w8+i7FH;2 zwpCcT1#1VWTnY!v3N}ys%{JhtuH0p9Va8*ct4YsV-l5VV66Mp;w&_LTZ|{O(6ATJ= zopS{ud;B=}=H@taMsHi9j-xQhs^)L12+MkW(5W53_G~9QaVm|o)PkO#@cGn`Rl=)? zWjyAr*d18;gJY`QywtwUS+t5Nvh2Z+J{m}#V4)4;pSm)@s}0#=7RHxri)?4%T+ory zh(JhEqt8^$Bp!s3G4r#@FuF3V2@OI>j8-eUgZi|?_2~>%Q(9o0nSe>5b0R|bKxR!o z*n+Z8o~eY9`5?WgKIp$Vn54>jYF+0iA$D=txuXYKW))Mr=Q6WcHZLoxl~V)83gDSz zYYgF%{*pSmvjy!}0sv=7VREtHp&u#doOr?!n_P$1-#PP0* z*C=Nt)|G#Tx13g+devX~lQXu}Fy32mOL&6~tz$=%CbY z;IA!IiRt#ZMNBho0x?G)PHa;vXG>TT$m4_b# + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/out/fonts/OpenSans-Italic-webfont.woff b/out/fonts/OpenSans-Italic-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..ff652e64356b538c001423b6aedefcf1ee66cd17 GIT binary patch literal 23188 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 literal 0 HcmV?d00001 diff --git a/out/fonts/OpenSans-Light-webfont.svg b/out/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 0000000..11a472c --- /dev/null +++ b/out/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/out/fonts/OpenSans-Light-webfont.woff b/out/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e786074813a27d0a7a249047832988d5bf0fe756 GIT binary patch literal 22248 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 literal 0 HcmV?d00001 diff --git a/out/fonts/OpenSans-LightItalic-webfont.eot b/out/fonts/OpenSans-LightItalic-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..8f445929ffb03b50e98c2a2f7d831a0cb1b276a2 GIT binary patch literal 20535 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 Z%0#`NexefPFV?Xj51H#Y#AC7WXn+Jg($4?@ literal 0 HcmV?d00001 diff --git a/out/fonts/OpenSans-LightItalic-webfont.svg b/out/fonts/OpenSans-LightItalic-webfont.svg new file mode 100644 index 0000000..431d7e3 --- /dev/null +++ b/out/fonts/OpenSans-LightItalic-webfont.svg @@ -0,0 +1,1835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/out/fonts/OpenSans-LightItalic-webfont.woff b/out/fonts/OpenSans-LightItalic-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..43e8b9e6cc061ff17fd2903075cbde12715512b3 GIT binary patch literal 23400 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 + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:59:18 GMT+0000 (Coordinated Universal Time) +
    + + + + + \ No newline at end of file diff --git a/out/index.html b/out/index.html new file mode 100644 index 0000000..b5d27e2 --- /dev/null +++ b/out/index.html @@ -0,0 +1,63 @@ + + + + + JSDoc: Home + + + + + + + + + +
    +

    Home

    + +

    Shifts

    +

    + Shifts.js +

    +

    + ShiftsData.js +

    + +

    Hours

    +

    + Hours.js +

    +

    + HoursData.js +

    + +
    + + + +
    + +
    + Documentation generated by + JSDoc 3.6.11 on Thu Sep 29 + 2022 14:59:18 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/metrics.js.html b/out/metrics.js.html new file mode 100644 index 0000000..b2bff95 --- /dev/null +++ b/out/metrics.js.html @@ -0,0 +1,226 @@ + + + + + JSDoc: Source: metrics.js + + + + + + + + + + +
    + +

    Source: metrics.js

    + + + + + + +
    +
    +
    import React from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import { Session } from "bc-react-session";
    +
    +import { Queue } from "./queue/Queue";
    +import { Punctuality } from "./punctuality/Punctuality";
    +import { Ratings } from "./ratings/Ratings";
    +import { GeneralStats } from "./general-stats/GeneralStats";
    +
    +import { store, search } from "../../actions";
    +
    +/**
    + * @description Creates the view for Metrics page, which renders tabs with different modules being called inside each one.
    + * @since 09.28.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * 
    + * @requires Punctuality
    + * @requires Ratings
    + * @requires GeneralStats
    + * @requires Queue
    + */
    +
    +export class Metrics extends Flux.DashView {
    +
    +    constructor() {
    +        super();
    +        this.state = {
    +            // Queue Data ----------------------------------------------------------------------------------------------------------------------------
    +
    +            // Variables for the Workers' List
    +            employees: [], // This will hold all the shifts.
    +            DocStatus: "", //This is needed to check the verification status of employees.
    +            empStatus: "unverified", //This is needed to filter out unverified employees.
    +            
    +            // Variables for the Shifts' List
    +            allShifts: [], // This will hold all the shifts.
    +            session: Session.get(), // This is needed to crate a user/employer session.
    +            calendarLoading: true, // This is needed to fill up the list of shifts, not sure how.
    +        };
    +
    +        // This updates the state values
    +        this.handleStatusChange = this.handleStatusChange.bind(this);
    +    }
    +
    +    // Generating the list of shifts and the list of employees ---------------------------------------------------------------------------------------
    +
    +    componentDidMount() {
    +        // Processes for the Shifts' List (Not sure how they work)
    +        const shifts = store.getState("shifts");
    +
    +        this.subscribe(store, "shifts", (_shifts) => {
    +            this.setState({ allShifts: _shifts, calendarLoading: false });
    +        });
    +
    +        // Processes for the Workers' List (Not sure how they work)
    +        this.filter();
    +
    +        this.subscribe(store, "employees", (employees) => {
    +            if (Array.isArray(employees) && employees.length !== 0)
    +                this.setState({ employees });
    +        });
    +
    +        this.handleStatusChange
    +    }
    +
    +    // Processes for the Workers' List (Not sure how they work)
    +    componentWillUnmount() {
    +        this.handleStatusChange
    +    }
    +
    +    handleStatusChange() {
    +        this.setState({ DocStatus: props.catalog.employee.employment_verification_status });
    +    }
    +
    +    filter(url) {
    +        let queries = window.location.search;
    +
    +        if (queries) queries = "&" + queries.substring(1);
    +
    +        if (url && url.length > 50) {
    +            const page = url.split("employees")[1];
    +
    +            if (page) {
    +                search(`employees`, `${page + queries}`).then((data) => {
    +                    this.setState({
    +                        employees: data.results,
    +                    });
    +                });
    +
    +            } else null;
    +
    +        } else {
    +            search(`employees`, `?envelope=true&limit=50${queries}`).then((data) => {
    +                this.setState({
    +                    employees: data.results,
    +                });
    +            });
    +        }
    +    }
    +
    +    // Render ---------------------------------------------------------------------------------------------------------------------------------------
    +
    +    render() {
    +        // List of workers with verified documents
    +        const verifiedEmpList = this.state.employees.filter((employees) => employees.employment_verification_status === "APPROVED")
    +
    +        // List of all shifts
    +        const listOfShifts = this.state.allShifts;
    +
    +        // ---------------------------------------------
    +        // Filtering expired shifts
    +        // let listOfShifts =
    +        //     (Array.isArray(shifts) &&
    +        //         shifts.length > 0 &&
    +        //         shifts.filter(
    +        //             (e) => e.status !== "EXPIRED"
    +        //         )) ||
    +        //     [];
    +        // ---------------------------------------------
    +
    +        // Return -----------------------------------------------------------------------------------------------------------------------------------
    +
    +        return (
    +            <div>
    +                {/* Title of the Page*/}
    +                <div className="mx-3 mb-3">
    +                    <h1>Metrics</h1>
    +                </div>
    +
    +                <div className="row d-flex flex-column mx-3">
    +                    {/* Tabs Controller Starts */}
    +                    <nav>
    +                        <div className="nav nav-tabs nav-fill" id="nav-tab" role="tablist">
    +                            <a className="nav-item nav-link active" id="nav-general-stats-tab" data-toggle="tab" href="#nav-general-stats" role="tab" aria-controls="nav-general-stats" aria-selected="true"><h2>General Stats</h2></a>
    +                            <a className="nav-item nav-link" id="nav-punctuality-tab" data-toggle="tab" href="#nav-punctuality" role="tab" aria-controls="nav-punctuality" aria-selected="false"><h2>Punctuality</h2></a>
    +                            <a className="nav-item nav-link" id="nav-ratings-tab" data-toggle="tab" href="#nav-ratings" role="tab" aria-controls="nav-ratings" aria-selected="false"><h2>Ratings</h2></a>
    +                            <a className="nav-item nav-link" id="nav-queue-tab" data-toggle="tab" href="#nav-queue" role="tab" aria-controls="nav-queue" aria-selected="false"><h2>Queue</h2></a>
    +                        </div>
    +                    </nav>
    +                    {/* Tabs Controller Ends */}
    +
    +                    {/* Tabs Content Starts */}
    +                    <div
    +                        className="tab-content mt-5"
    +                        id="nav-tabContent"
    +                    >
    +                        {/* General Stats Tab Starts */}
    +                        <div className="tab-pane fade show active" id="nav-general-stats" role="tabpanel" aria-labelledby="nav-general-stats-tab">
    +                            <GeneralStats />
    +                        </div>
    +                        {/* General Stats Tab Ends */}
    +
    +                        {/* Punctuality Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-punctuality" role="tabpanel" aria-labelledby="nav-punctuality-tab">
    +                            <Punctuality />
    +                        </div>
    +                        {/* Punctuality Tab Ends */}
    +
    +                        {/* Ratings Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-ratings" role="tabpanel" aria-labelledby="nav-ratings-tab">
    +                            <Ratings />
    +                        </div>
    +                        {/* Ratings Tab Ends */}
    +
    +                        {/* Queue Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-queue" role="tabpanel" aria-labelledby="nav-queue-tab">
    +                            <Queue shifts={listOfShifts} workers={verifiedEmpList} />
    +                        </div>
    +                        {/* Queue Tab Ends */}
    +                    </div>
    +                    {/* Tabs Content Ends */}
    +                </div>
    +            </div>
    +        );
    +    }
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:00:24 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/scripts/linenumber.js b/out/scripts/linenumber.js new file mode 100644 index 0000000..4354785 --- /dev/null +++ b/out/scripts/linenumber.js @@ -0,0 +1,25 @@ +/*global document */ +(() => { + const source = document.getElementsByClassName('prettyprint source linenums'); + let i = 0; + let lineNumber = 0; + let lineId; + let lines; + let totalLines; + let anchorHash; + + if (source && source[0]) { + anchorHash = document.location.hash.substring(1); + lines = source[0].getElementsByTagName('li'); + totalLines = lines.length; + + for (; i < totalLines; i++) { + lineNumber++; + lineId = `line${lineNumber}`; + lines[i].id = lineId; + if (lineId === anchorHash) { + lines[i].className += ' selected'; + } + } + } +})(); diff --git a/out/scripts/prettify/Apache-License-2.0.txt b/out/scripts/prettify/Apache-License-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/out/scripts/prettify/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/out/scripts/prettify/lang-css.js b/out/scripts/prettify/lang-css.js new file mode 100644 index 0000000..041e1f5 --- /dev/null +++ b/out/scripts/prettify/lang-css.js @@ -0,0 +1,2 @@ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", +/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); diff --git a/out/scripts/prettify/prettify.js b/out/scripts/prettify/prettify.js new file mode 100644 index 0000000..eef5ad7 --- /dev/null +++ b/out/scripts/prettify/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p th:last-child { border-right: 1px solid #ddd; } + +.ancestors, .attribs { color: #999; } +.ancestors a, .attribs a +{ + color: #999 !important; + text-decoration: none; +} + +.clear +{ + clear: both; +} + +.important +{ + font-weight: bold; + color: #950B02; +} + +.yes-def { + text-indent: -1000px; +} + +.type-signature { + color: #aaa; +} + +.name, .signature { + font-family: Consolas, Monaco, 'Andale Mono', monospace; +} + +.details { margin-top: 14px; border-left: 2px solid #DDD; } +.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } +.details dd { margin-left: 70px; } +.details ul { margin: 0; } +.details ul { list-style-type: none; } +.details li { margin-left: 30px; padding-top: 6px; } +.details pre.prettyprint { margin: 0 } +.details .object-value { padding-top: 0; } + +.description { + margin-bottom: 1em; + margin-top: 1em; +} + +.code-caption +{ + font-style: italic; + font-size: 107%; + margin: 0; +} + +.source +{ + border: 1px solid #ddd; + width: 80%; + overflow: auto; +} + +.prettyprint.source { + width: inherit; +} + +.source code +{ + font-size: 100%; + line-height: 18px; + display: block; + padding: 4px 12px; + margin: 0; + background-color: #fff; + color: #4D4E53; +} + +.prettyprint code span.line +{ + display: inline-block; +} + +.prettyprint.linenums +{ + padding-left: 70px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.prettyprint.linenums ol +{ + padding-left: 0; +} + +.prettyprint.linenums li +{ + border-left: 3px #ddd solid; +} + +.prettyprint.linenums li.selected, +.prettyprint.linenums li.selected * +{ + background-color: lightyellow; +} + +.prettyprint.linenums li * +{ + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.params .name, .props .name, .name code { + color: #4D4E53; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 100%; +} + +.params td.description > p:first-child, +.props td.description > p:first-child +{ + margin-top: 0; + padding-top: 0; +} + +.params td.description > p:last-child, +.props td.description > p:last-child +{ + margin-bottom: 0; + padding-bottom: 0; +} + +.disabled { + color: #454545; +} diff --git a/out/styles/prettify-jsdoc.css b/out/styles/prettify-jsdoc.css new file mode 100644 index 0000000..5a2526e --- /dev/null +++ b/out/styles/prettify-jsdoc.css @@ -0,0 +1,111 @@ +/* JSDoc prettify.js theme */ + +/* plain text */ +.pln { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* string content */ +.str { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a keyword */ +.kwd { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a comment */ +.com { + font-weight: normal; + font-style: italic; +} + +/* a type name */ +.typ { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a literal value */ +.lit { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* punctuation */ +.pun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp open bracket */ +.opn { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp close bracket */ +.clo { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a markup tag name */ +.tag { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute name */ +.atn { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute value */ +.atv { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a declaration */ +.dec { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a variable name */ +.var { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a function name */ +.fun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; +} diff --git a/out/styles/prettify-tomorrow.css b/out/styles/prettify-tomorrow.css new file mode 100644 index 0000000..b6f92a7 --- /dev/null +++ b/out/styles/prettify-tomorrow.css @@ -0,0 +1,132 @@ +/* Tomorrow Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* Pretty printing styles. Used with prettify.js. */ +/* SPAN elements with the classes below are added by prettyprint. */ +/* plain text */ +.pln { + color: #4d4d4c; } + +@media screen { + /* string content */ + .str { + color: #718c00; } + + /* a keyword */ + .kwd { + color: #8959a8; } + + /* a comment */ + .com { + color: #8e908c; } + + /* a type name */ + .typ { + color: #4271ae; } + + /* a literal value */ + .lit { + color: #f5871f; } + + /* punctuation */ + .pun { + color: #4d4d4c; } + + /* lisp open bracket */ + .opn { + color: #4d4d4c; } + + /* lisp close bracket */ + .clo { + color: #4d4d4c; } + + /* a markup tag name */ + .tag { + color: #c82829; } + + /* a markup attribute name */ + .atn { + color: #f5871f; } + + /* a markup attribute value */ + .atv { + color: #3e999f; } + + /* a declaration */ + .dec { + color: #f5871f; } + + /* a variable name */ + .var { + color: #c82829; } + + /* a function name */ + .fun { + color: #4271ae; } } +/* Use higher contrast and text-weight for printable form. */ +@media print, projection { + .str { + color: #060; } + + .kwd { + color: #006; + font-weight: bold; } + + .com { + color: #600; + font-style: italic; } + + .typ { + color: #404; + font-weight: bold; } + + .lit { + color: #044; } + + .pun, .opn, .clo { + color: #440; } + + .tag { + color: #006; + font-weight: bold; } + + .atn { + color: #404; } + + .atv { + color: #060; } } +/* Style */ +/* +pre.prettyprint { + background: white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 12px; + line-height: 1.5; + border: 1px solid #ccc; + padding: 10px; } +*/ + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; } + +/* IE indents via margin-left */ +li.L0, +li.L1, +li.L2, +li.L3, +li.L4, +li.L5, +li.L6, +li.L7, +li.L8, +li.L9 { + /* */ } + +/* Alternate shading for lines */ +li.L1, +li.L3, +li.L5, +li.L7, +li.L9 { + /* */ } diff --git a/package-lock.json b/package-lock.json index 0086de9..7e647c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6099,6 +6099,25 @@ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=" }, + "@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==" + }, + "@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "requires": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==" + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -8615,9 +8634,9 @@ } }, "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==" + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "bn.js": { "version": "4.11.8", @@ -9085,6 +9104,14 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "requires": { + "lodash": "^4.17.15" + } + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -19538,12 +19565,59 @@ "esprima": "^2.6.0" } }, + "js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "requires": { + "xmlcreate": "^2.0.4" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, + "jsdoc": { + "version": "3.6.11", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.11.tgz", + "integrity": "sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg==", + "requires": { + "@babel/parser": "^7.9.4", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.2" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + } + } + }, "jsdom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", @@ -19753,6 +19827,14 @@ "is-buffer": "^1.1.5" } }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "requires": { + "graceful-fs": "^4.1.9" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -19810,6 +19892,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -20071,6 +20161,40 @@ "object-visit": "^1.0.0" } }, + "markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "requires": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" + } + } + }, + "markdown-it-anchor": { + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.5.tgz", + "integrity": "sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==" + }, + "marked": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", + "integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==" + }, "math-expression-evaluator": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.3.7.tgz", @@ -20108,6 +20232,11 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "media-engine": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/media-engine/-/media-engine-1.0.3.tgz", @@ -32036,6 +32165,14 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "requizzle": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.3.tgz", + "integrity": "sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ==", + "requires": { + "lodash": "^4.17.14" + } + }, "resolve": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", @@ -33658,6 +33795,11 @@ } } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA==" + }, "tapable": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz", @@ -34169,6 +34311,11 @@ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", "integrity": "sha512-uRdSdu1oA1rncCQL7sCj8vSyZkgtL7faaw9Tc9rZ3mGgraQ7+Pdx7w5mnOSF3gw9ZNG6oc+KXfkon3bKuROm0g==" }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, "uglify-js": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.16.tgz", @@ -34199,9 +34346,9 @@ } }, "underscore": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", - "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -36753,6 +36900,11 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 35aa193..694133b 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "interactjs": "^1.3.4", "jquery": "^3.4.1", "js-cookie": "^3.0.1", + "jsdoc": "^3.6.11", "lodash": "^4.17.15", "moment": "^2.22.2", "pdf-lib": "^1.16.0", diff --git a/src/js/PrivateLayout.js b/src/js/PrivateLayout.js index 55fd971..71584c8 100644 --- a/src/js/PrivateLayout.js +++ b/src/js/PrivateLayout.js @@ -796,6 +796,13 @@ class PrivateLayout extends Flux.DashView {
  • + {/* {showHideHR && ( + + + Metrics + + )} */} + Metrics diff --git a/src/js/views/metrics/charts.js b/src/js/views/metrics/charts.js index 5ea7cb5..f61afdd 100644 --- a/src/js/views/metrics/charts.js +++ b/src/js/views/metrics/charts.js @@ -2,6 +2,14 @@ import React from 'react'; import { Pie, Bar } from 'react-chartjs-2'; import { Chart as ChartJS } from 'chart.js/auto'; +/** + * @function + * @description Creates a pie chart with the data passed as an argument + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires Pie + * @param {object} pieData - Object with data like colors, labels, and values for the chart. + */ export const PieChart = ({ pieData }) => { return ( { ) } +/** + * @function + * @description Creates a bar chart with the data passed as an argument + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires Bar + * @param {object} barData - Object with data like colors, labels, and values for the chart. + */ export const BarChart = ({ barData }) => { let delayed; diff --git a/src/js/views/metrics/general-stats/Employees/DummyDataShifts.js b/src/js/views/metrics/general-stats/Employees/DummyDataShifts.js deleted file mode 100644 index 3c79197..0000000 --- a/src/js/views/metrics/general-stats/Employees/DummyDataShifts.js +++ /dev/null @@ -1,253 +0,0 @@ -export const DummyDataShifts = [ - { - clockin: [ - { - id: 16, - started_at: "2022-09-19T10:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.7", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 18, - started_at: "2022-09-20T09:20:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.5", - ended_at: "2022-09-20T20:45:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 49, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 17, - started_at: "2022-09-21T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.4", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 4, - shift: 46, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 19, - started_at: "2022-09-21T10:36:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.4", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 5, - shift: 47, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-09-10T09:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.8", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 4, - shift: 50, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-09-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 50, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-01-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-01-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 3, - shift: 50, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-04-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 3, - shift: 50, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-04-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 2, - shift: 50, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-04-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 2, - shift: 50, - author: 95 - } - ] - }, - { - clockin: [ - { - id: 15, - started_at: "2022-04-10T10:05:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-01-10T19:28:08.811375Z", - updated_at: "2022-01-10T19:28:08.818207Z", - status: "PENDING", - employee: 1, - shift: 50, - author: 95 - } - ] - }, - { - clockin:[] - }, - { - clockin:[] - }, - { - clockin:[] - }, -]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js b/src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js deleted file mode 100644 index daf0495..0000000 --- a/src/js/views/metrics/general-stats/Employees/DummyDataWorkers.js +++ /dev/null @@ -1 +0,0 @@ -export const DummyDataWorkers = [ { id: 4 }, { id: 5 }, { id: 3 }, { id: 2 }, { id: 1 } ] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Employees/Employees.js b/src/js/views/metrics/general-stats/Employees/Employees.js deleted file mode 100644 index 7ff942b..0000000 --- a/src/js/views/metrics/general-stats/Employees/Employees.js +++ /dev/null @@ -1,88 +0,0 @@ -import React from "react"; -import { PieChart } from '../../charts'; -import { EmployeesData } from "./EmployeesData"; - -// Colors -const purple = "#5c00b8"; -const lightPink = "#eb00eb"; - -export const Employees = () => { - - // Data for pie chart ------------------------------------------------------------------------------------- - - let pieData = EmployeesData.filter((item) => { return item.description !== "Total Employees" }) - - const employeesData = { - labels: pieData.map((data) => data.description), - datasets: [{ - label: "Employees", - data: pieData.map((data) => data.qty), - backgroundColor: [ - purple, lightPink - ], - }] - } - - // Return ---------------------------------------------------------------------------------------------------- - - return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Employees Table Starts */} -
    -

    Employees Table

    - - - - - - - - - - - - {EmployeesData.map((item, i) => { - return item.description === "Total Employees" ? ( - - - - - - ) : - ( - - - - - - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    -
    - {/* Employees Table Ends */} -
    -
    - {/* Left Column Ends */} - - {/* Right Column Starts */} -
    -
    - {/* Employees Chart Starts*/} -
    -

    Employees Chart

    - -
    - -
    -
    - {/* Employees Chart Ends*/} -
    -
    - {/* Right Column Ends */} -
    - ) -} diff --git a/src/js/views/metrics/general-stats/GeneralStats.js b/src/js/views/metrics/general-stats/GeneralStats.js index c6e23ff..68b817a 100644 --- a/src/js/views/metrics/general-stats/GeneralStats.js +++ b/src/js/views/metrics/general-stats/GeneralStats.js @@ -1,8 +1,17 @@ import React from "react"; -import { Employees } from "./Employees/Employees"; +import { JobSeekers } from "./JobSeekers/JobSeekers"; import { Hours } from "./Hours/Hours"; import { Shifts } from "./Shifts/Shifts"; +/** + * @function + * @description Creates a page with 3 tabs that show metrics about Shifts, Punctuality, and Hours. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires Hours + * @requires Shifts + * @requires JobSeekers + */ export const GeneralStats = () => { // Return ---------------------------------------------------------------------------------------------------- @@ -15,7 +24,7 @@ export const GeneralStats = () => { {/* Tabs Controller Ends */} @@ -37,11 +46,11 @@ export const GeneralStats = () => {
  • )CdQ3F!eKyF|9D2V!F-rhUpJ8J+l7g0OBBVM#THTI&O8)>EGR%u6Gd9D9pm5!S}%&6DxW}^f|7=Y zPoO3(pTZY#?(7(|!5}5Nn!D%DotZmlW)?smSMcEE<^aT$6gw#LlwubPI9BYTffL0! zyu-EPCnz{Y#ZR&1d{F!hr_NW!&#~mXis$jseXDo@U)-kR7sMBeUt-T&RQw9By@BF9 z3f?cpmw4m-R{RHncaC**(V--ipJ<~6LkW2fi6RVfh%vcYt9@z>&M0LBSf-Q|Et8wU zCt43_*JB)mHR71wb`K@~5Cizwp{`A2uuJ^_Bcl3k{7ree$8&@l?;^2nagS+NqCDBfkB?pJws=PbK~+A7|2 z{gCDJKI-i%m4LD$n{WIwWR|c+NRy`C1#)1sSBI7FiH6z-QkhY&Q_|%I3exQ zQ`X1M?cZH4^M&BSyr;2z$+^SZUMA*0001Z+HKHROw(}?!13=vX`$@Br+fGR zZ%e`5O6%Txi$Yrz0gF{}p>fY>OnlS0Uevf}oDXW;D{d2gcE<2)oFcV80@g$H)63L{HN*d{8kVzKVW(;E)$9N_%kx5Ku3R9WJbY?JW^G#k0Wdx>E$NBBVtKRLiL?sA*s%w`TdsNz1=+~FRNdB8&+@iBD0 zXFTC4C-8-Cwv(4U=LLQ~^Oa4^rG|OTr5?ItoaPMYxxh`%a*kVU z;HYGAjq6;IY{`*awo0DlOMw(hkrYdb(O28l;MYvSx*ChcQW4f^QL5UdE3HbqvbxB$pfSg`>Cj#;?~00;nMAg}==M6d%RaIhCe zARtS)01i=0um)3FSgr#ump{<1pq_<0a34Kp8x=7I1^|9 literal 0 HcmV?d00001 diff --git a/out/fonts/OpenSans-Regular-webfont.eot b/out/fonts/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..6bbc3cf58cb011a6b4bf3cb1612ce212608f7274 GIT binary patch literal 19836 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/out/fonts/OpenSans-Regular-webfont.woff b/out/fonts/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e231183dce4c7b452afc9e7799586fd285e146f4 GIT binary patch literal 22660 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 ML;wL%uV=LY00kR;TmS$7 literal 0 HcmV?d00001 diff --git a/out/global.html b/out/global.html new file mode 100644 index 0000000..3559e63 --- /dev/null +++ b/out/global.html @@ -0,0 +1,238 @@ + + + + + JSDoc: Global + + + + + + + + + + +

    {/* Hours Tab Ends */} - {/* Employees Tab Starts */} - {/* Tabs Content Ends */}
    diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js index 69e3010..1e2401f 100644 --- a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js +++ b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js @@ -1,161 +1,346 @@ export const DummyDataShifts = [ { + id: 1, + employees: [7, 4], clockin: [ { started_at: "2022-09-10T10:45:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z" + ended_at: "2022-09-10T15:00:09Z", + employee: 7, + shift: 1 + }, + { + started_at: "2022-09-10T15:35:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z", + employee: 7, + shift: 1 + }, + { + started_at: "2022-09-10T10:20:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T15:00:09Z", + employee: 4, + shift: 1 + }, + { + started_at: "2022-09-10T15:30:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z", + employee: 4, + shift: 1 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 2, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z" + ended_at: "2022-09-10T13:20:09Z", + employee: 3, + shift: 2 + }, + { + started_at: "2022-09-10T13:50:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T19:25:09Z", + employee: 3, + shift: 2 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 3, + employees: [3], clockin: [ { started_at: "2022-09-10T09:15:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:17:09Z" + ended_at: "2022-09-10T12:10:09Z", + employee: 3, + shift: 3 + }, + { + started_at: "2022-09-10T12:20:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T17:00:09Z", + employee: 3, + shift: 3 + }, + { + started_at: "2022-09-10T17:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:17:09Z", + employee: 3, + shift: 3 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 4, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:31:09Z" + ended_at: "2022-09-10T15:30:09Z", + employee: 3, + shift: 4 + }, + { + started_at: "2022-09-10T15:42:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T18:00:09Z", + employee: 3, + shift: 4 + }, + { + started_at: "2022-09-10T18:30:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:31:09Z", + employee: 3, + shift: 4 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 5, + employees: [4], clockin: [ { started_at: "2022-09-10T10:55:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:19:09Z" + ended_at: "2022-09-10T20:19:09Z", + employee: 4, + shift: 5 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 6, + employees: [4], clockin: [ { started_at: "2022-09-10T10:40:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z" + ended_at: "2022-09-10T19:25:09Z", + employee: 4, + shift: 6 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 7, + employees: [7], clockin: [ { started_at: "2022-09-10T11:00:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:20:09Z" + ended_at: "2022-09-10T14:10:09Z", + employee: 7, + shift: 7 + }, + { + started_at: "2022-09-10T14:28:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T19:20:09Z", + employee: 7, + shift: 7 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 8, + employees: [7], clockin: [ { started_at: "2022-09-10T10:14:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T21:35:09Z" + ended_at: "2022-09-10T16:30:09Z", + employee: 7, + shift: 8 + }, + { + started_at: "2022-09-10T17:00:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T21:35:09Z", + employee: 7, + shift: 8 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 9, + employees: [7], clockin: [ { started_at: "2022-09-10T10:08:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:15:09Z" + ended_at: "2022-09-10T15:00:09Z", + employee: 7, + shift: 9 + }, + { + started_at: "2022-09-10T15:36:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:15:09Z", + employee: 7, + shift: 9 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 10, + employees: [5], clockin: [ { started_at: "2022-09-10T10:40:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:40:09Z" + ended_at: "2022-09-10T14:00:09Z", + employee: 5, + shift: 10 + }, + { + started_at: "2022-09-10T14:40:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:40:09Z", + employee: 5, + shift: 10 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 11, + employees: [5], clockin: [ { started_at: "2022-09-10T08:00:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:45:09Z" + ended_at: "2022-09-10T13:00:09Z", + employee: 5, + shift: 11 + }, + { + started_at: "2022-09-10T13:45:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:45:09Z", + employee: 5, + shift: 11 + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T20:00:00Z" + }, + { + id: 12, + employees: [3], + clockin: [ + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T12:00:09Z", + employee: 3, + shift: 12 + }, + { + started_at: "2022-09-10T12:34:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z", + employee: 3, + shift: 12 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 13, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z" + ended_at: "2022-09-10T22:00:09Z", + employee: 3, + shift: 13 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 14, + employees: [3, 5], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z" + ended_at: "2022-09-10T22:00:09Z", + employee: 3, + shift: 14 + }, + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z", + employee: 5, + shift: 14 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" } -]; - - -// You may need to add employees for available hours; add more clock-ins for long breaks, and check distance in and out \ No newline at end of file +]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js b/src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js deleted file mode 100644 index daf0495..0000000 --- a/src/js/views/metrics/general-stats/Hours/DummyDataWorkers.js +++ /dev/null @@ -1 +0,0 @@ -export const DummyDataWorkers = [ { id: 4 }, { id: 5 }, { id: 3 }, { id: 2 }, { id: 1 } ] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/Hours.js b/src/js/views/metrics/general-stats/Hours/Hours.js index f930aa6..1bd2443 100644 --- a/src/js/views/metrics/general-stats/Hours/Hours.js +++ b/src/js/views/metrics/general-stats/Hours/Hours.js @@ -9,10 +9,19 @@ const darkTeal = "#009e9e"; const lightPink = "#eb00eb"; const darkPink = "#b200b2"; +/** + * @function + * @description Creates a page with a table and a graph of the hours worked and their trends. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires PieChart + * @requires HoursData + */ export const Hours = () => { // Data for pie chart ------------------------------------------------------------------------------------- + // Preparing data to be passed to the chart component const hoursData = { labels: HoursData.map((data) => data.description), datasets: [{ @@ -37,6 +46,7 @@ export const Hours = () => { + {/* Table columns */} @@ -45,7 +55,8 @@ export const Hours = () => { - {HoursData.map((item, i) => { + {/* Mapping the data to diplay it as table rows */} + {HoursData.map((item, i) => { return item.description === "Available Hours" ? ( diff --git a/src/js/views/metrics/general-stats/Hours/HoursData.js b/src/js/views/metrics/general-stats/Hours/HoursData.js index 802907b..d1e6b56 100644 --- a/src/js/views/metrics/general-stats/Hours/HoursData.js +++ b/src/js/views/metrics/general-stats/Hours/HoursData.js @@ -1,78 +1,331 @@ -import moment from "moment" -import { DummyDataShifts } from "./DummyDataShifts" -//import { DummyDataWorkers } from "./DummyDataWorkers" +import moment from "moment"; +import { DummyDataShifts } from "./DummyDataShifts"; +/** + * @function + * @description Takes in list a of shifts and generates data of the hours worked for Hours.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires DummyDataShifts + * @returns Array of objects + */ const HoursDataGenerator = () => { + + // 1st - Separation of shifts ---------------------------------------------------------- - // First array - let completeList = []; + // Array for shifts with multiple clock-ins + let multipleClockIns = []; + + // Array for single clock-ins made by single workers + let singleClockInSingleWorker = []; // Gathering both clock-ins and clock-outs DummyDataShifts.forEach((shift) => { - shift.clockin.forEach((clockIn) => { - if (shift.clockin.length > 0) { - completeList.push({ + if (shift.clockin.length > 1) { + multipleClockIns.push({ + starting_at: shift.starting_at, + ending_at: shift.ending_at, + clockin: shift.clockin, + id: shift.id, + employees: shift.employees + }); + } else if (shift.clockin.length === 1) { + shift.clockin.forEach((clockIn) => { + singleClockInSingleWorker.push({ + id: shift.id, starting_at: shift.starting_at, - started_at: clockIn.started_at, ending_at: shift.ending_at, + started_at: clockIn.started_at, ended_at: clockIn.ended_at }); + }); + } + }); + + // Setting up arrays for shifts with multiple + // clock-ins but different amount of workers + let multipleClockInsMultipleWorkers = []; + let multipleClockInsSingleWorker = []; + + // Separating shifts based on the number of workers present + multipleClockIns.forEach((shift) => { + if (shift.employees.length > 1) { + // Adding shifts to 'multipleClockInsMultipleWorkers' + multipleClockInsMultipleWorkers.push(shift.clockin); + } else if (shift.employees.length === 1) { + // Adding shifts to 'multipleClockInsSingleWorker' + multipleClockInsSingleWorker.push(shift.clockin); + } + }); + + // Array of multiple clock-ins with multiple workers, but organized + let MCIMWOrganized = []; + + // Adding shifts to 'MCIMWOrganized' + multipleClockInsMultipleWorkers.forEach((shift) => { + let newObj = shift.reduce((obj, value) => { + let key = value.employee; + if (obj[key] == null) obj[key] = []; + + obj[key].push(value); + return obj; + }, []); + + newObj.forEach((shift) => { + MCIMWOrganized.push(shift); + }); + }); + + // Array for the polished version of 'multipleClockInsMultipleWorkers' + let MCIMWPolished = []; + + // Array for single clock-ins made by single workers + // inside shifts with multiple workers present + let singleClockinsMultipleWorkers = []; + + // Separating shifts of multiple workers based on + // how many clock-ins each worker has + MCIMWOrganized.forEach((shift) => { + if (shift.length === 1) { + shift.forEach((clockIn) => { + singleClockinsMultipleWorkers.push(clockIn); + }); + } else if (shift.length > 1) { + MCIMWPolished.push(shift); + } + }); + + // Array for polished version of 'singleClockinsMultipleWorkers' + let SCIMWPolished = []; + + // Adding shifts to 'SCIMWPolished' + DummyDataShifts.forEach((originalShift) => { + singleClockinsMultipleWorkers.forEach((filteredShift) => { + if (originalShift.id === filteredShift.shift) { + SCIMWPolished.push({ + id: originalShift.id, + started_at: filteredShift.started_at, + ended_at: filteredShift.ended_at, + starting_at: originalShift.starting_at, + ending_at: originalShift.ending_at + }); } }); }); - // Adding all the scheduled hours from each shift - let scheduledHours = completeList.reduce( + // Combining all shifts with single clock-ins. These will not have break times. + let singleClockInsCombined = [...singleClockInSingleWorker, ...SCIMWPolished]; + + // Combining all shifts with multiple clock-ins. These will have break times. + let multipleClockInsCombined = [ + ...multipleClockInsSingleWorker, + ...MCIMWPolished + ]; + + // 2nd - Calculation of Hours and Minutes ---------------------------------------------- + + // Calculating scheduled hours of all single clock-in shifts --------------------------- + + let SCICScheduledHours = singleClockInsCombined.reduce( (total, { starting_at, ending_at }) => total + moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(), 0 ); - // Formatting scheduled hours - let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(0); + // Total scheduled hours + let SCICScheduledHoursF = parseInt( + (Math.round(SCICScheduledHours * 4) / 4).toFixed(0), + 10 + ); + + // Calculating worked hours of all single clock-in shifts ------------------------------ - // Adding all the worked hours from each shift - let workedHours = completeList.reduce( + let SCICWorkedHours = singleClockInsCombined.reduce( (total, { started_at, ended_at }) => total + moment.duration(moment(ended_at).diff(moment(started_at))).asHours(), 0 ); - // Formatting worked hours - let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(0); + // Total worked hours + let SCICWorkedHoursF = parseInt( + (Math.round(SCICWorkedHours * 4) / 4).toFixed(0), + 10 + ); + + // Extra worked hours + let extraWorkedHoursSingleClockIns = SCICWorkedHoursF - SCICScheduledHoursF; + + // Calculating scheduled hours of all multiple clock-in shifts ------------------------- + + // Array for scheduled minutes + let MCICScheduledMinutes = []; + + // Adding shifts to 'MCICScheduledMinutes' + multipleClockInsCombined.forEach((shift) => { + let shiftStart = moment(shift[0].started_at); + let shiftEnd = moment(shift[shift.length - 1].ended_at); + let id = shift[0].shift; + let diff = moment.duration(shiftEnd.diff(shiftStart)).asMinutes(); + + MCICScheduledMinutes.push({ + id: id, + employee: shift[0].employee, + scheduled_mins: diff + }); + }); + + // Total scheduled minutes + let TotalMCICScheduledMinutes = MCICScheduledMinutes.reduce((acc, obj) => { + return acc + obj.scheduled_mins; + }, 0); + + // Total scheduled hours + let MCICScheduledHours = + Math.floor(TotalMCICScheduledMinutes / 60) + SCICScheduledHoursF; + + // Calculating worked hours of all multiple clock-in shifts ---------------------------- + + // Array for worked minutes + let MCICWorkedMinutes = []; + + // Adding shifts to 'MCICWorkedMinutes' + multipleClockInsCombined.forEach((shift) => { + shift.forEach((clockIn) => { + let start = moment(clockIn.started_at); + let end = moment(clockIn.ended_at); + let id = clockIn.shift; + + let diff = moment.duration(end.diff(start)).asMinutes(); + + MCICWorkedMinutes.push({ + id: id, + employee: clockIn.employee, + worked_mins: diff + }); + }); + }); + + // Polished version of 'MCICWorkedMinutes' + let MCICWorkedMinutesPolished = MCICWorkedMinutes.reduce( + (result, { id, employee, worked_mins }) => { + let temp = result.find((o) => { + return o.id === id && o.employee === employee; + }); + if (!temp) { + result.push({ id, employee, worked_mins }); + } else { + temp.worked_mins += worked_mins; + } + return result; + }, + [] + ); + + // Total worked minutes + let TotalMCICWorkedMinutes = MCICWorkedMinutesPolished.reduce((acc, obj) => { + return acc + obj.worked_mins; + }, 0); + + // Total worked hours + let MCICWorkedHours = + Math.floor(TotalMCICWorkedMinutes / 60) + SCICWorkedHoursF; + + // Extra worked hours + let extraWorkedHoursMultipleClockIns = MCICWorkedHours - MCICScheduledHours; + + // Extra calculations ----------------------------------------------------------------- + + // Array for the break times + let breakTimes = []; + + // Calculating break times of every shift + MCICScheduledMinutes.forEach((scheduledShift) => { + MCICWorkedMinutesPolished.forEach((workedShift) => { + if ( + scheduledShift.id === workedShift.id && + scheduledShift.employee === workedShift.employee + ) { + let scheduled = scheduledShift.scheduled_mins; + let worked = workedShift.worked_mins; + + let diff = scheduled - worked; + + breakTimes.push({ + id: scheduledShift.id, + break_mins: diff + }); + } + }); + }); + + // Array for long breaks + let longBreaks = []; + + // Adding shifts to 'longBreaks' + breakTimes.forEach((shift) => { + if (shift.break_mins > 30) { + longBreaks.push(shift); + } + }); + + // Calculating worked hours + let workedHours = MCICWorkedHours + SCICWorkedHoursF; + + // Calculating scheduled hours + let scheduledHours = MCICScheduledHours + SCICScheduledHoursF; - // THIS IS A PLACEHOLDER, we double the hours worked to mimic available hours - let availableHours = (workedHoursFormatted * 2).toString() + // Calculating extra worked hours + let extraWorkedHours = + extraWorkedHoursSingleClockIns + extraWorkedHoursMultipleClockIns; + + // Setting up conditional rendering of extra worked hours + let qtyOfExtraWorkedHours = () => { + if (extraWorkedHours > 0) { + return extraWorkedHours; + } else { + return 0; + } + }; + + // 3rd - Setting up objects ----------------------------------------------------------- + + // THIS IS A PLACEHOLDER, this number should be + // the total hours available of all employees + let availableHours = 300; + + // Creating object for scheduled hours + let scheduledHoursObj = { + description: "Scheduled Hours", + qty: scheduledHours + }; // Creating object for worked hours let workedHoursObj = { - description: "Hours Worked", - qty: workedHoursFormatted + description: "Worked Hours", + qty: workedHours }; - // Calculating extra worked hours - let extraHours = workedHoursFormatted - scheduledHoursFormatted; - // Creating object for extra worked hours - let extraHoursObj = { - description: "Extra Hours Worked", - qty: extraHours + let extraWorkedHoursObj = { + description: "Extra Worked Hours", + qty: qtyOfExtraWorkedHours() }; // Creating object for long breaks let longBreaksObj = { description: "Long Breaks", - qty: "10" + qty: `${longBreaks.length}` }; // Generate semi-final list let semiFinalList = []; - // Adding object of worked hours to semi-final list + // Adding objects to semi-final list + semiFinalList.push(scheduledHoursObj); semiFinalList.push(workedHoursObj); - semiFinalList.push(extraHoursObj); + semiFinalList.push(extraWorkedHoursObj); semiFinalList.push(longBreaksObj); // Generating final array with percentages as new properties @@ -92,13 +345,14 @@ const HoursDataGenerator = () => { // Adding object of available hours to final list finalList.push(availableHoursObj); + // Adding IDs to each object in the array finalList.forEach((item, i) => { item.id = i + 1; }); // Returning the final array - return finalList -} + return finalList; +}; // Exporting the final array -export const HoursData = HoursDataGenerator() \ No newline at end of file +export const HoursData = HoursDataGenerator(); \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js b/src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js new file mode 100644 index 0000000..5759121 --- /dev/null +++ b/src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js @@ -0,0 +1,145 @@ +export const DummyDataShifts = [ + { + clockin: [ + { + started_at: "2022-09-19T10:20:09Z", + ended_at: "2022-09-19T19:10:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + employee: 5, + shift: 45, + } + ] + }, + { + clockin: [ + { + started_at: "2022-09-20T09:20:09Z", + ended_at: "2022-09-20T20:45:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + employee: 5, + shift: 49, + } + ] + }, + { + clockin: [ + { + started_at: "2022-09-21T10:00:09Z", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + employee: 4, + shift: 46, + } + ] + }, + { + clockin: [ + { + started_at: "2022-09-21T10:36:09Z", + ended_at: "2022-09-21T21:00:09Z", + automatically_closed: true, + created_at: "2022-09-10T19:00:08.811375Z", + updated_at: "2022-09-10T19:00:08.818207Z", + employee: 5, + shift: 47, + } + ] + }, + { + clockin: [ + { + started_at: "2022-09-10T09:00:09Z", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + employee: 4, + shift: 50, + } + ] + }, + { + clockin: [ + { + started_at: "2022-09-10T10:05:09Z", + ended_at: "2022-09-10T20:05:09Z", + automatically_closed: false, + created_at: "2022-09-10T19:28:08.811375Z", + updated_at: "2022-09-10T19:28:08.818207Z", + employee: 5, + shift: 50, + } + ] + }, + { + clockin: [ + { + started_at: "2022-01-10T10:05:09Z", + distance_out_miles: "0.000", + ended_at: "2022-01-10T20:05:09Z", + automatically_closed: false, + employee: 3, + shift: 50, + } + ] + }, + { + clockin: [ + { + started_at: "2022-04-10T10:05:09Z", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + employee: 3, + shift: 50, + } + ] + }, + { + clockin: [ + { + started_at: "2022-04-10T10:05:09Z", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + employee: 2, + shift: 50, + } + ] + }, + { + clockin: [ + { + started_at: "2022-04-10T10:05:09Z", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + employee: 2, + shift: 50, + } + ] + }, + { + clockin: [ + { + started_at: "2022-04-10T10:05:09Z", + ended_at: "2022-04-10T20:05:09Z", + automatically_closed: false, + employee: 1, + shift: 50, + } + ] + }, + { + clockin: [] + }, + { + clockin: [] + }, + { + clockin: [] + }, +]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js b/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js new file mode 100644 index 0000000..92bc195 --- /dev/null +++ b/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js @@ -0,0 +1,70 @@ +export const DummyDataWorkers = [ + { id: 4 }, + { id: 5 }, + { id: 3 }, + { id: 2 }, + { id: 1 } +] + +export const DummyDataNewWorkers = [ + { + id: 1, + created_at: "2022-01-27T23:12:48.975618Z" + }, + { + id: 2, + created_at: "2022-02-27T23:12:48.975618Z" + }, + { + id: 3, + created_at: "2022-03-27T23:12:48.975618Z" + }, + { + id: 4, + created_at: "2022-04-01T23:12:48.975618Z" + }, + { + id: 5, + created_at: "2022-05-27T23:12:48.975618Z" + }, + { + id: 6, + created_at: "2022-06-27T23:12:48.975618Z" + }, + { + id: 7, + created_at: "2022-07-27T23:12:48.975618Z" + }, + { + id: 8, + created_at: "2022-08-27T23:12:48.975618Z" + }, + { + id: 9, + created_at: "2022-09-01T23:12:48.975618Z" + }, + { + id: 10, + created_at: "2022-10-27T23:12:48.975618Z" + }, + { + id: 11, + created_at: "2022-11-27T23:12:48.975618Z" + }, + { + id: 12, + created_at: "2022-12-27T23:12:48.975618Z" + }, + { + id: 13, + created_at: "2021-10-27T23:12:48.975618Z" + }, + { + id: 14, + created_at: "2021-11-27T23:12:48.975618Z" + }, + { + id: 15, + created_at: "2021-12-27T23:12:48.975618Z" + } +] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js new file mode 100644 index 0000000..d6a144d --- /dev/null +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js @@ -0,0 +1,170 @@ +import React from "react"; +import { PieChart, BarChart } from '../../charts'; +import { JobSeekersData, NewJobSeekersData } from "./JobSeekersData"; + +// Colors +const purple = "#5c00b8"; +const lightPink = "#eb00eb"; +const darkTeal = "#009e9e"; +const green = "#06ff05"; + +/** + * @function + * @description Creates a page with 2 tables and 2 graphs of all the active/inactive and new job seekers. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires PieChart + * @requires JobSeekersData + */ +export const JobSeekers = () => { + + // Data for pie chart ------------------------------------------------------------------------------------- + + // Taking out the "Totals" from the chart view + let pieData = JobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view + + // Preparing data to be passed to the chart component + const jobSeekersData = { + labels: pieData.map((data) => data.description), + datasets: [{ + label: "Job Seekers", + data: pieData.map((data) => data.qty), + backgroundColor: [ + purple, lightPink + ], + }] + } + + // Data for bar chart ------------------------------------------------------------------------------------- + + // Taking out the "Totals" from the chart view + let barData = NewJobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view + + // Preparing data to be passed to the chart component + const newJobSeekersData = { + labels: barData.map((data) => data.description), + datasets: [{ + label: "New Job Seekers", + data: barData.map((data) => data.qty), + backgroundColor: [ + darkTeal, green + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Job Seekers Table Starts */} +
    +

    Job Seekers Table

    + +

    Description

    Quantity

    {item.description}

    + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {JobSeekersData.map((item, i) => { + return item.description === "Total Job Seekers" ? ( + + + + + + ) : + ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Job Seekers Table Ends */} +
    + +
    + {/* New Job Seekers Table Starts */} +
    +

    New Job Seekers Table

    + + + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {NewJobSeekersData.map((item, i) => { + return item.description === "Total Job Seekers" ? ( + + + + + + ) : + ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* New Job Seekers Table Ends */} +
    + + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Job Seekers Chart Starts*/} +
    +

    Job Seekers Chart

    + +
    + +
    +
    + {/* Job Seekers Chart Ends*/} +
    + +
    + {/* New Job Seekers Chart Starts*/} +
    +

    New Job Seekers Chart

    + +
    + +
    +
    + {/* New Job Seekers Chart Ends*/} +
    +
    + {/* Right Column Ends */} + + ) +} \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js new file mode 100644 index 0000000..53ebf48 --- /dev/null +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js @@ -0,0 +1,176 @@ +import { DummyDataShifts } from "./DummyDataShifts" +import { DummyDataWorkers, DummyDataNewWorkers } from "./DummyDataWorkers" +import moment from "moment"; + +// Today +const now = moment().format("YYYY-MM-DD") + +// Today, four weeks in the past +const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD") + + +// Job Seekers Data ------------------------------------------------------------------ + +/** + * @function + * @description Takes in list a of shifts and job seekers and generates data of inactive/active job seekers for JobSeekers.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires moment + * @requires DummyDataShifts + * @requires DummyDataWorkers + * @returns Array of objects + */ +const JobSeekersDataGenerator = () => { + + // Array for all clock-ins + let clockInsList = [] + + // Gathering the clock-ins of all the shifts + DummyDataShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (shift.clockin.length > 0) { + clockInsList.push(clockIn); + } + }) + }) + + // Array for all clock-ins + let recentClockIns = [] + + // Filtering out clock-ins that happened longer than 4 weeks ago + clockInsList.forEach((clockIn) => { + let clockInStart = moment(clockIn.started_at).format("YYYY-MM-DD"); + + if (clockInStart > fourWeeksBack && clockInStart < now) { + recentClockIns.push(clockIn) + } + }) + + // Array for worker ids + let workerIDs = [] + + // Gethering worker ids from recent clock-ins + recentClockIns.forEach((clockIn) => { + workerIDs.push(clockIn.employee) + }) + + // Filtering out repeated worker ids + let filteredWorkerIDs = [...new Set(workerIDs)]; + + // Calculating total, active, and inactive workers + let totalWorkers = DummyDataWorkers.length + let totalActiveWorkers = filteredWorkerIDs.length + let totalInactiveWorkers = totalWorkers - totalActiveWorkers + + // Setting up objects for the semi-final array + let activeWorkers = { + id: 1, + description: "Active Job Seekers", + qty: totalActiveWorkers + } + let inactiveWorkers = { + id: 2, + description: "Inactive Job Seekers", + qty: totalInactiveWorkers + } + + // Creating the semi-final array + let semiFinalList = [] + + // Adding objects to the semi-final array + semiFinalList.push(activeWorkers) + semiFinalList.push(inactiveWorkers) + + // Generating final array with percentages as new properties + let finalList = semiFinalList.map(({ id, description, qty }) => ({ + id, + description, + qty, + pct: ((qty * 100) / totalWorkers).toFixed(0) + })); + + // Generating the object of total workers + let totalJobSeekers = { + id: 3, + description: "Total Job Seekers", + qty: totalWorkers, + pct: "100" + } + + // Adding the object of total workers to the final array + finalList.push(totalJobSeekers) + + // Returning the final array + return finalList +} + +// Exporting the final array +export const JobSeekersData = JobSeekersDataGenerator() + +// Job Seekers Data ------------------------------------------------------------------ + +/** + * @function + * @description Takes in list a of job seekers and generates data of new job seekers for JobSeekers.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires moment + * @requires DummyDataNewWorkers + * @returns Array of objects + */ +const NewJobSeekersDataGenerator = () => { + + // Array for new workers + let newWorkersList = [] + + // Adding workers to 'newWorkersList' + DummyDataNewWorkers.forEach((worker) => { + let creation_date = moment(worker.created_at).format("YYYY-MM-DD"); + + if (creation_date > fourWeeksBack && creation_date < now) { + newWorkersList.push(worker) + } + }) + + let totalWorkers = DummyDataNewWorkers.length + let totalNewWorkers = newWorkersList.length + + // Setting up objects for the semi-final array + let newWorkers = { + id: 0, + description: "New Job Seekers", + qty: totalNewWorkers + } + + // Creating the semi-final array + let semiFinalList = [] + + // Adding objects to the semi-final array + semiFinalList.push(newWorkers) + + // Generating final array with percentages as new properties + let finalList = semiFinalList.map(({ id, description, qty }) => ({ + id, + description, + qty, + pct: ((qty * 100) / totalWorkers).toFixed(0) + })); + + // Generating the object of total workers + let totalJobSeekers = { + id: 1, + description: "Total Job Seekers", + qty: totalWorkers, + pct: "100" + } + + // Adding the object of total workers to the final array + finalList.push(totalJobSeekers) + + // Returning the final array + return finalList +} + +// Exporting the final array +export const NewJobSeekersData = NewJobSeekersDataGenerator() \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Shifts/Shifts.js b/src/js/views/metrics/general-stats/Shifts/Shifts.js index f634d25..3d0142a 100644 --- a/src/js/views/metrics/general-stats/Shifts/Shifts.js +++ b/src/js/views/metrics/general-stats/Shifts/Shifts.js @@ -9,12 +9,22 @@ const darkTeal = "#009e9e"; const lightPink = "#eb00eb"; const darkPink = "#b200b2"; +/** + * @function + * @description Creates a page with a table and a graph of all the shift statuses. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires ShiftsData + * @requires BarChart + */ export const Shifts = () => { // Data for bar chart ------------------------------------------------------------------------------------- - let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted "}) + // Taking out the "Totals" from the chart vie + let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted "}) // Taking out the "Totals" from the chart view + // Preparing data to be passed to the chart component const shiftsData = { labels: barData.map((data) => data.description), datasets: [{ @@ -39,6 +49,7 @@ export const Shifts = () => { + {/* Table columns */} @@ -47,6 +58,7 @@ export const Shifts = () => { + {/* Mapping the data to diplay it as table rows */} {ShiftsData.map((item, i) => { return item.description === "Total Shifts Posted" ? ( diff --git a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js index a8c5537..6a1e5a7 100644 --- a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js +++ b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js @@ -1,5 +1,13 @@ import { DummyData } from "./DummyData"; +/** + * @function + * @description Takes in list a of shifts and generates data of shift statuses for Shifts.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires DummyData + * @returns Array of objects + */ const ShiftsDataGenerator = () => { // First array diff --git a/src/js/views/metrics/metrics.js b/src/js/views/metrics/metrics.js index f2399ac..2f7a6a3 100644 --- a/src/js/views/metrics/metrics.js +++ b/src/js/views/metrics/metrics.js @@ -9,7 +9,16 @@ import { GeneralStats } from "./general-stats/GeneralStats"; import { store, search } from "../../actions"; - +/** + * @description Creates the view for Metrics page, which renders tabs with different modules being called inside each one. + * @since 09.28.22 by Paola Sanchez + * @author Paola Sanchez + * + * @requires Punctuality + * @requires Ratings + * @requires GeneralStats + * @requires Queue + */ export class Metrics extends Flux.DashView { constructor() { @@ -17,15 +26,15 @@ export class Metrics extends Flux.DashView { this.state = { // Queue Data ---------------------------------------------------------------------------------------------------------------------------- - // Workers - DocStatus: "", - empStatus: "unverified", - employees: [], + // Variables for the Workers' List + employees: [], // This will hold all the shifts. + DocStatus: "", //This is needed to check the verification status of employees. + empStatus: "unverified", //This is needed to filter out unverified employees. - // Shifts - allShifts: [], - session: Session.get(), - calendarLoading: true, + // Variables for the Shifts' List + allShifts: [], // This will hold all the shifts. + session: Session.get(), // This is needed to crate a user/employer session. + calendarLoading: true, // This is needed to fill up the list of shifts, not sure how. }; // This updates the state values @@ -35,14 +44,14 @@ export class Metrics extends Flux.DashView { // Generating the list of shifts and the list of employees --------------------------------------------------------------------------------------- componentDidMount() { - // Shifts + // Processes for the Shifts' List (Not sure how they work) const shifts = store.getState("shifts"); this.subscribe(store, "shifts", (_shifts) => { this.setState({ allShifts: _shifts, calendarLoading: false }); }); - // Workers + // Processes for the Workers' List (Not sure how they work) this.filter(); this.subscribe(store, "employees", (employees) => { @@ -53,7 +62,7 @@ export class Metrics extends Flux.DashView { this.handleStatusChange } - // Workers + // Processes for the Workers' List (Not sure how they work) componentWillUnmount() { this.handleStatusChange } @@ -98,7 +107,7 @@ export class Metrics extends Flux.DashView { const listOfShifts = this.state.allShifts; // --------------------------------------------- - // Filtering expired lists + // Filtering expired shifts // let listOfShifts = // (Array.isArray(shifts) && // shifts.length > 0 && diff --git a/src/js/views/metrics/punctuality/DummyDataShifts.js b/src/js/views/metrics/punctuality/DummyDataShifts.js index ef05c49..00689a2 100644 --- a/src/js/views/metrics/punctuality/DummyDataShifts.js +++ b/src/js/views/metrics/punctuality/DummyDataShifts.js @@ -1,197 +1,427 @@ export const DummyDataShifts = [ { + id: 1, + employees: [7, 4], clockin: [ { - started_at: "2022-09-10T10:45:09Z", + started_at: "2022-09-10T09:05:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T15:00:09Z", + employee: 7, + automatically_closed: false, + shift: 1 + }, + { + started_at: "2022-09-10T15:35:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z", + employee: 7, + automatically_closed: false, + shift: 1 + }, + { + started_at: "2022-09-10T10:00:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T15:00:09Z", + employee: 4, + automatically_closed: false, + shift: 1 + }, + { + started_at: "2022-09-10T15:30:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T20:00:09Z", - automatically_closed: false + employee: 4, + automatically_closed: false, + shift: 1 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 2, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z", - automatically_closed: false + ended_at: "2022-09-10T13:20:09Z", + employee: 3, + automatically_closed: false, + shift: 2 + }, + { + started_at: "2022-09-10T13:50:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:35:09Z", + employee: 3, + automatically_closed: false, + shift: 2 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 3, + employees: [3], clockin: [ { started_at: "2022-09-10T09:15:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", + ended_at: "2022-09-10T12:10:09Z", + employee: 3, + automatically_closed: false, + shift: 3 + }, + { + started_at: "2022-09-10T12:20:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T17:00:09Z", + employee: 3, + automatically_closed: false, + shift: 3 + }, + { + started_at: "2022-09-10T17:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", ended_at: "2022-09-10T20:17:09Z", - automatically_closed: false + employee: 3, + automatically_closed: false, + shift: 3 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 4, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", + ended_at: "2022-09-10T15:30:09Z", + employee: 3, + automatically_closed: false, + shift: 4 + }, + { + started_at: "2022-09-10T15:42:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T18:00:09Z", + employee: 3, + automatically_closed: false, + shift: 4 + }, + { + started_at: "2022-09-10T18:30:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", ended_at: "2022-09-10T20:31:09Z", - automatically_closed: false + employee: 3, + automatically_closed: false, + shift: 4 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 5, + employees: [4], clockin: [ { - started_at: "2022-09-10T10:55:09Z", + started_at: "2022-09-10T10:00:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T20:19:09Z", - automatically_closed: false + employee: 4, + automatically_closed: false, + shift: 5 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 6, + employees: [4], clockin: [ { started_at: "2022-09-10T10:40:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T19:25:09Z", - automatically_closed: false + employee: 4, + automatically_closed: false, + shift: 6 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 7, + employees: [7], clockin: [ { - started_at: "2022-09-10T11:00:09Z", + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T14:10:09Z", + employee: 7, + automatically_closed: false, + shift: 7 + }, + { + started_at: "2022-09-10T14:28:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T19:20:09Z", - automatically_closed: false + employee: 7, + automatically_closed: false, + shift: 7 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 8, + employees: [7], clockin: [ { started_at: "2022-09-10T10:14:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", + ended_at: "2022-09-10T16:30:09Z", + employee: 7, + automatically_closed: false, + shift: 8 + }, + { + started_at: "2022-09-10T17:00:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", ended_at: "2022-09-10T21:35:09Z", - automatically_closed: false + employee: 7, + automatically_closed: false, + shift: 8 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 9, + employees: [7], clockin: [ { started_at: "2022-09-10T10:08:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:15:09Z", - automatically_closed: false + ended_at: "2022-09-10T15:00:09Z", + employee: 7, + automatically_closed: false, + shift: 9 + }, + { + started_at: "2022-09-10T15:36:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T20:00:09Z", + employee: 7, + automatically_closed: true, + shift: 9 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 10, + employees: [5], clockin: [ { - started_at: "2022-09-10T10:40:09Z", + started_at: "2022-09-10T10:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T14:00:09Z", + employee: 5, + automatically_closed: false, + shift: 10 + }, + { + started_at: "2022-09-10T14:40:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T20:40:09Z", - automatically_closed: false + employee: 5, + automatically_closed: false, + shift: 10 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 11, + employees: [5], clockin: [ { started_at: "2022-09-10T08:00:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", + ended_at: "2022-09-10T13:00:09Z", + employee: 5, + automatically_closed: false, + shift: 11 + }, + { + started_at: "2022-09-10T13:45:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", ended_at: "2022-09-10T20:45:09Z", - automatically_closed: false + employee: 5, + automatically_closed: false, + shift: 11 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 12, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", + ended_at: "2022-09-10T12:00:09Z", + employee: 3, + automatically_closed: false, + shift: 12 + }, + { + started_at: "2022-09-10T12:34:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", ended_at: "2022-09-10T20:00:09Z", - automatically_closed: false + employee: 3, + automatically_closed: true, + shift: 12 } ], starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z", + ending_at: "2022-09-10T20:00:00Z" }, { + id: 13, + employees: [3], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T22:00:09Z", - automatically_closed: false + employee: 3, + automatically_closed: true, + shift: 13 } ], starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" + ending_at: "2022-09-10T22:00:00Z" }, { + id: 14, + employees: [3, 5], clockin: [ { started_at: "2022-09-10T09:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", ended_at: "2022-09-10T22:00:09Z", - automatically_closed: true, + employee: 3, + automatically_closed: false, + shift: 14 + }, + { + started_at: "2022-09-10T09:10:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z", + employee: 5, + automatically_closed: false, + shift: 14 } ], starting_at: "2022-09-10T10:00:00Z", ending_at: "2022-09-10T20:00:00Z" }, { + id: 15, + employees: [3], clockin: [ { - started_at: "2022-09-10T09:10:09Z", + started_at: "2022-09-10T10:10:09Z", distance_in_miles: "0.009", distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - automatically_closed: true + ended_at: "2022-09-10T12:00:09Z", + employee: 3, + automatically_closed: false, + shift: 15 + }, + { + started_at: "2022-09-10T12:30:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z", + employee: 3, + automatically_closed: true, + shift: 15 } ], starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" + ending_at: "2022-09-10T22:00:00Z" + }, + { + id: 16, + employees: [3], + clockin: [ + { + started_at: "2022-09-10T10:05:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T12:00:09Z", + employee: 3, + automatically_closed: false, + shift: 16 + }, + { + started_at: "2022-09-10T12:30:09Z", + distance_in_miles: "0.009", + distance_out_miles: "0.000", + ended_at: "2022-09-10T22:00:09Z", + employee: 3, + automatically_closed: true, + shift: 16 + } + ], + starting_at: "2022-09-10T10:00:00Z", + ending_at: "2022-09-10T22:00:00Z" } -]; +]; \ No newline at end of file diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index 23be3fc..89bd8b4 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -7,40 +7,58 @@ const purple = "#5c00b8"; const lightTeal = "#00ebeb"; const darkTeal = "#009e9e"; const green = "#06ff05"; - +const lightPink = "#eb00eb"; +const darkPink = "#b200b2"; + +/** + * @function + * @description Creates a page with 2 tables and 2 graphs of all the clock-in and clock-out trends. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires PieChart + * @requires ClockInsData + * @requires ClockOutsData + */ export const Punctuality = () => { - // Data for pie charts ------------------------------------------------------------------------------------- // Clock-Ins - let dataCI = ClockInsData.filter((item) => { return item.description !== "Total Clock-Ins" }) + // Taking out the "Totals" from the chart view + let dataCI = ClockInsData.filter((item) => { + return item.description !== "Total Clock-Ins"; + }); // Taking out the "Totals" from the chart view + // Preparing data to be passed to the chart component const clockInsData = { labels: dataCI.map((data) => data.description), - datasets: [{ - label: "Clock-Ins", - data: dataCI.map((data) => data.qty), - backgroundColor: [ - purple, lightTeal - ], - }] - } + datasets: [ + { + label: "Clock-Ins", + data: dataCI.map((data) => data.qty), + backgroundColor: [green, lightTeal, darkPink] + } + ] + }; // Clock-Outs - let dataCO = ClockOutsData.filter((item) => { return item.description !== "Total Clock-Outs" }) + // Taking out the "Totals" from the chart view + let dataCO = ClockOutsData.filter((item) => { + return item.description !== "Total Clock-Outs"; + }); + // Preparing data to be passed to the chart component const clockOutsData = { labels: dataCO.map((data) => data.description), - datasets: [{ - label: "Clock-Outs", - data: dataCO.map((data) => data.qty), - backgroundColor: [ - green, darkTeal, purple - ], - }] - } + datasets: [ + { + label: "Clock-Outs", + data: dataCO.map((data) => data.qty), + backgroundColor: [purple, darkTeal, lightTeal, lightPink] + } + ] + }; // Return ---------------------------------------------------------------------------------------------------- @@ -55,6 +73,7 @@ export const Punctuality = () => {

    Description

    Quantity

    + {/* Table columns */} @@ -63,6 +82,7 @@ export const Punctuality = () => { + {/* Mapping the data to diplay it as table rows */} {ClockInsData.map((item, i) => { return item.description === "Total Clock-Ins" ? ( @@ -91,6 +111,7 @@ export const Punctuality = () => {

    Description

    Quantity

    + {/* Table columns */} @@ -99,6 +120,7 @@ export const Punctuality = () => { + {/* Mapping the data to diplay it as table rows */} {ClockOutsData.map((item, i) => { return item.description === "Total Clock-Outs" ? ( diff --git a/src/js/views/metrics/punctuality/PunctualityData.js b/src/js/views/metrics/punctuality/PunctualityData.js index 71f8339..c5e89d5 100644 --- a/src/js/views/metrics/punctuality/PunctualityData.js +++ b/src/js/views/metrics/punctuality/PunctualityData.js @@ -3,14 +3,24 @@ import moment from "moment"; // Clock-Ins Data ------------------------------------------------------------------ +/** + * @function + * @description Takes in list a of shifts and generates data of clock-in trends for Punctuality.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires moment + * @requires DummyDataShifts + * @returns Array of objects + */ const ClockInsDataGenerator = () => { - // Clock-ins array let clockIns = []; + let totalPunches = []; // Sorting the shifts DummyDataShifts.forEach((shift) => { shift.clockin.forEach((clockIn) => { + totalPunches.push(clockIn); if (shift.clockin.length > 0) { // Gathering the clock-ins clockIns.push({ @@ -24,6 +34,7 @@ const ClockInsDataGenerator = () => { // Setting up counters let earlyClockins = 0; let lateClockins = 0; + let onTimeClockins = 0; // Increasing clock-in counters clockIns.forEach((shift) => { @@ -36,6 +47,8 @@ const ClockInsDataGenerator = () => { lateClockins++; } else if (startDiff <= -30) { earlyClockins++; + } else { + onTimeClockins++; } }); @@ -48,6 +61,10 @@ const ClockInsDataGenerator = () => { description: "Late Clock-Ins", qty: lateClockins }; + let onTimeClockinsObj = { + description: "On Time Clock-Ins", + qty: onTimeClockins + }; // Setting up base array for all objects let cleanedClockIns = []; @@ -55,6 +72,7 @@ const ClockInsDataGenerator = () => { // Pushing objects to base array cleanedClockIns.push(earlyClockinsObj); cleanedClockIns.push(lateClockinsObj); + cleanedClockIns.push(onTimeClockinsObj); // Setting up totals let totalClockIns = clockIns.length; @@ -82,17 +100,24 @@ const ClockInsDataGenerator = () => { }); // Returning clock-ins array - return pctClockIns -} + return pctClockIns; +}; // Exporting clock-ins array -export const ClockInsData = ClockInsDataGenerator() - +export const ClockInsData = ClockInsDataGenerator(); // Clock-Outs Data ----------------------------------------------------------------- +/** + * @function + * @description Takes in list a of shifts and generates data of clock-out trends for Punctuality.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires moment + * @requires DummyDataShifts + * @returns Array of objects + */ const ClockOutsDataGenerator = () => { - // Clock-outs array let clockOuts = []; @@ -113,7 +138,8 @@ const ClockOutsDataGenerator = () => { // Setting up counters let earlyClockouts = 0; let lateClockouts = 0; - let forgotClockOut= 0; + let onTimeClockouts = 0; + let forgotClockOut = 0; // Increasing clock-out counters clockOuts.forEach((shift) => { @@ -126,7 +152,9 @@ const ClockOutsDataGenerator = () => { lateClockouts++; } else if (endDiff <= -30) { earlyClockouts++; - } + } else { + onTimeClockouts++; + } }); // Increasing forgotClockOut counter @@ -145,6 +173,10 @@ const ClockOutsDataGenerator = () => { description: "Late Clock-Outs", qty: lateClockouts }; + let onTimeClockoutsObj = { + description: "On Time Clock-Outs", + qty: onTimeClockouts + }; let forgotClockOutObj = { description: "Forgotten Clock-Outs", qty: forgotClockOut @@ -156,12 +188,13 @@ const ClockOutsDataGenerator = () => { // Pushing objects to base array cleanedClockOuts.push(earlyClockoutsObj); cleanedClockOuts.push(lateClockoutsObj); + cleanedClockOuts.push(onTimeClockoutsObj); cleanedClockOuts.push(forgotClockOutObj); // Setting up totals let totalClockOuts = clockOuts.length; - // Generating percentages as new properties + // Generating percentages as new properties let pctClockOuts = cleanedClockOuts.map(({ description, qty }) => ({ description, qty, @@ -184,8 +217,8 @@ const ClockOutsDataGenerator = () => { }); // Returning clock-outs array - return pctClockOuts -} + return pctClockOuts; +}; // Exporting clock-outs array -export const ClockOutsData = ClockOutsDataGenerator() \ No newline at end of file +export const ClockOutsData = ClockOutsDataGenerator(); \ No newline at end of file diff --git a/src/js/views/metrics/queue/DummyDataShifts.js b/src/js/views/metrics/queue/DummyDataShifts.js index 974eae9..e7adc92 100644 --- a/src/js/views/metrics/queue/DummyDataShifts.js +++ b/src/js/views/metrics/queue/DummyDataShifts.js @@ -1,343 +1,328 @@ export const DummyDataShifts = [ - { - id: 45, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 + { + id: 1, + employees: [5, 4], + clockin: [ + { + started_at: "2022-10-03T09:00:00Z", + ended_at: "2022-10-03T15:00:00Z", + employee: 5, + shift: 1 }, - position: { - title: "Server", - id: 1 + { + started_at: "2022-10-03T15:00:00Z", + ended_at: "2022-10-03T20:10:00Z", + employee: 5, + shift: 1 }, - status: "COMPLETED", - clockin: [ - { - id: 16, - started_at: "2022-09-19T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-19T15:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 45, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-19T10:00:00Z", - ending_at: "2022-09-19T20:00:00Z", - created_at: "2022-09-10T19:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - }, - { - id: 49, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 + { + started_at: "2022-10-03T10:05:00Z", + ended_at: "2022-10-03T15:00:00Z", + employee: 4, + shift: 1 }, - position: { - title: "Server", - id: 1 + { + started_at: "2022-10-03T15:00:00Z", + ended_at: "2022-10-03T20:15:00Z", + employee: 4, + shift: 1 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 2, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T09:30:09Z", + ended_at: "2022-10-03T13:00:00Z", + employee: 5, + shift: 2 }, - status: "COMPLETED", - clockin: [ - { - id: 18, - started_at: "2022-09-20T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-20T15:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 49, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-20T10:00:00Z", - ending_at: "2022-09-20T20:00:00Z", - created_at: "2022-09-10T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - }, - { - id: 46, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 + { + started_at: "2022-10-03T13:00:00Z", + ended_at: "2022-10-03T20:25:00Z", + employee: 5, + shift: 2 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 3, + employees: [4], + clockin: [ + { + started_at: "2022-10-03T09:00:00Z", + ended_at: "2022-10-03T12:07:00Z", + employee: 4, + shift: 3 }, - position: { - title: "Server", - id: 1 + { + started_at: "2022-10-03T12:20:00Z", + ended_at: "2022-10-03T17:00:00Z", + employee: 4, + shift: 3 }, - status: "COMPLETED", - clockin: [ - { - id: 17, - started_at: "2022-09-21T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-21T15:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - status: "PENDING", - employee: 4, - shift: 46, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-21T10:00:00Z", - ending_at: "2022-09-21T20:00:00Z", - created_at: "2022-09-10T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [4] - }, - { - id: 47, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 + { + started_at: "2022-10-03T17:00:00Z", + ended_at: "2022-10-03T20:20:00Z", + employee: 4, + shift: 3 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 4, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T09:10:09Z", + ended_at: "2022-10-03T15:30:09Z", + employee: 5, + shift: 4 }, - position: { - title: "Server", - id: 1 + { + started_at: "2022-10-03T15:42:09Z", + ended_at: "2022-10-03T18:00:09Z", + employee: 5, + shift: 4 }, - status: "FILLED", - clockin: [], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-22T10:00:00Z", - ending_at: "2022-09-22T20:30:00Z", - created_at: "2022-09-15T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - }, - { - id: 48, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 + { + started_at: "2022-10-03T18:30:09Z", + ended_at: "2022-10-03T20:31:09Z", + employee: 5, + shift: 4 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 5, + employees: [4], + clockin: [ + { + started_at: "2022-10-03T10:00:09Z", + ended_at: "2022-10-03T20:19:09Z", + employee: 4, + shift: 5 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 6, + employees: [4], + clockin: [ + { + started_at: "2022-10-03T10:40:09Z", + ended_at: "2022-10-03T19:25:09Z", + employee: 4, + shift: 6 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 7, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T09:10:09Z", + ended_at: "2022-10-03T14:10:09Z", + employee: 5, + shift: 7 }, - position: { - title: "Server", - id: 1 + { + started_at: "2022-10-03T14:28:09Z", + ended_at: "2022-10-03T19:20:09Z", + employee: 5, + shift: 7 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 8, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T10:14:09Z", + ended_at: "2022-10-03T16:30:09Z", + employee: 5, + shift: 8 }, - status: "FILLED", - clockin: [], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-23T10:00:00Z", - ending_at: "2022-09-23T20:10:00Z", - created_at: "2022-09-15T19:21:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [4] - }, - { - id: 50, - venue: { - title: "250 Catalonia Avenue", - id: 30, - latitude: "25.744481", - longitude: "-80.259618", - street_address: "250 Catalonia Avenue, Coral Gables, FL, USA", - zip_code: 33134 + { + started_at: "2022-10-03T17:00:09Z", + ended_at: "2022-10-03T21:35:09Z", + employee: 5, + shift: 8 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 9, + employees: [4], + clockin: [ + { + started_at: "2022-10-03T10:08:09Z", + ended_at: "2022-10-03T15:00:09Z", + employee: 4, + shift: 9 }, - position: { - title: "Server", - id: 1 + { + started_at: "2022-10-03T15:36:09Z", + ended_at: "2022-10-03T20:00:09Z", + employee: 4, + shift: 9 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 10, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T10:10:09Z", + ended_at: "2022-10-03T14:00:09Z", + employee: 5, + shift: 10 }, - status: "COMPLETED", - clockin: [ - { - id: 16, - started_at: "2022-09-10T10:00:09Z", - latitude_in: "25.74446444000", - longitude_in: "-80.25952809000", - distance_in_miles: "0.009", - latitude_out: "0.00000000000", - longitude_out: "0.00000000000", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - status: "PENDING", - employee: 5, - shift: 50, - author: 95 - } - ], - maximum_allowed_employees: 1, - minimum_hourly_rate: "8.0", - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z", - created_at: "2022-09-10T19:34.638331Z", - description: "", - employer: 80, - author: null, - candidates: [], - employees: [5] - } - ]; - - /* - FILLED + CLOCK-IN // NO CLOCK-OUT - - { - "id": 45, - "venue": { - "title": "250 Catalonia Avenue", - "id": 30, - "latitude": "25.744481", - "longitude": "-80.259618", - "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Server", - "id": 1 - }, - "status": "FILLED", - "clockin": [ - { - "id": 16, - "started_at": "2022-09-94T19:28:09Z", - "latitude_in": "25.74446444000", - "longitude_in": "-80.25952809000", - "distance_in_miles": "0.009", - "latitude_out": "0.00000000000", - "longitude_out": "0.00000000000", - "distance_out_miles": "0.000", - "ended_at": null, - "automatically_closed": false, - "created_at": "2022-09-19T19:28:08.811375Z", - "updated_at": "2022-09-19T19:28:08.818207Z", - "status": "PENDING", - "employee": 19, - "shift": 45, - "author": 95 - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "8.0", - "starting_at": "2022-09-28T19:30:00Z", - "ending_at": "2022-09208T21:30:00Z", - "created_at": "2022-09-19T19:21:34.638331Z", - "description": "", - "employer": 80, - "author": null, - "candidates": [], - "employees": [ - 19 - ] - } - - COMPLETED - - { - "id": 4335, - "venue": { - "title": "The Club of Knights", - "id": 47, - "latitude": "25.744510", - "longitude": "-80.260054", - "street_address": "270 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Cleaning Specialist", - "id": 41 - }, - "status": "COMPLETED", - "clockin": [ - { - "id": 3382, - "started_at": "2021-08-99T11:00:00Z", - "latitude_in": "25.74440950000", - "longitude_in": "-80.25995216000", - "distance_in_miles": "0.150", - "latitude_out": "25.74440950000", - "longitude_out": "-80.25995216000", - "distance_out_miles": "0.150", - "ended_at": "2021-08-09T17:00:00Z", - "automatically_closed": false, - "created_at": "2021-08-12T14:15:29.510326Z", - "updated_at": "2021-08-12T14:15:29.510343Z", - "status": "PENDING", - "employee": 179, - "shift": 4335, - "author": null - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "12.5", - "starting_at": "2021-08-09T11:00:00Z", - "ending_at": "2021-08209T19:00:04Z", - "created_at": "2021-08-12T14:09:11.474735Z", - "description": "", - "employer": 1, - "author": null, - "candidates": [], - "employees": [] - } - */ - \ No newline at end of file + { + started_at: "2022-10-03T14:40:09Z", + ended_at: "2022-10-03T20:40:09Z", + employee: 5, + shift: 10 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 11, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T08:00:09Z", + ended_at: "2022-10-03T13:00:09Z", + employee: 5, + shift: 11 + }, + { + started_at: "2022-10-03T13:45:09Z", + ended_at: "2022-10-03T20:45:09Z", + employee: 5, + shift: 11 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 12, + employees: [4], + clockin: [ + { + started_at: "2022-10-03T09:10:09Z", + ended_at: "2022-10-03T12:00:09Z", + employee: 4, + shift: 12 + }, + { + started_at: "2022-10-03T12:34:09Z", + ended_at: "2022-10-03T20:00:09Z", + employee: 4, + shift: 12 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 13, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T09:10:09Z", + ended_at: "2022-10-03T22:00:09Z", + employee: 5, + shift: 13 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T22:00:00Z" + }, + { + id: 14, + employees: [4, 5], + clockin: [ + { + started_at: "2022-10-03T09:10:09Z", + ended_at: "2022-10-03T22:00:09Z", + employee: 4, + shift: 14 + }, + { + started_at: "2022-10-03T09:10:09Z", + ended_at: "2022-10-03T22:00:09Z", + employee: 5, + shift: 14 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T20:00:00Z" + }, + { + id: 15, + employees: [4], + clockin: [ + { + started_at: "2022-10-03T10:10:09Z", + ended_at: "2022-10-03T12:00:09Z", + employee: 4, + shift: 15 + }, + { + started_at: "2022-10-03T12:30:09Z", + ended_at: "2022-10-03T22:00:09Z", + employee: 4, + shift: 15 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T22:00:00Z" + }, + { + id: 16, + employees: [5], + clockin: [ + { + started_at: "2022-10-03T10:05:09Z", + ended_at: "2022-10-03T12:00:09Z", + employee: 5, + shift: 16 + }, + { + started_at: "2022-10-03T12:30:09Z", + ended_at: "2022-10-03T22:00:09Z", + employee: 5, + shift: 16 + } + ], + starting_at: "2022-10-03T10:00:00Z", + ending_at: "2022-10-03T22:00:00Z" + } +]; \ No newline at end of file diff --git a/src/js/views/metrics/queue/Queue.js b/src/js/views/metrics/queue/Queue.js index 96671f7..0af86f7 100644 --- a/src/js/views/metrics/queue/Queue.js +++ b/src/js/views/metrics/queue/Queue.js @@ -3,20 +3,37 @@ import "react-datepicker/dist/react-datepicker.css"; import DatePicker from "react-datepicker"; import moment from "moment"; -import { WorkerStats } from "./WorkerStats"; +import { QueueData } from "./QueueData"; import { Button } from "../../../components/index"; import { DummyDataShifts } from "./DummyDataShifts"; import { DummyDataWorkers } from "./DummyDataWorkers"; - +/** + * @function + * @description Creates a page with a DatePicker and table of all employees with their worked/scheduled hours. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires moment + * @requires DatePicker + * @requires QueueData + * @requires Button + * @requires DummyDataShifts + * @requires DummyDataWorkers + * @param {object} props - Contains an array of all shifts, and an array of all workers + */ export const Queue = (props) => { // Setting up my variables --------------------------------------------------------------------------------- + // Date selected through the DatePicker const [selectedDate, setSelectedDate] = useState(new Date()); + + // Monday of X week (default: current week) const [start, setStart] = useState( moment().startOf("isoWeek").format("YYYY-MM-DD") ); + + // Sunday of X week (default: current week) const [end, setEnd] = useState( moment().endOf("isoWeek").format("YYYY-MM-DD") ); @@ -115,7 +132,7 @@ export const Queue = (props) => {
    {workers?.map((singleWorker, i) => { return ( - { - - // Setting up my variables --------------------------------------------------------------------------------- - +/** + * @function + * @description Creates a table of all employees with their worked/scheduled hours for Queue.js + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires moment + * @requires Avatar + * @requires Button + * @requires Theme + */ +export const QueueData = (props) => { + + // Setting up my variables -------------------------------- const worker = props.worker; const shifts = props.shifts; - // Scheduled Hours ----------------------------------------------------------------------------------------- - - // Filtering scheduled Shifts - const workerShiftsScheduled = []; + // Worker Shifts ------------------------------------------ + const workerShifts = []; shifts.forEach((shift) => { shift.employees.forEach((employee) => { if (employee === worker.id) { - workerShiftsScheduled.push(shift); + workerShifts.push(shift); } }); }); - // Calculating total scheduled hours - let scheduledHours = workerShiftsScheduled.reduce( - (total, { starting_at, ending_at }) => - total + - moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(), - 0 - ); + // Worker Clock-ins --------------------------------------- + let workerClockIns = []; + + workerShifts.forEach((shift) => { + shift.clockin.forEach((clockIn) => { + if (clockIn.employee === worker.id) { + workerClockIns.push(clockIn); + } + }); + }); - let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(2); + // Scheduled Hours --------------------------------------- + let scheduledHours = []; - // Worked Hours ------------------------------------------------------------------------------------------------ + workerShifts.forEach((shift) => { + let start = moment(shift.starting_at); + let end = moment(shift.ending_at); - // Filtering worked Shifts - const workerShiftsClockedIn = []; + let diff = moment.duration(end.diff(start)).asHours(); - workerShiftsScheduled.forEach((shift) => { - shift.clockin.forEach((clockIn) => { - if (shift.clockin.length > 0) { - workerShiftsClockedIn.push(clockIn); - } + scheduledHours.push({ + id: shift.id, + scheduled_hours: diff }); }); - // Calculating total worked hours - let workedHours = workerShiftsClockedIn.reduce( - (total, { started_at, ended_at }) => - total + - moment.duration(moment(ended_at).diff(moment(started_at))).asHours(), - 0 - ); + let totalScheduledHours = scheduledHours.reduce((acc, obj) => { + return acc + obj.scheduled_hours; + }, 0); + + let totalScheduledHoursF = totalScheduledHours.toFixed(2) + + // Worked Hours ---------------------------------------- + let workedHours = []; + + workerClockIns.forEach((shift) => { + let start = moment(shift.started_at); + let end = moment(shift.ended_at); + + let diff = moment.duration(end.diff(start)).asHours(); + + workedHours.push({ + id: shift.id, + worked_hours: diff + }); + }); + + let totalWorkedHours = workedHours.reduce((acc, obj) => { + return acc + obj.worked_hours; + }, 0); - let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(2); + let totalWorkedHoursF = totalWorkedHours.toFixed(2) // Return ------------------------------------------------------------------------------------------------------ @@ -73,21 +100,21 @@ export const WorkerStats = (props) => {
    {`${worker.user.first_name} ${worker.user.last_name}`}
    -
    {worker.rating == null ? "No rating available" : worker.rating > 1 ? `Rating: ${worker.rating} stars` : `Rating: ${worker.rating} star`}
    +
    {worker.rating == null ? "No rating available" : worker.rating > 1 ? `Rating: ${worker.rating} stars` : `Rating: ${worker.rating} star`}
    {/* Employee Image/Name/Rating Ends */} {/* Scheduled Hours Starts */}
    -

    {`Scheduled Hours: ${scheduledHoursFormatted}`}

    +

    {`Scheduled Hours: ${totalScheduledHoursF}`}

    {/* Scheduled Hours Ends */} {/* Worked Hours Starts */}
    -

    {`Worked Hours: ${workedHoursFormatted}`}

    +

    {`Worked Hours: ${totalWorkedHoursF}`}

    {/* Worked Hours Ends */} diff --git a/src/js/views/metrics/ratings/Ratings.js b/src/js/views/metrics/ratings/Ratings.js index 929a0b1..3e81dd0 100644 --- a/src/js/views/metrics/ratings/Ratings.js +++ b/src/js/views/metrics/ratings/Ratings.js @@ -10,12 +10,23 @@ const green = "#06ff05"; const lightPink = "#eb00eb"; const darkPink = "#b200b2"; +/** + * @function + * @description Creates a view of the number of ratings per worker. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires PieChart + * @requires RatingsData + * @returns A table and a chart displaying the ratings data + */ export const Ratings = () => { // Data for pie chart ------------------------------------------------------------------------------------- + // Taking out the "Totals" from the chart view let pieData = RatingsData.filter((item) => { return item.rating !== "Total Employees" }) + // Preparing data to be passed to the chart component const ratingsData = { labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }), datasets: [{ @@ -41,6 +52,7 @@ export const Ratings = () => {

    Description

    Quantity

    + {/* Table columns */} @@ -49,6 +61,7 @@ export const Ratings = () => { + {/* Mapping the data to diplay it as table rows */} {RatingsData.map((item, i) => { return item.rating === null ? ( @@ -89,7 +102,7 @@ export const Ratings = () => { - {/* Ratings Ends */} + {/* Ratings Chart Ends */} {/* Right Column Ends */} diff --git a/src/js/views/metrics/ratings/RatingsData.js b/src/js/views/metrics/ratings/RatingsData.js index 6cf1d6b..f4f0d41 100644 --- a/src/js/views/metrics/ratings/RatingsData.js +++ b/src/js/views/metrics/ratings/RatingsData.js @@ -1,5 +1,13 @@ import { DummyDataWorkers } from "./DummyDataWorkers"; +/** + * @function + * @description Takes in list a of employees and generates data of rating trends for Ratings.js. + * @since 09.29.22 by Paola Sanchez + * @author Paola Sanchez + * @requires DummyDataWorkers + * @returns Array of objects + */ const RatingDataGenerator = () => { // First array From ef0268487ea3332ff50dbe8602f40dbb15eb1b7c Mon Sep 17 00:00:00 2001 From: paola9896 Date: Tue, 4 Oct 2022 15:14:07 +0000 Subject: [PATCH 06/10] Improved Job Seekers and Punctuality --- .../JobSeekers/DummyDataWorkers.js | 30 +++++++++---------- .../general-stats/JobSeekers/JobSeekers.js | 9 ++---- .../views/metrics/punctuality/Punctuality.js | 2 +- 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js b/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js index 92bc195..29cb4d7 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js +++ b/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js @@ -9,62 +9,62 @@ export const DummyDataWorkers = [ export const DummyDataNewWorkers = [ { id: 1, - created_at: "2022-01-27T23:12:48.975618Z" + created_at: "2022-09-01T23:12:48.975618Z" }, { id: 2, - created_at: "2022-02-27T23:12:48.975618Z" + created_at: "2022-09-02T23:12:48.975618Z" }, { id: 3, - created_at: "2022-03-27T23:12:48.975618Z" + created_at: "2022-08-25T23:12:48.975618Z" }, { id: 4, - created_at: "2022-04-01T23:12:48.975618Z" + created_at: "2022-08-29T23:12:48.975618Z" }, { id: 5, - created_at: "2022-05-27T23:12:48.975618Z" + created_at: "2022-08-27T23:12:48.975618Z" }, { id: 6, - created_at: "2022-06-27T23:12:48.975618Z" + created_at: "2022-08-26T23:12:48.975618Z" }, { id: 7, - created_at: "2022-07-27T23:12:48.975618Z" + created_at: "2022-08-30T23:12:48.975618Z" }, { id: 8, - created_at: "2022-08-27T23:12:48.975618Z" + created_at: "2022-09-26T23:12:48.975618Z" }, { id: 9, - created_at: "2022-09-01T23:12:48.975618Z" + created_at: "2022-09-27T23:12:48.975618Z" }, { id: 10, - created_at: "2022-10-27T23:12:48.975618Z" + created_at: "2022-09-28T23:12:48.975618Z" }, { id: 11, - created_at: "2022-11-27T23:12:48.975618Z" + created_at: "2022-09-29T23:12:48.975618Z" }, { id: 12, - created_at: "2022-12-27T23:12:48.975618Z" + created_at: "2022-09-30T23:12:48.975618Z" }, { id: 13, - created_at: "2021-10-27T23:12:48.975618Z" + created_at: "2021-10-01T23:12:48.975618Z" }, { id: 14, - created_at: "2021-11-27T23:12:48.975618Z" + created_at: "2021-10-02T23:12:48.975618Z" }, { id: 15, - created_at: "2021-12-27T23:12:48.975618Z" + created_at: "2021-10-03T23:12:48.975618Z" } ] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js index d6a144d..5783a95 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js @@ -37,15 +37,12 @@ export const JobSeekers = () => { // Data for bar chart ------------------------------------------------------------------------------------- - // Taking out the "Totals" from the chart view - let barData = NewJobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view - // Preparing data to be passed to the chart component const newJobSeekersData = { - labels: barData.map((data) => data.description), + labels: NewJobSeekersData.map((data) => data.description), datasets: [{ label: "New Job Seekers", - data: barData.map((data) => data.qty), + data: NewJobSeekersData.map((data) => data.qty), backgroundColor: [ darkTeal, green ], @@ -142,7 +139,7 @@ export const JobSeekers = () => {
    {/* Job Seekers Chart Starts*/} -
    +

    Job Seekers Chart

    diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index 89bd8b4..fdeca93 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -148,7 +148,7 @@ export const Punctuality = () => {
    {/* Clock-Ins Chart Starts */} -
    +

    Clock-Ins Chart

    From 6d95b759740c0340ea6f0c4b696dd68401def870 Mon Sep 17 00:00:00 2001 From: paola9896 Date: Wed, 5 Oct 2022 17:51:49 +0000 Subject: [PATCH 07/10] Fixed Ratings, JobSeekers, and Documentation --- docs/MakePayment.html | 170 + docs/Metrics.html | 222 + docs/actions.js.html | 1407 +++++ {out => docs}/fonts/OpenSans-Bold-webfont.eot | Bin {out => docs}/fonts/OpenSans-Bold-webfont.svg | 0 .../fonts/OpenSans-Bold-webfont.woff | Bin .../fonts/OpenSans-BoldItalic-webfont.eot | Bin .../fonts/OpenSans-BoldItalic-webfont.svg | 0 .../fonts/OpenSans-BoldItalic-webfont.woff | Bin .../fonts/OpenSans-Italic-webfont.eot | Bin .../fonts/OpenSans-Italic-webfont.svg | 0 .../fonts/OpenSans-Italic-webfont.woff | Bin .../fonts/OpenSans-Light-webfont.eot | Bin .../fonts/OpenSans-Light-webfont.svg | 0 .../fonts/OpenSans-Light-webfont.woff | Bin .../fonts/OpenSans-LightItalic-webfont.eot | Bin .../fonts/OpenSans-LightItalic-webfont.svg | 0 .../fonts/OpenSans-LightItalic-webfont.woff | Bin .../fonts/OpenSans-Regular-webfont.eot | Bin .../fonts/OpenSans-Regular-webfont.svg | 0 .../fonts/OpenSans-Regular-webfont.woff | Bin docs/global.html | 5003 +++++++++++++++++ docs/index.html | 72 + {out => docs}/scripts/linenumber.js | 0 .../scripts/prettify/Apache-License-2.0.txt | 0 {out => docs}/scripts/prettify/lang-css.js | 0 {out => docs}/scripts/prettify/prettify.js | 0 {out => docs}/styles/jsdoc-default.css | 0 {out => docs}/styles/prettify-jsdoc.css | 0 {out => docs}/styles/prettify-tomorrow.css | 0 docs/utils_api_wrapper.js.html | 369 ++ docs/views_applications.js.html | 376 ++ docs/views_deductions.js.html | 252 + docs/views_favorites.js.html | 566 ++ docs/views_invites.js.html | 332 ++ docs/views_locations.js.html | 559 ++ docs/views_metrics_charts.js.html | 112 + ...metrics_general-stats_GeneralStats.js.html | 115 + ..._metrics_general-stats_Hours_Hours.js.html | 28 +- ...rics_general-stats_Hours_HoursData.js.html | 410 ++ ...eneral-stats_JobSeekers_JobSeekers.js.html | 258 + ...al-stats_JobSeekers_JobSeekersData.js.html | 225 + ...etrics_general-stats_Shifts_Shifts.js.html | 36 +- ...cs_general-stats_Shifts_ShiftsData.js.html | 146 + .../views_metrics_metrics.js.html | 30 +- ...ws_metrics_punctuality_Punctuality.js.html | 89 +- ...etrics_punctuality_PunctualityData.js.html | 282 + .../views_metrics_queue_Queue.js.html | 41 +- docs/views_metrics_queue_QueueData.js.html | 218 + docs/views_metrics_ratings_Ratings.js.html | 261 + docs/views_payments.js.html | 235 + docs/views_payroll.js.html | 4888 ++++++++++++++++ docs/views_profile.js.html | 1043 ++++ docs/views_ratings.js.html | 560 ++ docs/views_shifts.js.html | 3261 +++++++++++ docs/views_subscriptions.js.html | 236 + docs/views_talents.js.html | 692 +++ jsdoc-custom-template/README.md | 12 + jsdoc-custom-template/publish.js | 692 +++ .../static/fonts/OpenSans-Bold-webfont.eot | Bin 0 -> 19544 bytes .../static/fonts/OpenSans-Bold-webfont.svg | 1830 ++++++ .../static/fonts/OpenSans-Bold-webfont.woff | Bin 0 -> 22432 bytes .../fonts/OpenSans-BoldItalic-webfont.eot | Bin 0 -> 20133 bytes .../fonts/OpenSans-BoldItalic-webfont.svg | 1830 ++++++ .../fonts/OpenSans-BoldItalic-webfont.woff | Bin 0 -> 23048 bytes .../static/fonts/OpenSans-Italic-webfont.eot | Bin 0 -> 20265 bytes .../static/fonts/OpenSans-Italic-webfont.svg | 1830 ++++++ .../static/fonts/OpenSans-Italic-webfont.woff | Bin 0 -> 23188 bytes .../static/fonts/OpenSans-Light-webfont.eot | Bin 0 -> 19514 bytes .../static/fonts/OpenSans-Light-webfont.svg | 1831 ++++++ .../static/fonts/OpenSans-Light-webfont.woff | Bin 0 -> 22248 bytes .../fonts/OpenSans-LightItalic-webfont.eot | Bin 0 -> 20535 bytes .../fonts/OpenSans-LightItalic-webfont.svg | 1835 ++++++ .../fonts/OpenSans-LightItalic-webfont.woff | Bin 0 -> 23400 bytes .../static/fonts/OpenSans-Regular-webfont.eot | Bin 0 -> 19836 bytes .../static/fonts/OpenSans-Regular-webfont.svg | 1831 ++++++ .../fonts/OpenSans-Regular-webfont.woff | Bin 0 -> 22660 bytes .../static/scripts/linenumber.js | 25 + .../scripts/prettify/Apache-License-2.0.txt | 202 + .../static/scripts/prettify/lang-css.js | 2 + .../static/scripts/prettify/prettify.js | 28 + .../static/styles/jsdoc-default.css | 358 ++ .../static/styles/prettify-jsdoc.css | 111 + .../static/styles/prettify-tomorrow.css | 132 + jsdoc-custom-template/tmpl/augments.tmpl | 10 + jsdoc-custom-template/tmpl/container.tmpl | 196 + jsdoc-custom-template/tmpl/details.tmpl | 143 + jsdoc-custom-template/tmpl/example.tmpl | 2 + jsdoc-custom-template/tmpl/examples.tmpl | 13 + jsdoc-custom-template/tmpl/exceptions.tmpl | 32 + jsdoc-custom-template/tmpl/layout.tmpl | 38 + jsdoc-custom-template/tmpl/mainpage.tmpl | 14 + jsdoc-custom-template/tmpl/members.tmpl | 38 + jsdoc-custom-template/tmpl/method.tmpl | 131 + jsdoc-custom-template/tmpl/modifies.tmpl | 14 + jsdoc-custom-template/tmpl/params.tmpl | 131 + jsdoc-custom-template/tmpl/properties.tmpl | 108 + jsdoc-custom-template/tmpl/returns.tmpl | 19 + jsdoc-custom-template/tmpl/source.tmpl | 8 + jsdoc-custom-template/tmpl/tutorial.tmpl | 19 + jsdoc-custom-template/tmpl/type.tmpl | 7 + jsdoc-readme.md | 7 + jsdoc.json | 18 + objeto | 372 -- out/Employees.js.html | 151 - out/EmployeesData.js.html | 156 - out/GeneralStats.js.html | 110 - out/HoursData.js.html | 163 - out/Metrics.html | 187 - out/PunctualityData.js.html | 259 - out/Ratings.js.html | 162 - out/RatingsData.js.html | 127 - out/ShiftsData.js.html | 149 - out/WorkerStats.js.html | 175 - out/charts.js.html | 112 - out/global.html | 238 - out/index.html | 63 - package.json | 3 +- src/js/views/metrics/charts.js | 6 +- .../metrics/general-stats/GeneralStats.js | 13 +- .../general-stats/Hours/DummyDataShifts.js | 346 -- .../metrics/general-stats/Hours/Hours.js | 20 +- .../metrics/general-stats/Hours/HoursData.js | 28 +- .../JobSeekers/DummyDataShifts.js | 145 - .../JobSeekers/DummyDataWorkers.js | 70 - .../general-stats/JobSeekers/JobSeekers.js | 313 +- .../JobSeekers/JobSeekersData.js | 49 +- .../metrics/general-stats/Shifts/DummyData.js | 282 - .../metrics/general-stats/Shifts/Shifts.js | 28 +- .../general-stats/Shifts/ShiftsData.js | 17 +- src/js/views/metrics/metrics.js | 19 +- .../metrics/punctuality/DummyDataShifts.js | 427 -- .../views/metrics/punctuality/Punctuality.js | 42 +- .../metrics/punctuality/PunctualityData.js | 62 +- src/js/views/metrics/queue/DummyDataShifts.js | 328 -- .../views/metrics/queue/DummyDataWorkers.js | 687 --- src/js/views/metrics/queue/Queue.js | 25 +- src/js/views/metrics/queue/QueueData.js | 36 +- .../views/metrics/ratings/DummyDataWorkers.js | 28 - src/js/views/metrics/ratings/Ratings.js | 288 +- src/js/views/metrics/ratings/RatingsData.js | 77 - 141 files changed, 36465 insertions(+), 5289 deletions(-) create mode 100644 docs/MakePayment.html create mode 100644 docs/Metrics.html create mode 100644 docs/actions.js.html rename {out => docs}/fonts/OpenSans-Bold-webfont.eot (100%) rename {out => docs}/fonts/OpenSans-Bold-webfont.svg (100%) rename {out => docs}/fonts/OpenSans-Bold-webfont.woff (100%) rename {out => docs}/fonts/OpenSans-BoldItalic-webfont.eot (100%) rename {out => docs}/fonts/OpenSans-BoldItalic-webfont.svg (100%) rename {out => docs}/fonts/OpenSans-BoldItalic-webfont.woff (100%) rename {out => docs}/fonts/OpenSans-Italic-webfont.eot (100%) rename {out => docs}/fonts/OpenSans-Italic-webfont.svg (100%) rename {out => docs}/fonts/OpenSans-Italic-webfont.woff (100%) rename {out => docs}/fonts/OpenSans-Light-webfont.eot (100%) rename {out => docs}/fonts/OpenSans-Light-webfont.svg (100%) rename {out => docs}/fonts/OpenSans-Light-webfont.woff (100%) rename {out => docs}/fonts/OpenSans-LightItalic-webfont.eot (100%) rename {out => docs}/fonts/OpenSans-LightItalic-webfont.svg (100%) rename {out => docs}/fonts/OpenSans-LightItalic-webfont.woff (100%) rename {out => docs}/fonts/OpenSans-Regular-webfont.eot (100%) rename {out => docs}/fonts/OpenSans-Regular-webfont.svg (100%) rename {out => docs}/fonts/OpenSans-Regular-webfont.woff (100%) create mode 100644 docs/global.html create mode 100644 docs/index.html rename {out => docs}/scripts/linenumber.js (100%) rename {out => docs}/scripts/prettify/Apache-License-2.0.txt (100%) rename {out => docs}/scripts/prettify/lang-css.js (100%) rename {out => docs}/scripts/prettify/prettify.js (100%) rename {out => docs}/styles/jsdoc-default.css (100%) rename {out => docs}/styles/prettify-jsdoc.css (100%) rename {out => docs}/styles/prettify-tomorrow.css (100%) create mode 100644 docs/utils_api_wrapper.js.html create mode 100644 docs/views_applications.js.html create mode 100644 docs/views_deductions.js.html create mode 100644 docs/views_favorites.js.html create mode 100644 docs/views_invites.js.html create mode 100644 docs/views_locations.js.html create mode 100644 docs/views_metrics_charts.js.html create mode 100644 docs/views_metrics_general-stats_GeneralStats.js.html rename out/Hours.js.html => docs/views_metrics_general-stats_Hours_Hours.js.html (53%) create mode 100644 docs/views_metrics_general-stats_Hours_HoursData.js.html create mode 100644 docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html create mode 100644 docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html rename out/Shifts.js.html => docs/views_metrics_general-stats_Shifts_Shifts.js.html (52%) create mode 100644 docs/views_metrics_general-stats_Shifts_ShiftsData.js.html rename out/metrics.js.html => docs/views_metrics_metrics.js.html (62%) rename out/Punctuality.js.html => docs/views_metrics_punctuality_Punctuality.js.html (56%) create mode 100644 docs/views_metrics_punctuality_PunctualityData.js.html rename out/Queue.js.html => docs/views_metrics_queue_Queue.js.html (53%) create mode 100644 docs/views_metrics_queue_QueueData.js.html create mode 100644 docs/views_metrics_ratings_Ratings.js.html create mode 100644 docs/views_payments.js.html create mode 100644 docs/views_payroll.js.html create mode 100644 docs/views_profile.js.html create mode 100644 docs/views_ratings.js.html create mode 100644 docs/views_shifts.js.html create mode 100644 docs/views_subscriptions.js.html create mode 100644 docs/views_talents.js.html create mode 100644 jsdoc-custom-template/README.md create mode 100644 jsdoc-custom-template/publish.js create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.eot create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.svg create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.woff create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-BoldItalic-webfont.eot create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-BoldItalic-webfont.svg create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-BoldItalic-webfont.woff create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Italic-webfont.eot create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Italic-webfont.svg create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Italic-webfont.woff create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.eot create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.svg create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.woff create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.eot create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.svg create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.woff create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.eot create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.svg create mode 100644 jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.woff create mode 100644 jsdoc-custom-template/static/scripts/linenumber.js create mode 100644 jsdoc-custom-template/static/scripts/prettify/Apache-License-2.0.txt create mode 100644 jsdoc-custom-template/static/scripts/prettify/lang-css.js create mode 100644 jsdoc-custom-template/static/scripts/prettify/prettify.js create mode 100644 jsdoc-custom-template/static/styles/jsdoc-default.css create mode 100644 jsdoc-custom-template/static/styles/prettify-jsdoc.css create mode 100644 jsdoc-custom-template/static/styles/prettify-tomorrow.css create mode 100644 jsdoc-custom-template/tmpl/augments.tmpl create mode 100644 jsdoc-custom-template/tmpl/container.tmpl create mode 100644 jsdoc-custom-template/tmpl/details.tmpl create mode 100644 jsdoc-custom-template/tmpl/example.tmpl create mode 100644 jsdoc-custom-template/tmpl/examples.tmpl create mode 100644 jsdoc-custom-template/tmpl/exceptions.tmpl create mode 100644 jsdoc-custom-template/tmpl/layout.tmpl create mode 100644 jsdoc-custom-template/tmpl/mainpage.tmpl create mode 100644 jsdoc-custom-template/tmpl/members.tmpl create mode 100644 jsdoc-custom-template/tmpl/method.tmpl create mode 100644 jsdoc-custom-template/tmpl/modifies.tmpl create mode 100644 jsdoc-custom-template/tmpl/params.tmpl create mode 100644 jsdoc-custom-template/tmpl/properties.tmpl create mode 100644 jsdoc-custom-template/tmpl/returns.tmpl create mode 100644 jsdoc-custom-template/tmpl/source.tmpl create mode 100644 jsdoc-custom-template/tmpl/tutorial.tmpl create mode 100644 jsdoc-custom-template/tmpl/type.tmpl create mode 100644 jsdoc-readme.md create mode 100644 jsdoc.json delete mode 100644 objeto delete mode 100644 out/Employees.js.html delete mode 100644 out/EmployeesData.js.html delete mode 100644 out/GeneralStats.js.html delete mode 100644 out/HoursData.js.html delete mode 100644 out/Metrics.html delete mode 100644 out/PunctualityData.js.html delete mode 100644 out/Ratings.js.html delete mode 100644 out/RatingsData.js.html delete mode 100644 out/ShiftsData.js.html delete mode 100644 out/WorkerStats.js.html delete mode 100644 out/charts.js.html delete mode 100644 out/global.html delete mode 100644 out/index.html delete mode 100644 src/js/views/metrics/general-stats/Hours/DummyDataShifts.js delete mode 100644 src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js delete mode 100644 src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js delete mode 100644 src/js/views/metrics/general-stats/Shifts/DummyData.js delete mode 100644 src/js/views/metrics/punctuality/DummyDataShifts.js delete mode 100644 src/js/views/metrics/queue/DummyDataShifts.js delete mode 100644 src/js/views/metrics/queue/DummyDataWorkers.js delete mode 100644 src/js/views/metrics/ratings/DummyDataWorkers.js delete mode 100644 src/js/views/metrics/ratings/RatingsData.js diff --git a/docs/MakePayment.html b/docs/MakePayment.html new file mode 100644 index 0000000..7775d02 --- /dev/null +++ b/docs/MakePayment.html @@ -0,0 +1,170 @@ + + + + + JSDoc: Class: MakePayment + + + + + + + + + + +
    + +

    Class: MakePayment

    + + + + + + +
    + +
    + +

    MakePayment()

    + +

    Make Payment

    + + +
    + +
    +
    + + + + +

    Constructor

    + + + +

    new MakePayment()

    + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + \ No newline at end of file diff --git a/docs/Metrics.html b/docs/Metrics.html new file mode 100644 index 0000000..8946845 --- /dev/null +++ b/docs/Metrics.html @@ -0,0 +1,222 @@ + + + + + JSDoc: Class: Metrics + + + + + + + + + + +
    + +

    Class: Metrics

    + + + + + + +
    + +
    + +

    Metrics()

    + + +
    + +
    +
    + + + + + + +

    new Metrics()

    + + + + + + +
    +

    Creates the view for Metrics page, which renders 4 tabs with different components being called inside each one.

    +
    + + + + + + + + + + + + + +
    + + + + +
    Since:
    +
    • 09.28.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:Punctuality
    • + +
    • module:Ratings
    • + +
    • module:GeneralStats
    • + +
    • module:Queue
    • + +
    • module:store
    • + +
    • module:search
    • + +
    • module:Session
    • +
    + + + + + + + + + + + + + + + + + + + +
    + + + + +

    Requires

    + +
      +
    • module:Punctuality
    • + +
    • module:Ratings
    • + +
    • module:GeneralStats
    • + +
    • module:Queue
    • + +
    • module:store
    • + +
    • module:search
    • + +
    • module:Session
    • +
    + + + +

    Classes

    + +
    +
    Metrics
    +
    +
    + + + + + + + + + + + + + + + +
    + +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + \ No newline at end of file diff --git a/docs/actions.js.html b/docs/actions.js.html new file mode 100644 index 0000000..307c9e0 --- /dev/null +++ b/docs/actions.js.html @@ -0,0 +1,1407 @@ + + + + + JSDoc: Source: actions.js + + + + + + + + + + +
    + +

    Source: actions.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { useHistory } from 'react-router-dom';
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import { Session } from "bc-react-session";
    +import { Notify } from "bc-react-notifier";
    +import { Shift } from "./views/shifts.js";
    +import { Talent } from "./views/talents.js";
    +import { Rating } from "./views/ratings.js";
    +import { useNavigate } from "react-router-dom";
    +import { Invite } from "./views/invites.js";
    +import { Clockin, PayrollPeriod } from "./views/payroll.js";
    +import moment from "moment";
    +import { POST, GET, PUT, DELETE, PUTFiles, POSTcsrf, POSTcsrf2 } from "./utils/api_wrapper";
    +import log from "./utils/log";
    +import WEngine from "./utils/write_engine.js";
    +import qs from "query-string";
    +import { normalizeToSnakeCase } from "./utils/validation";
    +
    +const Models = {
    +  shifts: Shift,
    +  ratings: Rating,
    +  "payroll-periods": PayrollPeriod,
    +  talents: Talent,
    +  employees: Talent,
    +};
    +
    +export const autoLogin = (token = "") => {
    +  Session.destroy();
    +
    +  return new Promise((resolve, reject) =>
    +    GET("profiles/me", null, { Authorization: "JWT " + token })
    +      .then(function (profile) {
    +        if (!profile.employer) {
    +          Notify.error(
    +            "Only employers are allowed to login into this application"
    +          );
    +          reject("Only employers are allowed to login into this application");
    +        } else if (!profile.status === "SUSPENDED") {
    +          Notify.error(
    +            "Your account seems to be innactive, contact support for any further details"
    +          );
    +          reject(
    +            "Your account seems to be innactive, contact support for any further details"
    +          );
    +        } else {
    +          const payload = {
    +            user: { ...profile.user, profile },
    +            access_token: token,
    +          };
    +          Session.start({ payload });
    +          resolve(payload);
    +        }
    +      })
    +      .catch(function (error) {
    +        reject(error.message || error);
    +        Notify.error(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +};
    +export const stripeStatus = (email, password, keep, history, id) => {
    +  GET('subscription_auth/' + email).then(
    +    function (value) {
    +      Notify.success("Welcome!")
    +
    +    },
    +    function (reason) {
    +      history.push("/subscribe")
    +      Notify.error("Your subscription is not active, please get a new one")
    +    }
    +  )
    +
    +}
    +export const login = (email, password, keep, history, id) => {
    +  new Promise((resolve, reject) =>
    +    POST("login", {
    +      username_or_email: email,
    +      password: password,
    +      // employer_id: Number(id),
    +      exp_days: keep ? 30 : 1,
    +    })
    +      .then(
    +        setTimeout(() => { stripeStatus(email, password, keep, history, id) }, 1000)
    +      )
    +      .then(function (data) {
    +        // if (Number(data.user.profile.employer) != Number(id)) {
    +        //     let company = data.user.profile.other_employers.find(emp => emp.employer == Number(id) );
    +        //     updateCompanyUser({id: company.profile_id, employer: company.employer, employer_role: company.employer_role}, { 'Authorization': 'JWT ' + data.token });
    +        //     Session.start({ {payload: {user: data.user, access_token: data.token} });
    +        //     history.push('/');
    +        //     resolve();
    +        // }
    +        if (!data.user.profile.employer) {
    +          Notify.error(
    +            "Only employers are allowed to login into this application"
    +          );
    +          reject("Only employers are allowed to login into this application");
    +        } else if (!data.user.profile.status === "SUSPENDED") {
    +          Notify.error(
    +            "Your account seems to be innactive, contact support for any further details"
    +          );
    +          reject(
    +            "Your account seems to be innactive, contact support for any further details"
    +          );
    +        } else {
    +          Session.start({
    +            payload: {
    +              user: data.user,
    +              access_token: data.token,
    +            },
    +          });
    +          if (!data.user.profile.employer.active_subscription)
    +
    +            history.push("/subscribe");
    +          else history.push("/");
    +          resolve();
    +        }
    +      })
    +      .catch(function (error) {
    +        reject(error.message || error);
    +        Notify.error(error.message || error);
    +        log.error(error);
    +
    +      })
    +  );
    +}
    +
    +
    +export const signup = (formData, history) =>
    +  new Promise((resolve, reject) => {
    +    POST("user/register", {
    +      email: formData.email,
    +      account_type: formData.account_type,
    +      employer_role: formData.employer_role || "",
    +      employer: formData.company || formData.employer,
    +      token: formData.token || "",
    +      username: formData.email,
    +      first_name: formData.first_name,
    +      last_name: formData.last_name,
    +      password: formData.password,
    +      business_name: formData.business_name,
    +      business_website: formData.business_website,
    +      about_business: formData.about_business,
    +      phone: formData.phone,
    +    })
    +      .then(function (data) {
    +        Notify.success("You have signed up successfully! You are being redirected to the login screen");
    +        setTimeout(() => { history.push(`/login?type=${formData.account_type}`) }, 2500)
    +        resolve();
    +      })
    +      .catch(function (error) {
    +        reject(error.message || error);
    +        Notify.error(error.message || error);
    +        log.error(error);
    +      })
    +  });
    +
    +export const remind = (email) =>
    +  new Promise((resolve, reject) =>
    +    POST("user/password/reset", {
    +      email: email,
    +    })
    +      .then(function (data) {
    +        resolve();
    +        Notify.success("Check your email!");
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        reject(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +export const resetPassword = (formData, history) =>
    +  new Promise((resolve, reject) =>
    +    PUT("user/password/reset", {
    +      new_password: formData.new_password,
    +      repeat_password: formData.new_password,
    +      token: formData.token,
    +    })
    +      .then(function (data) {
    +        resolve();
    +        Notify.success(
    +          "You have change password successfully, proceed to log in"
    +        );
    +        history.push(`/login`);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        reject(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +
    +export const resendValidationLinkCurrent = (email, employer) =>
    +  new Promise((resolve, reject) =>
    +    POST("user/email/validate/send/" + email, {
    +      email: email,
    +    })
    +      .then(function (data) {
    +        resolve();
    +        Notify.success("We have sent you a validation link, check your email!");
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        reject(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +export const resendValidationLink = (email, employer) =>
    +  new Promise((resolve, reject) =>
    +    POST("user/email/validate/send/" + email + "/" + employer, {
    +      email: email,
    +      employer: employer,
    +    })
    +      .then(function (data) {
    +        resolve();
    +        Notify.success("We have sent you a validation link, check your email!");
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        reject(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +
    +//Send company inviation to user
    +export const sendCompanyInvitation = (email, employer, employer_role, sender) =>
    +  new Promise((resolve, reject) =>
    +    POST(
    +      "user/email/company/send/" +
    +      email +
    +      "/" +
    +      sender +
    +      "/" +
    +      employer +
    +      "/" +
    +      employer_role,
    +      {
    +        email: email,
    +        sender: sender,
    +        employer: employer,
    +        employer_role: employer_role,
    +      }
    +    )
    +      .then(function (data) {
    +        //fisrt check if I have any of this on the store
    +        let entities = store.getState("jobcore-invites");
    +        if (!entities || !Array.isArray(entities)) entities = [];
    +
    +        //if the response from the server is not a list
    +        if (!Array.isArray(data)) {
    +          // if the response is not a list, I will add the new object into that list
    +          Flux.dispatchEvent(
    +            "jobcore-invites",
    +            entities.concat([{ ...data, id: data.id }])
    +          );
    +        }
    +        //if it is an array
    +        else {
    +          const newShifts = data.map((inc) =>
    +            Object.assign({ ...data, id: inc.id })
    +          );
    +          Flux.dispatchEvent("jobcore-invites", entities.concat(newShifts));
    +        }
    +        resolve();
    +        Notify.success("We have sent the company invitation!");
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        reject(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +
    +export const logout = () => {
    +  setTimeout(() => {
    +    Session.destroy();
    +    store = new _Store();
    +  }, 3000);
    +};
    +
    +/**
    + * GENERIC ACTIONS, try to reuse them!!!!
    + */
    +
    +export const fetchAllIfNull = (entities) => {
    +  const _entities = entities.filter((e) => !store.getState("entity"));
    +  return fetchAll(_entities);
    +};
    +export const fetchAll = (entities) =>
    +  new Promise((resolve, reject) => {
    +    let requests = [];
    +    const checkPromiseResolution = () => {
    +      const hasPending = requests.find((r) => r.pending == true);
    +      if (!hasPending) {
    +        const hasError = requests.find((r) => r.error == true);
    +        if (hasError) reject();
    +        else resolve();
    +      }
    +    };
    +
    +    entities.forEach((entity) => {
    +      const currentRequest = {
    +        entity: entity.slug || entity,
    +        pending: true,
    +        error: false,
    +      };
    +      requests.push(currentRequest);
    +
    +      GET(entity.url || entity.slug || entity)
    +        .then(function (list) {
    +          if (typeof entity.callback == "function") entity.callback();
    +          Flux.dispatchEvent(entity.slug || entity, list);
    +
    +          currentRequest.pending = false;
    +          checkPromiseResolution();
    +        })
    +        .catch(function (error) {
    +          Notify.error(error.message || error);
    +          log.error(error);
    +
    +          currentRequest.pending = false;
    +          currentRequest.error = true;
    +          checkPromiseResolution();
    +        });
    +    });
    +  });
    +
    +export const fetchAllMeIfNull = (entities) => {
    +  const _entities = entities.filter((e) => !store.getState("entity"));
    +  return fetchAllMe(_entities);
    +};
    +export const fetchAllMe = (entities) =>
    +  new Promise((resolve, reject) => {
    +    let requests = [];
    +    const checkPromiseResolution = () => {
    +      const hasPending = requests.find((r) => r.pending == true);
    +      if (!hasPending) {
    +        const hasError = requests.find((r) => r.error == true);
    +        if (hasError) reject();
    +        else resolve();
    +      }
    +    };
    +
    +    entities.forEach((entity) => {
    +      const currentRequest = {
    +        entity: entity.slug || entity,
    +        pending: true,
    +        error: false,
    +      };
    +      requests.push(currentRequest);
    +
    +      GET("employers/me/" + (entity.slug || entity))
    +        .then(function (list) {
    +          if (typeof entity.callback == "function") entity.callback();
    +          Flux.dispatchEvent(entity.slug || entity, list);
    +
    +          currentRequest.pending = false;
    +          checkPromiseResolution();
    +        })
    +        .catch(function (error) {
    +          Notify.error(error.message || error);
    +          log.error(error);
    +
    +          currentRequest.pending = false;
    +          currentRequest.error = true;
    +          checkPromiseResolution();
    +        });
    +    });
    +  });
    +
    +export const fetchSingle = (entity, id) =>
    +  new Promise((resolve, reject) => {
    +    const _entity = entity.slug || entity;
    +    GET(entity.url || "employers/me/" + _entity + "/" + id)
    +      .then(function (data) {
    +        const cachedEntity = WEngine.get(_entity, id);
    +        if (cachedEntity) data = Object.assign(data, cachedEntity);
    +        Flux.dispatchEvent(
    +          _entity,
    +          store.replaceMerged(
    +            _entity,
    +            data.id,
    +            Models[_entity](data).defaults().unserialize()
    +          )
    +        );
    +        resolve(data);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject();
    +      });
    +  });
    +
    +export const processPendingPayrollPeriods = () =>
    +  new Promise((resolve, reject) => {
    +    const payload = Session.getPayload();
    +    const params = {
    +      employer:
    +        payload.user.profile.employer.id || payload.user.profile.employer,
    +    };
    +    GET(`hook/generate_periods?${qs.stringify(params)}`)
    +      .then(function (_newPeriods) {
    +        let periods = store.getState("payroll-periods");
    +        Flux.dispatchEvent("payroll-periods", periods.concat(_newPeriods));
    +        resolve(_newPeriods);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject();
    +      });
    +  });
    +
    +export const hook = (hookName) =>
    +  new Promise((resolve, reject) => {
    +    const payload = Session.getPayload();
    +    const params = {
    +      employer:
    +        payload.user.profile.employer.id || payload.user.profile.employer,
    +    };
    +    GET(`hook/${hookName}?${qs.stringify(params)}`)
    +      .then(function (data) {
    +        resolve(data);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject();
    +      });
    +  });
    +
    +export const fetchTemporal = async (url, event_name, callback = null) => {
    +  try {
    +    const data = await GET(url);
    +    if (typeof callback == "function") callback();
    +    Flux.dispatchEvent(event_name, data);
    +    return data;
    +  } catch (error) {
    +    Notify.error(error.message || error);
    +    log.error(error);
    +    throw error;
    +  }
    +};
    +
    +export const search = (entity, queryString = null) =>
    +  new Promise((accept, reject) =>
    +    GET(entity, queryString)
    +      .then(function (list) {
    +        //console.log("list", list);
    +        if (typeof entity.callback == "function") entity.callback();
    +        Flux.dispatchEvent(entity.slug || entity, list);
    +        accept(list);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      })
    +  );
    +export const searchMe = (entity, queryString, mergeResults = false) =>
    +  new Promise((accept, reject) =>
    +    GET("employers/me/" + entity, queryString)
    +      .then(function (list) {
    +        if (typeof entity.callback == "function") entity.callback();
    +        if (mergeResults) {
    +          const previous = store.getState(entity.slug || entity);
    +          if (Array.isArray(previous))
    +            list = previous.concat(list.results || list);
    +        }
    +        Flux.dispatchEvent(entity.slug || entity, list);
    +        accept(list);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      })
    +  );
    +
    +export const create = (entity, data, status = WEngine.modes.LIVE) =>
    +  new Promise((resolve, reject) => {
    +    POST("employers/me/" + (entity.url || entity), data)
    +      .then(function (incoming) {
    +        //console.log("incoming", incoming);
    +        if (
    +          typeof entity.url === "string" &&
    +          typeof entity.slug === "undefined"
    +        )
    +          throw Error("Missing entity slug on the create method");
    +
    +        //fisrt check if I have any of this on the store
    +        let entities = store.getState(entity.slug || entity);
    +        if (!entities || !Array.isArray(entities)) entities = [];
    +
    +        //if the response from the server is not a list
    +        if (!Array.isArray(incoming)) {
    +          // if the response is not a list, I will add the new object into that list
    +          Flux.dispatchEvent(
    +            entity.slug || entity,
    +            entities.concat([{ ...data, id: incoming.id }])
    +          );
    +        }
    +        //if it is an array
    +        else {
    +          var newShifts;
    +          if (entity === "shifts" && incoming.length > 1) {
    +            newShifts = incoming;
    +          } else {
    +            newShifts = incoming.map((inc) =>
    +              Object.assign({ ...data, id: inc.id })
    +            );
    +          }
    +          //console.log("slug", entity.slug);
    +          //console.log("entity", entity);
    +          //console.log("entities", entities);
    +          //console.log("newShifts", newShifts);
    +          Flux.dispatchEvent(entity.slug || entity, entities.concat(newShifts));
    +        }
    +        Notify.success(
    +          "The " +
    +          (entity.slug || entity).substring(
    +            0,
    +            (entity.slug || entity).length - 1
    +          ) +
    +          " was created successfully"
    +        );
    +        resolve(incoming);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +export const update = (entity, data, mode = WEngine.modes.LIVE) =>
    +  new Promise((resolve, reject) => {
    +    let path =
    +      typeof entity == "string"
    +        ? `employers/me/${entity}/${data.id}`
    +        : entity.path + (typeof data.id !== "undefined" ? `/${data.id}` : "");
    +    const event_name = typeof entity == "string" ? entity : entity.event_name;
    +    if (mode === WEngine.modes.POSPONED) path += "?posponed=true";
    +    PUT(path, data)
    +      .then(function (incomingObject) {
    +        if (mode === WEngine.modes.POSPONED) {
    +          if (event_name === "shifts")
    +            data = Shift(incomingObject).defaults().unserialize();
    +          WEngine.add({ entity: event_name, method: "PUT", data, id: data.id });
    +        } else if (entity == "payrates") data = incomingObject;
    +        else if (event_name === "current_employer")
    +          Notify.success(
    +            "The " + "payroll settings" + " was updated successfully"
    +          );
    +        else Notify.success("The " + event_name + " was updated successfully");
    +        let entities = store.replaceMerged(event_name, data.id, data);
    +        Flux.dispatchEvent(event_name, entities);
    +        resolve(data);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +export const remove = (entity, data) => {
    +  const path =
    +    typeof entity == "string"
    +      ? `employers/me/${entity}/${data.id}`
    +      : `${entity.path}/${data.id}`;
    +  const event_name = typeof entity == "string" ? entity : entity.event_name;
    +  DELETE(path)
    +    .then(function (incomingObject) {
    +      let entities = store.remove(event_name, data.id);
    +      Flux.dispatchEvent(event_name, entities);
    +
    +      const name = path.split("/");
    +      Notify.success(
    +        "The " +
    +        name[0].substring(0, name[0].length - 1) +
    +        " was deleted successfully"
    +      );
    +    })
    +    .catch(function (error) {
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    });
    +};
    +
    +/**
    + * From here on the actions are not generic anymore
    + */
    +
    +export const updateProfileImage = (file) =>
    +  PUTFiles("employers/me/image", [file])
    +    .then(function (incomingObject) {
    +      const payload = Session.getPayload();
    +
    +      const user = Object.assign(payload.user, { profile: incomingObject });
    +      // Session.setPayload({ user });
    +      return user.profile.picture;
    +    })
    +    .catch(function (error) {
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    });
    +
    +export const updateProfile = (data) => {
    +  PUT(`profiles/${data.id}`, data)
    +    .then(function (incomingObject) {
    +      const payload = Session.getPayload();
    +      const user = Object.assign(payload.user, { profile: incomingObject });
    +      Session.setPayload({ user });
    +    })
    +    .catch(function (error) {
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    });
    +};
    +export const updateProfileMe = (data) => {
    +  PUT(`profiles/me`, data)
    +    .then(function (incomingObject) {
    +      const payload = Session.getPayload();
    +      const user = Object.assign(payload.user, { profile: incomingObject });
    +      Session.setPayload({ user });
    +    })
    +    .catch(function (error) {
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    });
    +};
    +
    +export const updateEmployability = (data) => {
    +  PUT(`employee/employability_expired_at/update/${data.catalog.employee.id}`,
    +    data)
    +    .then()
    +}
    +export const updateDocs = (data) => {
    +  PUT(`employee/employment_verification_status/update/${data.catalog.employee.id}`,
    +    data)
    +    .then(response => response.json())
    +    .then(data => console.log(data))
    +}
    +export const createSubscription = (data, history) => {
    +  const employer = store.getState("current_employer");
    +
    +  POST(`employers/me/subscription`, data)
    +    .then(function (active_subscription) {
    +
    +      Flux.dispatchEvent("current_employer", {
    +        ...employer,
    +        active_subscription,
    +      });
    +      Notify.success("The subscription was created successfully");
    +
    +
    +    }).then(
    +      setTimeout(() => { history.push("/welcome") }, 4000)
    +
    +    )
    +    .catch(function (error) {
    +      console.log("ERROR", error);
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    })
    +
    +};
    +
    +export const createStripePayment2 = async () => {
    +  const response = await POSTcsrf2('create-payment-single-emp')
    +    .then(
    +      Notify.success("The payment was received successfully")
    +    )
    +    .catch(function (error) {
    +      console.log("ERROR", error);
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    })
    +
    +  return response
    +
    +};
    +
    +export const createStripePayment = async (stripeToken) => {
    +  const response = await POSTcsrf('create-payment-intent', stripeToken)
    +    .then(
    +      Notify.success("The payment was received successfully")
    +    )
    +    .catch(function (error) {
    +      console.log("ERROR", error);
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    })
    +
    +  return response
    +
    +};
    +
    +export const updateSubscription = (data, history) => {
    +  const employer = store.getState("current_employer");
    +  PUT(`employers/me/subscription`, data)
    +    .then(function (active_subscription) {
    +      Flux.dispatchEvent("current_employer", {
    +        ...employer,
    +        active_subscription,
    +      });
    +      Notify.success("The subscription was updated successfully");
    +    })
    +    .catch(function (error) {
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    });
    +};
    +
    +export const removeBankAccount = (route, data) => {
    +  const path = `${route}/${data.id}`;
    +  DELETE(path)
    +    .then(() => {
    +      Notify.success("The " + data.name + " was deleted successfully");
    +      searchBankAccounts();
    +    })
    +    .catch((error) => {
    +      console.log("bank-accounts error: ", error);
    +      Notify.error(error.message || error);
    +      log.error(error);
    +    });
    +};
    +
    +export const rejectCandidate = async (shiftId, applicant) => {
    +  let shift = store.get("shifts", shiftId);
    +  if (!shift) shift = await fetchSingle("shifts", shiftId);
    +  if (shift) {
    +    const newCandidates = shift.candidates.filter(
    +      (candidate) => candidate.id != applicant.id
    +    );
    +    const updatedShift = {
    +      candidates: newCandidates.map((cand) => cand.id),
    +    };
    +
    +    try {
    +      await PUT(`employers/me/shifts/${shiftId}/candidates`, updatedShift);
    +
    +      Flux.dispatchEvent(
    +        "shifts",
    +        store.replaceMerged("shifts", shiftId, {
    +          candidates: newCandidates,
    +        })
    +      );
    +
    +      const applications = store.getState("applications");
    +      if (applications)
    +        Flux.dispatchEvent(
    +          "applications",
    +          store.filter(
    +            "applications",
    +            (item) =>
    +              item.shift.id != shiftId || item.employee.id != applicant.id
    +          )
    +        );
    +
    +      Notify.success("The candidate was successfully rejected");
    +      return { ...shift, candidates: newCandidates };
    +    } catch (error) {
    +      Notify.error(error.message || error);
    +      log.error(error);
    +      throw error;
    +    }
    +  } else {
    +    Notify.error("Shift not found");
    +    throw new Error("Shift not found");
    +  }
    +};
    +
    +export const updateCompanyUser = (user, header = {}) =>
    +  new Promise((resolve, reject) => {
    +    PUT(`employers/me/users/${user.id}`, user, header)
    +      .then((resp) => {
    +        const users = store.getState("users");
    +
    +        if (users) {
    +          let _users = users.map((u) => {
    +            if (u.email == resp.email) return resp;
    +            else return u;
    +          });
    +
    +          Flux.dispatchEvent("users", _users);
    +        }
    +
    +        resolve(resp);
    +      })
    +      .catch((error) => {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +export const updateUser = (user, header = {}) =>
    +  new Promise((resolve, reject) => {
    +    PUT(`employers/me/users/${user.id}`, user, header)
    +      .then((resp) => {
    +        const users = store.getState("users");
    +
    +        if (users) {
    +          let _users = users.map((u) => {
    +            if (u.email == resp.email) return resp;
    +            else return u;
    +          });
    +
    +          Flux.dispatchEvent("users", _users);
    +        }
    +
    +        Notify.success("The user was successfully updated");
    +        resolve(resp);
    +      })
    +      .catch((error) => {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +export const removeUser = (user) =>
    +  new Promise((resolve, reject) => {
    +    DELETE(`employers/me/users/${user.profile.id}`)
    +      .then((resp) => {
    +        Flux.dispatchEvent(
    +          "users",
    +          store.getState("users").filter((u) => u.email != user.email)
    +        );
    +
    +        Notify.success("The user was successfully updated");
    +        resolve(resp);
    +      })
    +      .catch((error) => {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +export const deleteShiftEmployee = async (shiftId, employee) => {
    +  let shift = store.get("shifts", shiftId);
    +  if (!shift) shift = await fetchSingle("shifts", shiftId);
    +  if (shift) {
    +    const newEmployees = shift.employees.filter((emp) => emp.id != employee.id);
    +    const updatedShift = {
    +      employees: newEmployees.map((emp) => emp.id),
    +    };
    +    PUT(`employers/me/shifts/${shiftId}/employees`, updatedShift)
    +      .then(() => {
    +        Flux.dispatchEvent(
    +          "shifts",
    +          store.replaceMerged("shifts", shiftId, {
    +            employees: newEmployees,
    +          })
    +        );
    +
    +        Notify.success("The employee was successfully deleted");
    +      })
    +      .catch((error) => {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +      });
    +  } else Notify.error("Shift not found");
    +};
    +
    +export const acceptCandidate = async (shiftId, applicant) => {
    +  let shift = store.get("shifts", shiftId);
    +  if (!shift) shift = await fetchSingle("shifts", shiftId);
    +  if (shift) {
    +    if (
    +      shift.status === "OPEN" ||
    +      shift.employees.length < shift.maximum_allowed_employees
    +    ) {
    +      const newEmployees = shift.employees.concat([applicant]);
    +      const newCandidates = shift.candidates.filter((c) =>
    +        Number.isInteger(c) ? c !== applicant.id : c.id !== applicant.id
    +      );
    +      const shiftData = {
    +        employees: newEmployees.map((emp) =>
    +          Number.isInteger(emp) ? emp : emp.id
    +        ),
    +        candidates: newCandidates.map((can) =>
    +          Number.isInteger(can) ? can : can.id
    +        ),
    +      };
    +
    +      try {
    +        const data = await PUT(
    +          `employers/me/shifts/${shiftId}/candidates`,
    +          shiftData
    +        );
    +
    +        const applications = store.getState("applications");
    +        if (applications)
    +          Flux.dispatchEvent(
    +            "applications",
    +            store.filter(
    +              "applications",
    +              (item) =>
    +                item.shift.id != shiftId || item.employee.id != applicant.id
    +            )
    +          );
    +        Flux.dispatchEvent(
    +          "shifts",
    +          store.replaceMerged("shifts", shiftId, {
    +            employees: newEmployees,
    +            candidates: newCandidates,
    +          })
    +        );
    +        Notify.success("The candidate was successfully accepted");
    +        return null;
    +      } catch (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        throw error;
    +      }
    +    } else {
    +      Notify.error("This shift is already filled.");
    +      throw new Error("This shift is already filled.");
    +    }
    +  } else {
    +    Notify.error("Shift not found");
    +    throw new Error("Shift not found");
    +  }
    +};
    +
    +export const updateTalentList = (action, employee, listId) => {
    +  const favoriteList = store.get("favlists", listId);
    +
    +  return new Promise((resolve, reject) => {
    +    if (favoriteList) {
    +      let employeeIdsArr = favoriteList.employees.map(
    +        (employee) => employee.id || employee
    +      );
    +      if (action === "add") {
    +        employeeIdsArr.push(employee.id || employee);
    +      } else if (action === "delete") {
    +        employeeIdsArr = employeeIdsArr.filter(
    +          (id) => id != (employee.id || employee)
    +        );
    +      }
    +      PUT("employers/me/favlists/" + listId, { employees: employeeIdsArr })
    +        .then((updatedFavlist) => {
    +          Flux.dispatchEvent(
    +            "favlists",
    +            store.replaceMerged("favlists", listId, {
    +              employees: updatedFavlist.employees,
    +            })
    +          );
    +          Notify.success(
    +            `The talent was successfully ${action == "add" ? "added" : "removed"
    +            }`
    +          );
    +          resolve(updatedFavlist);
    +        })
    +        .catch((error) => {
    +          Notify.error(error.message || error);
    +          log.error(error);
    +          reject(error);
    +        });
    +    } else {
    +      Notify.error("Favorite list not found");
    +      reject();
    +    }
    +  });
    +};
    +
    +export const updatePayments = async (payments, period) => {
    +  if (!Array.isArray(payments)) payments = [payments];
    +  for (let i = 0; i < payments.length; i++) {
    +    let data = { ...payments[i] };
    +    if (data.shift) data.shift = data.shift.id || data.shift;
    +    if (data.employer) data.employer = data.employer.id || data.employer;
    +    if (data.employee) data.employee = data.employee.id || data.employee;
    +    if (data.clockin) data.clockin = data.clockin.id || data.clockin;
    +
    +    const _updated = await update("payment", data);
    +    period = {
    +      ...period,
    +      payments: period.payments.map((p) => {
    +        if (p.id === _updated.id) return { ...p, ...payments[i] };
    +        else return p;
    +      }),
    +    };
    +  }
    +
    +  Flux.dispatchEvent(
    +    "payroll-periods",
    +    store.replace("payroll-periods", period.id, period)
    +  );
    +  return period;
    +};
    +
    +export const createPayment = async (payment, period) => {
    +  const _new = await create("payment", {
    +    ...payment,
    +    employee: payment.employee.id || payment.employee,
    +    shift: payment.shift.id || payment.shift,
    +  });
    +  const _period = {
    +    ...period,
    +    payments: period.payments.concat([
    +      { ..._new, employee: payment.employee, shift: payment.shift },
    +    ]),
    +  };
    +
    +  Flux.dispatchEvent(
    +    "payroll-periods",
    +    store.replace("payroll-periods", period.id, _period)
    +  );
    +  return period;
    +};
    +
    +/**
    + * Make employee payment
    + * @param  {string}  employeePaymentId employee payment id
    + * @param  {string}  paymentType payment type could be: CHECK, FAKE or ELECTRONIC TRANSFERENCE
    + * @param  {string}  employer_bank_account_id employer bank account id
    + * @param  {string}  employee_bank_account_id employee bank account id
    + */
    +export const makeEmployeePayment = (
    +  employeePaymentId,
    +  paymentType,
    +  employer_bank_account_id,
    +  employee_bank_account_id,
    +  deductions_list,
    +  deductions
    +) =>
    +  new Promise((resolve, reject) => {
    +    const data = {
    +      payment_type: paymentType,
    +      payment_data:
    +        paymentType === "CHECK"
    +          ? {}
    +          : {
    +            employer_bank_account_id: employer_bank_account_id,
    +            employee_bank_account_id: employee_bank_account_id,
    +          },
    +      deductions_list: deductions_list,
    +      deductions: deductions,
    +    };
    +
    +    POST(`employers/me/employee-payment/${employeePaymentId}`, data)
    +      .then((resp) => {
    +        Flux.dispatchEvent("employee-payment", resp);
    +        Notify.success("Payment was successful");
    +        resolve(resp);
    +      })
    +      .catch((error) => {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +/**
    + * Fetch payroll period payments
    + * @param  {string}  payrollPeriodId employee payment id
    + */
    +export const fetchPeyrollPeriodPayments = async (payrollPeriodId) => {
    +  try {
    +    const response = await GET(
    +      `employers/me/employee-payment-list/${payrollPeriodId}`
    +    );
    +    Flux.dispatchEvent("payroll-period-payments", response);
    +  } catch (error) {
    +    Notify.error(error.message || error);
    +  }
    +};
    +
    +export const addBankAccount = (token, metadata) =>
    +  new Promise((resolve, reject) =>
    +    POST(
    +      "bank-accounts/",
    +      normalizeToSnakeCase({
    +        publicToken: token,
    +        institutionName: metadata.institution.name,
    +      })
    +    )
    +      .then(function (data) {
    +        console.log("addBankAccount data: ", data);
    +        resolve();
    +        searchBankAccounts();
    +      })
    +      .catch(function (error) {
    +        reject(error.message || error);
    +        Notify.error(error.message || error);
    +        log.error(error);
    +      })
    +  );
    +
    +export const searchBankAccounts = () =>
    +  new Promise((accept, reject) =>
    +    GET("bank-accounts/")
    +      .then(function (list) {
    +        console.log("bank-accounts list: ", list);
    +        Flux.dispatchEvent("bank-accounts", list);
    +        accept(list);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      })
    +  );
    +
    +/**
    + * Get payments report
    + * @param  {string}  periodId payroll period id
    + * @param  {string}  startDate start date
    + * @param  {string}  endDate end date
    + */
    +export const getPaymentsReport = (periodId, startDate, endDate) =>
    +  new Promise((accept, reject) => {
    +    const route = `employers/me/employee-payment/report?start_date=${startDate}&end_date=${endDate}&period_id=${periodId}`;
    +    GET(route)
    +      .then(function (list) {
    +        Flux.dispatchEvent("payments-reports", list);
    +        accept(list);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +/**
    + * Get deductions report
    + * @param  {string}  periodId payroll period id
    + * @param  {string}  startDate start date
    + * @param  {string}  endDate end date
    + */
    +export const getDeductionsReport = (periodId, startDate, endDate) =>
    +  new Promise((accept, reject) => {
    +    const route = `employers/me/employee-payment/deduction-report?start_date=${startDate}&end_date=${endDate}&period_id=${periodId}`;
    +    GET(route)
    +      .then(function (list) {
    +        Flux.dispatchEvent("deductions-reports", list);
    +        accept(list);
    +      })
    +      .catch(function (error) {
    +        Notify.error(error.message || error);
    +        log.error(error);
    +        reject(error);
    +      });
    +  });
    +
    +// export const createPayrollPeriodRating = (entity, queryString) => new Promise((accept, reject) =>
    +//     GET('employers/me/' + entity, queryString)
    +//         .then(function (list) {
    +//             if (typeof entity.callback == 'function') entity.callback();
    +//             Flux.dispatchEvent(entity.slug || entity, list);
    +//             accept(list);
    +//         })
    +//         .catch(function (error) {
    +//             Notify.error(error.message || error);
    +//             log.error(error);
    +//             reject(error);
    +//         })
    +// );
    +
    +export const http = { GET };
    +
    +class _Store extends Flux.DashStore {
    +  constructor() {
    +    super();
    +    this.addEvent("positions");
    +    this.addEvent("venues");
    +    this.addEvent("onboarding");
    +    this.addEvent("users");
    +    this.addEvent("invites", (invites) => {
    +      if (!Array.isArray(invites)) return [];
    +      return invites.map((inv) => Invite(inv).defaults().unserialize());
    +    });
    +    this.addEvent("payment");
    +    this.addEvent("employee-payment");
    +    this.addEvent("clockins", (clockins) =>
    +      !Array.isArray(clockins)
    +        ? []
    +        : clockins.map((c) => ({
    +          ...c,
    +          started_at: moment(c.starting_at),
    +          ended_at: moment(c.ended_at),
    +        }))
    +    );
    +    this.addEvent("jobcore-invites");
    +    this.addEvent("ratings", (_ratings) =>
    +      !Array.isArray(_ratings)
    +        ? []
    +        : _ratings.map((ra) => Rating(ra).defaults().unserialize())
    +    );
    +    this.addEvent("bank-accounts");
    +    this.addEvent("employees", (employees) => {
    +      if (!Array.isArray(employees)) return [];
    +      return employees
    +        .filter((em) => em.user.profile)
    +        .map((tal) => Talent(tal).defaults().unserialize());
    +    });
    +    this.addEvent("favlists");
    +    this.addEvent("company-user");
    +    this.addEvent("deduction");
    +    this.addEvent("payrates");
    +    this.addEvent("payroll-period-payments");
    +    this.addEvent("payments-reports");
    +    this.addEvent("deductions-reports");
    +    this.addEvent("badges");
    +
    +    this.addEvent("applications", (applicants) => {
    +      return !applicants ||
    +        (Object.keys(applicants).length === 0 &&
    +          applicants.constructor === Object)
    +        ? []
    +        : applicants.map((app) => {
    +          app.shift = Shift(app.shift).defaults().unserialize();
    +          return app;
    +        });
    +    });
    +    this.addEvent("shifts", (shifts) => {
    +      shifts = Array.isArray(shifts.results)
    +        ? shifts.results
    +        : Array.isArray(shifts)
    +          ? shifts
    +          : null;
    +      let newShifts =
    +        !shifts ||
    +          (Object.keys(shifts).length === 0 && shifts.constructor === Object)
    +          ? []
    +          : shifts
    +            .filter((s) => s.status !== "CANCELLED")
    +            .map((shift) => {
    +              //already transformed
    +              return Shift(shift).defaults().unserialize();
    +            });
    +
    +      const applicants = this.getState("applications");
    +      if (!applicants && Session.get().isValid) fetchAllMe(["applications"]);
    +
    +      // const _shift = newShifts.find(s => s.id == 1095);
    +      return newShifts;
    +    });
    +
    +    // Payroll related data
    +    // this.addEvent('payroll-periods', (period) => {
    +    //     return (!period || (Object.keys(period).length === 0 && period.constructor === Object)) ? [{ label: "Loading payment periods...", value: null }] : period.map(p => {
    +    //         p.label = `From ${moment(p.starting_at).format('MM-D-YY h:mm A')} to ${moment(p.ending_at).format('MM-D-YY h:mm A')}`;
    +    //         if(!Array.isArray(p.payments)) p.payments = [];
    +    //         return p;
    +    //     });
    +    // });
    +    this.addEvent("payroll-periods");
    +    this.addEvent("subscription");
    +    this.addEvent("w4-form");
    +    this.addEvent("previos-employee-shifts");
    +    this.addEvent("employee-expired-shifts"); //temporal, just used on the payroll report
    +
    +    //temporal storage (for temporal views, information that is read only)
    +    this.addEvent("current_employer", (employer) => {
    +      employer.payroll_configured =
    +        employer.payroll_period_starting_time != null;
    +      employer.payroll_period_starting_time = moment.isMoment(
    +        employer.payroll_period_starting_time
    +      )
    +        ? employer.payroll_period_starting_time
    +        : employer.payroll_period_starting_time
    +          ? moment(employer.payroll_period_starting_time)
    +          : moment(employer.created_at).startOf("isoWeek");
    +      return employer;
    +    });
    +    this.addEvent("single_payroll_detail", (payroll) => {
    +      const clockins = payroll.clockins;
    +      let approved = true;
    +      let paid = true;
    +      payroll.clockins =
    +        !clockins ||
    +          (Object.keys(clockins).length === 0 && clockins.constructor === Object)
    +          ? []
    +          : clockins.map((clockin) => {
    +            //already transformed
    +            if (clockin.status == "PENDING") {
    +              approved = false;
    +              paid = false;
    +            } else if (clockin.status != "PAID") paid = false;
    +
    +            return Clockin(clockin).defaults().unserialize();
    +          });
    +
    +      if (typeof payroll.talent != "undefined")
    +        payroll.talent.paymentsApproved = approved;
    +      if (typeof payroll.talent != "undefined")
    +        payroll.talent.paymentsPaid = paid;
    +      payroll.approved = approved;
    +      payroll.paid = paid;
    +      return payroll;
    +    });
    +  }
    +
    +  get(type, id) {
    +    const entities = this.getState(type);
    +    if (entities)
    +      return entities.find(
    +        (ent) => ent.id == parseInt(id, 10) || ent.value == parseInt(id, 10)
    +      );
    +    else return null;
    +  }
    +  add(type, item) {
    +    const entities = this.getState(type);
    +    if (item) return entities.concat([item]);
    +    //else return entities;
    +    else throw new Error("Trying to add a null item into " + type);
    +  }
    +  replace(type, id, item) {
    +    const entities = this.getState(type);
    +    if (!entities) throw new Error("No item found in " + type);
    +
    +    if (Array.isArray(entities)) {
    +      return entities.concat([]).map((ent) => {
    +        if (ent.id != parseInt(id, 10)) return ent;
    +        return item;
    +      });
    +    } else return item;
    +  }
    +  replaceMerged(type, id, item) {
    +    let entities = this.getState(type);
    +    if (!entities) entities = [];
    +
    +    if (Array.isArray(entities)) {
    +      const result = entities.concat([]).map((ent) => {
    +        if (ent.id != parseInt(id, 10)) return ent;
    +        return Object.assign(ent, item);
    +      });
    +      return result;
    +    } else {
    +      return Object.assign(entities, item);
    +    }
    +  }
    +  remove(type, id) {
    +    const entities = this.getState(type);
    +    if (entities)
    +      return entities.filter((ent) => {
    +        return ent.id != parseInt(id, 10);
    +      });
    +    else throw new Error("No items found in " + entities);
    +  }
    +
    +  filter(type, callback) {
    +    const entities = this.getState(type);
    +    if (entities) return entities.filter(callback);
    +    else throw new Error("No items found in entity type: " + type);
    +  }
    +}
    +export let store = new _Store();
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/fonts/OpenSans-Bold-webfont.eot b/docs/fonts/OpenSans-Bold-webfont.eot similarity index 100% rename from out/fonts/OpenSans-Bold-webfont.eot rename to docs/fonts/OpenSans-Bold-webfont.eot diff --git a/out/fonts/OpenSans-Bold-webfont.svg b/docs/fonts/OpenSans-Bold-webfont.svg similarity index 100% rename from out/fonts/OpenSans-Bold-webfont.svg rename to docs/fonts/OpenSans-Bold-webfont.svg diff --git a/out/fonts/OpenSans-Bold-webfont.woff b/docs/fonts/OpenSans-Bold-webfont.woff similarity index 100% rename from out/fonts/OpenSans-Bold-webfont.woff rename to docs/fonts/OpenSans-Bold-webfont.woff diff --git a/out/fonts/OpenSans-BoldItalic-webfont.eot b/docs/fonts/OpenSans-BoldItalic-webfont.eot similarity index 100% rename from out/fonts/OpenSans-BoldItalic-webfont.eot rename to docs/fonts/OpenSans-BoldItalic-webfont.eot diff --git a/out/fonts/OpenSans-BoldItalic-webfont.svg b/docs/fonts/OpenSans-BoldItalic-webfont.svg similarity index 100% rename from out/fonts/OpenSans-BoldItalic-webfont.svg rename to docs/fonts/OpenSans-BoldItalic-webfont.svg diff --git a/out/fonts/OpenSans-BoldItalic-webfont.woff b/docs/fonts/OpenSans-BoldItalic-webfont.woff similarity index 100% rename from out/fonts/OpenSans-BoldItalic-webfont.woff rename to docs/fonts/OpenSans-BoldItalic-webfont.woff diff --git a/out/fonts/OpenSans-Italic-webfont.eot b/docs/fonts/OpenSans-Italic-webfont.eot similarity index 100% rename from out/fonts/OpenSans-Italic-webfont.eot rename to docs/fonts/OpenSans-Italic-webfont.eot diff --git a/out/fonts/OpenSans-Italic-webfont.svg b/docs/fonts/OpenSans-Italic-webfont.svg similarity index 100% rename from out/fonts/OpenSans-Italic-webfont.svg rename to docs/fonts/OpenSans-Italic-webfont.svg diff --git a/out/fonts/OpenSans-Italic-webfont.woff b/docs/fonts/OpenSans-Italic-webfont.woff similarity index 100% rename from out/fonts/OpenSans-Italic-webfont.woff rename to docs/fonts/OpenSans-Italic-webfont.woff diff --git a/out/fonts/OpenSans-Light-webfont.eot b/docs/fonts/OpenSans-Light-webfont.eot similarity index 100% rename from out/fonts/OpenSans-Light-webfont.eot rename to docs/fonts/OpenSans-Light-webfont.eot diff --git a/out/fonts/OpenSans-Light-webfont.svg b/docs/fonts/OpenSans-Light-webfont.svg similarity index 100% rename from out/fonts/OpenSans-Light-webfont.svg rename to docs/fonts/OpenSans-Light-webfont.svg diff --git a/out/fonts/OpenSans-Light-webfont.woff b/docs/fonts/OpenSans-Light-webfont.woff similarity index 100% rename from out/fonts/OpenSans-Light-webfont.woff rename to docs/fonts/OpenSans-Light-webfont.woff diff --git a/out/fonts/OpenSans-LightItalic-webfont.eot b/docs/fonts/OpenSans-LightItalic-webfont.eot similarity index 100% rename from out/fonts/OpenSans-LightItalic-webfont.eot rename to docs/fonts/OpenSans-LightItalic-webfont.eot diff --git a/out/fonts/OpenSans-LightItalic-webfont.svg b/docs/fonts/OpenSans-LightItalic-webfont.svg similarity index 100% rename from out/fonts/OpenSans-LightItalic-webfont.svg rename to docs/fonts/OpenSans-LightItalic-webfont.svg diff --git a/out/fonts/OpenSans-LightItalic-webfont.woff b/docs/fonts/OpenSans-LightItalic-webfont.woff similarity index 100% rename from out/fonts/OpenSans-LightItalic-webfont.woff rename to docs/fonts/OpenSans-LightItalic-webfont.woff diff --git a/out/fonts/OpenSans-Regular-webfont.eot b/docs/fonts/OpenSans-Regular-webfont.eot similarity index 100% rename from out/fonts/OpenSans-Regular-webfont.eot rename to docs/fonts/OpenSans-Regular-webfont.eot diff --git a/out/fonts/OpenSans-Regular-webfont.svg b/docs/fonts/OpenSans-Regular-webfont.svg similarity index 100% rename from out/fonts/OpenSans-Regular-webfont.svg rename to docs/fonts/OpenSans-Regular-webfont.svg diff --git a/out/fonts/OpenSans-Regular-webfont.woff b/docs/fonts/OpenSans-Regular-webfont.woff similarity index 100% rename from out/fonts/OpenSans-Regular-webfont.woff rename to docs/fonts/OpenSans-Regular-webfont.woff diff --git a/docs/global.html b/docs/global.html new file mode 100644 index 0000000..7259fdd --- /dev/null +++ b/docs/global.html @@ -0,0 +1,5003 @@ + + + + + JSDoc: Global + + + + + + + + + + +
    + +

    Global

    + + + + + + +
    + +
    + +

    + + +
    + +
    +
    + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + + + + + + + + + + + + + + +

    Members

    + + + +

    (constant) AddFavlistsToTalent

    + + + + +
    +

    Add To Favorite List

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ApplicantExtendedCard

    + + + + +
    +

    Applican Card

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ApplicationDetails

    + + + + +
    +

    Application Details

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) CreateDeduction

    + + + + +
    +

    Create deduction

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) EditOrAddExpiredShift

    + + + + +
    +

    EditOrAddExpiredShift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) fetchAllIfNull

    + + + + +
    +

    GENERIC ACTIONS, try to reuse them!!!!

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) fetchPeyrollPeriodPayments

    + + + + +
    +

    Fetch payroll period payments

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) FilterApplications

    + + + + +
    +

    Filter Applications

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) FilterLocations

    + + + + +
    +

    AddShift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) FilterShifts

    + + + + +
    +

    FilterShifts

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) FilterTalents

    + + + + +
    +

    AddShift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) GET

    + + + + +
    +

    Fetch JSON from API through GET method

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) getDeductionsReport

    + + + + +
    +

    Get deductions report

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) getPaymentsReport

    + + + + +
    +

    Get payments report

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) InviteTalentToJobcore

    + + + + +
    +

    ShiftDetails

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) InviteUserToCompanyJobcore

    + + + + +
    +

    Invite a new user to the company

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) makeEmployeePayment

    + + + + +
    +

    Make employee payment

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) PendingInvites

    + + + + +
    +

    ShiftDetails

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) PendingJobcoreInvites

    + + + + +
    +

    ShiftDetails

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) PendingRatings

    + + + + +
    +

    Talent Details

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) RateShift

    + + + + +
    +

    RateShift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) RatingDetails

    + + + + +
    +

    Talent Details

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) RatingEmployees

    + + + + +
    +

    Review Talent in general

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ReviewTalentAndShift

    + + + + +
    +

    Revire Talent for a specific shift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) SearchShiftToInviteTalent

    + + + + +
    +

    AddShift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) SearchTalentToInviteToShift

    + + + + +
    +

    Invite Talent To Shift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ShiftApplicants

    + + + + +
    +

    ShiftApplicants

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ShiftDetails

    + + + + +
    +

    ShiftDetails

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ShiftEmployees

    + + + + +
    +

    ShiftApplicants

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ShiftInvites

    + + + + +
    +

    ShiftApplicants

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) ShiftTalentClockins

    + + + + +
    +

    RateShift

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) TalentDetails

    + + + + +
    +

    Talent Details

    +

    Before, the Stars component was rendered inside a p tag, +now its rendered inside a span tag

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) UpdateDeduction

    + + + + +
    +

    Edit deduction

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + +

    (constant) updateProfileImage

    + + + + +
    +

    From here on the actions are not generic anymore

    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + +

    Methods

    + + + + + + + +

    BarChart(barData)

    + + + + + + +
    +

    Creates a bar chart with the data passed as an argument

    +
    + + + + + + + + + +
    Parameters:
    + + +

    Star Rating

    Quantity

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    barData + + +object + + + +

    Object with data like colors, labels, and values needed for the chart.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:Bar
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    ClockInsDataGenerator(props)

    + + + + + + +
    +

    Generates array of objects with clock-in trends for Punctuality.js.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    ClockOutsDataGenerator(props)

    + + + + + + +
    +

    Generates array of objects with clock-out trends for Punctuality.js.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    createMapOptions()

    + + + + + + +
    +

    Add a Location

    +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    EditOrAddShift()

    + + + + + + +
    +

    EditOrAddShift

    +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    FeatureIndicator()

    + + + + + + +
    +

    YourSubscription

    +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    filterClockins()

    + + + + + + +
    +

    SelectTimesheet

    +
    + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +

    GeneralStats(props)

    + + + + + + +
    +

    Creates a page with 3 tabs that show metrics about Shifts, Punctuality, and Hours.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all shifts, and an array of all the workers.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:Hours
    • + +
    • module:Shifts
    • + +
    • module:JobSeekers
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    Hours()

    + + + + + + +
    +

    Creates a page with a table and a graph of the hours worked and their trends.

    +
    + + + + + + + + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:PieChart
    • + +
    • module:HoursData
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    HoursDataGenerator(props)

    + + + + + + +
    +

    Takes in list a of shifts and generates data of all the hours trends for Hours.js.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    JobSeekers(props)

    + + + + + + +
    +

    Creates a page with 2 graphs and 2 charts showing trends on active, inactive, and new job seekers.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts, and also an array of all the workers.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:PieChart
    • + +
    • module:BarChart
    • + +
    • module:NewJobSeekersDataGenerator
    • + +
    • module:JobSeekersDataGenerator
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    JobSeekersDataGenerator(props)

    + + + + + + +
    +

    Takes in list a of shifts and job seekers and generates data of inactive/active job seekers for JobSeekers.js.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts, and also an array of all the workers.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    NewJobSeekersDataGenerator(props)

    + + + + + + +
    +

    Takes in list a of job seekers and generates data of new job seekers for JobSeekers.js.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts, and also an array of all the workers.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    PieChart(pieData)

    + + + + + + +
    +

    Creates a pie chart with the data passed as an argument.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    pieData + + +object + + + +

    Object with data like colors, labels, and values needed for the chart.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:Pie
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    Punctuality(props)

    + + + + + + +
    +

    Creates a page with 2 tables and 2 graphs of the clock-in and clock-out trends.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:PieChart
    • + +
    • module:ClockInsDataGenerator
    • + +
    • module:ClockOutsDataGenerator
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    Queue(props)

    + + + + + + +
    +

    Creates a page with a DatePicker and table of all employees with their worked/scheduled hours.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all shifts, and an array of all workers.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • + +
    • module:DatePicker
    • + +
    • module:QueueData
    • + +
    • module:Button
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    QueueData(props)

    + + + + + + +
    +

    Creates a table of all employees with their worked/scheduled hours for Queue.js

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all shifts, and an object with information of a single worker, previously mapped in Queue.js

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:moment
    • + +
    • module:Avatar
    • + +
    • module:Button
    • + +
    • module:Theme
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    Ratings(props)

    + + + + + + +
    +

    Creates a pie chart and a table reflecting how many job seekers are in each category of star ratings (1 to 5 stars.)

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all shifts, and an array of all workers.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:PieChart
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    Shifts(props)

    + + + + + + +
    +

    Creates a page with a table and a graph of all the shift statuses.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains an array of all the shifts.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + +
    Requires:
    +
      +
    • module:ShiftsDataGenerator
    • + +
    • module:BarChart
    • +
    + + + + + + + + + + + + + + + + + + + + + + + + +

    ShiftsDataGenerator(props)

    + + + + + + +
    +

    Takes in list a of shifts and generates data of shift statuses for Shifts.js.

    +
    + + + + + + + + + +
    Parameters:
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    props + + +object + + + +

    Contains a list of all shifts.

    + + + + + + +
    + + + + +
    Since:
    +
    • 09.29.22 by Paola Sanchez
    + + + + + + + + + + + + + + + +
    Author:
    +
    +
      +
    • Paola Sanchez
    • +
    +
    + + + + + + + + + +
    Source:
    +
    + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8b021e4 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,72 @@ + + + + + JSDoc: JSDoc Home + + + + + + + + + + +
    + +

    JSDoc Home

    + + + + + + + + +

    + + + + + + + + + + + + + + + +
    +

    Welcome to the documentation of JobCore's employer-web-client repo.

    +

    Here, you can explore the documentation of each function and block of code we have.

    +

    To navigate this documentation, please look at the list of names on the right side of the screen, under "Global."

    +

    There, you will find each documentation file.

    +
    + + + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + \ No newline at end of file diff --git a/out/scripts/linenumber.js b/docs/scripts/linenumber.js similarity index 100% rename from out/scripts/linenumber.js rename to docs/scripts/linenumber.js diff --git a/out/scripts/prettify/Apache-License-2.0.txt b/docs/scripts/prettify/Apache-License-2.0.txt similarity index 100% rename from out/scripts/prettify/Apache-License-2.0.txt rename to docs/scripts/prettify/Apache-License-2.0.txt diff --git a/out/scripts/prettify/lang-css.js b/docs/scripts/prettify/lang-css.js similarity index 100% rename from out/scripts/prettify/lang-css.js rename to docs/scripts/prettify/lang-css.js diff --git a/out/scripts/prettify/prettify.js b/docs/scripts/prettify/prettify.js similarity index 100% rename from out/scripts/prettify/prettify.js rename to docs/scripts/prettify/prettify.js diff --git a/out/styles/jsdoc-default.css b/docs/styles/jsdoc-default.css similarity index 100% rename from out/styles/jsdoc-default.css rename to docs/styles/jsdoc-default.css diff --git a/out/styles/prettify-jsdoc.css b/docs/styles/prettify-jsdoc.css similarity index 100% rename from out/styles/prettify-jsdoc.css rename to docs/styles/prettify-jsdoc.css diff --git a/out/styles/prettify-tomorrow.css b/docs/styles/prettify-tomorrow.css similarity index 100% rename from out/styles/prettify-tomorrow.css rename to docs/styles/prettify-tomorrow.css diff --git a/docs/utils_api_wrapper.js.html b/docs/utils_api_wrapper.js.html new file mode 100644 index 0000000..bef2185 --- /dev/null +++ b/docs/utils_api_wrapper.js.html @@ -0,0 +1,369 @@ + + + + + JSDoc: Source: utils/api_wrapper.js + + + + + + + + + + +
    + +

    Source: utils/api_wrapper.js

    + + + + + + +
    +
    +
    /* global localStorage, fetch */
    +import { logout } from '../actions';
    +import log from './log';
    +import { Session } from 'bc-react-session';
    +import { setLoading } from '../components/load-bar/LoadBar.jsx';
    +// import { getCookie } from '../csrftoken';
    +import Cookies from 'js-cookie'
    +
    +
    +const rootAPIendpoint = process.env.API_HOST + '/api';
    +
    +let HEADERS = {
    +  'Content-Type': 'application/json'
    +};
    +
    +// TODO: implemente a queue for requests and status, also avoid calling the same request twice
    +let PendingReq = {
    +  _requests: [],
    +  add: function (req) {
    +    this._requests.push(req);
    +    setLoading(true);
    +  },
    +  remove: function (req) {
    +    this._requests = this._requests.filter(r => r !== req);
    +    if (this._requests.length == 0) {
    +      setLoading(false);
    +    }
    +  }
    +};
    +
    +const getToken = () => {
    +  if (Session) {
    +    const payload = Session.getPayload();
    +    const token = payload.access_token;
    +    return token;
    +  }
    +  return null;
    +};
    +
    +const appendCompany = (data) => {
    +  if (Session && data) {
    +    const payload = Session.getPayload();
    +    data.employer = payload.user.profile.employer.id || payload.user.profile.employer;
    +    return data;
    +  }
    +};
    +
    +/* AVAILABLE MODELS
    +  - badges
    +  - employees
    +  - employers
    +  - favlists
    +  - positions
    +  - profiles
    +  - shifts
    +  - venues
    +  - oauth/token (generate token)
    +  - tokenuser (get user data from local saved token)
    +*/
    +
    +/**
    + * Fetch JSON from API through GET method
    + * @param {string} model Model data to be fetched. **Must be plural**
    + * @returns {data}
    + */
    +export const GET = async (endpoint, queryString = null, extraHeaders = {}) => {
    +  let url = `${rootAPIendpoint}/${endpoint}`;
    +  if (queryString) url += queryString;
    +
    +  HEADERS['Authorization'] = `JWT ${getToken()}`;
    +  const REQ = {
    +    method: 'GET',
    +    headers: Object.assign(HEADERS, extraHeaders)
    +  };
    +
    +  const req = new Promise((resolve, reject) => fetch(url, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +  PendingReq.add(req);
    +  return req;
    +};
    +
    +export const POST = (endpoint, postData, extraHeaders = {}) => {
    +  if (['user/register', 'login', 'user/password/reset','employers/me/jobcore-invites'].indexOf(endpoint) == -1) {
    +    HEADERS['Authorization'] = `JWT ${getToken()}`;
    +    postData = appendCompany(postData);
    +  }
    +  
    +  const REQ = {
    +    method: 'POST',
    +    headers: Object.assign(HEADERS, extraHeaders),
    +    body: JSON.stringify(postData),
    +    // mode: 'no-cors'
    +  };
    +
    +  const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +
    +//   const req = new Promise((resolve, reject) => {
    +//     const loadData = async () => {
    +//       const res = await fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +//       const data = await res.json();
    +//     }
    +//     loadData();
    +// });
    +
    +  PendingReq.add(req);
    +  return req;
    +};
    +// function getCookie(name) {
    +//   let cookieValue = null;
    +
    +//   if (document.cookie && document.cookie !== '') {
    +//       const cookies = document.cookie.split(';');
    +//       for (let i = 0; i < cookies.length; i++) {
    +//           const cookie = cookies[i].trim();
    +
    +//           // Does this cookie string begin with the name we want?
    +//           if (cookie.substring(0, name.length + 1) === (name + '=')) {
    +//               cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
    +
    +//               break;
    +//           }
    +//       }
    +//   }
    +
    +//   return cookieValue;
    +// }
    +// var csrftoken = getCookie('csrftoken');
    +// var headers = new Headers();
    +// headers.append('X-CSRFToken', csrftoken);
    +export const POSTcsrf = (endpoint, postData, extraHeaders = {}) => {
    +  // Cookies.get('csrftoken')
    +  // console.log("postData###", postData)
    +  Cookies.set('stripetoken', postData.id)
    +  if (['user/register', 'login', 'user/password/reset','employers/me/jobcore-invites'].indexOf(endpoint) == -1) {
    +    HEADERS['Authorization']  = `JWT ${getToken()}`,`X-CSRFToken ${Cookies.get('stripetoken')}`
    +    postData = appendCompany(postData);
    +  }
    +
    +  const REQ = {
    +    method: 'POST',
    +    headers: Object.assign(HEADERS, extraHeaders),
    +    body: JSON.stringify(postData),
    +    // mode: 'no-cors'
    +  };
    +  const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +  
    +  PendingReq.add(req);
    +  return req;
    +};
    +
    +export const POSTcsrf2 = (endpoint, postData, extraHeaders = {}) => {
    +  // Cookies.get('csrftoken')
    +  // console.log("postData###", postData)
    +  // Cookies.set('stripetoken', postData.id)
    +  if (['user/register', 'login', 'user/password/reset','employers/me/jobcore-invites'].indexOf(endpoint) == -1) {
    +    HEADERS['Authorization']  = `JWT ${getToken()}`,`X-CSRFToken ${Cookies.get('stripetoken')}`
    +    postData = appendCompany(postData);
    +  }
    +
    +  const REQ = {
    +    method: 'POST',
    +    headers: Object.assign(HEADERS, extraHeaders),
    +    body: JSON.stringify(postData),
    +    // mode: 'no-cors'
    +  };
    +  const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +  
    +  PendingReq.add(req);
    +  return req;
    +};
    +
    +// fetch('/api/upload', {
    +//     method: 'POST',
    +//     body: payload,
    +//     headers: headers,
    +//     credentials: 'include'
    +// }).  
    +export const PUTFiles = (endpoint, files) => {
    +  const headers = {
    +    'Authorization': `JWT ${getToken()}`
    +  };
    +
    +  var fetchBody = new FormData();
    +  for (const file of files) fetchBody.append('image', file, file.name);
    +
    +  const REQ = {
    +    headers,
    +    method: 'PUT',
    +    body: fetchBody
    +  };
    +
    +  const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +  PendingReq.add(req);
    +
    +  return req;
    +};
    +
    +export const PUT = (endpoint, putData, extraHeaders = {}) => {
    +  if (['register', 'login','user/password/reset'].indexOf(endpoint) == -1) {
    +    HEADERS['Authorization'] = `JWT ${getToken()}`;
    +  }
    +  const REQ = {
    +    method: 'PUT',
    +    headers: Object.assign(HEADERS, extraHeaders),
    +    body: JSON.stringify(putData)
    +  };
    +
    +  const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +  PendingReq.add(req);
    +  
    +  return req;
    +};
    +
    +export const DELETE = (endpoint, extraHeaders = {}) => {
    +  HEADERS['Authorization'] = `JWT ${getToken()}`;
    +
    +  const REQ = {
    +    method: 'DELETE',
    +    headers: Object.assign(HEADERS, extraHeaders)
    +  };
    +
    +  const req = new Promise((resolve, reject) => fetch(`${rootAPIendpoint}/${endpoint}`, REQ)
    +    .then((resp) => processResp(resp, req))
    +    .then(data => resolve(data))
    +    .catch(err => {
    +      processFailure(err, req);
    +      reject(err);
    +    })
    +  );
    +  PendingReq.add(req);
    +  return req;
    +};
    +
    +const processResp = function (resp, req = null) {
    +  PendingReq.remove(req);
    +  if (resp.ok) {
    +    if (resp.status == 204) return new Promise((resolve, reject) => resolve(true));
    +    else return resp.json();
    +  }
    +  else return new Promise(function (resolve, reject) {
    +    if (resp.status == 400) parseError(resp).catch((errorMsg) => reject(errorMsg));
    +    else if (resp.status == 404) reject(new Error('Not found'));
    +    else if (resp.status == 503) {
    +      logout();
    +      reject(new Error('The JobCore API seems to be unavailable'));
    +    }
    +    else if (resp.status == 401) {
    +      logout();
    +      reject(new Error('You are not authorized for this action'));
    +    }
    +    else if (resp.status >= 500 && resp.status < 600) {
    +      resp.json().then(err => reject(new Error(err.detail)))
    +        .catch((errorMsg) => reject(new Error('Something bad happened while completing your request! Please try again later.')));
    +    }
    +    else reject(new Error('Something went wrong'));
    +  });
    +};
    +
    +const processFailure = function (err, req = null) {
    +  PendingReq.remove(req);
    +  log.error(err);
    +};
    +
    +const parseError = (error) => new Promise(function (resolve, reject) {
    +  const errorPromise = error.json();
    +  errorPromise.then(json => {
    +    let errorMsg = '';
    +    for (let type in json) {
    +      if (Array.isArray(json[type])) errorMsg += json[type].join(',');
    +      else if (typeof json[type] === 'object' && json[type] !== null) errorMsg += Object.values(json[type]).join(',');
    +      else errorMsg += json[type];
    +    }
    +    reject(errorMsg);
    +  })
    +    .catch(error => {
    +      reject(error);
    +    });
    +});
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_applications.js.html b/docs/views_applications.js.html new file mode 100644 index 0000000..fa5f1c5 --- /dev/null +++ b/docs/views_applications.js.html @@ -0,0 +1,376 @@ + + + + + JSDoc: Source: views/applications.js + + + + + + + + + + +
    + +

    Source: views/applications.js

    + + + + + + +
    +
    +
    import React from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from 'prop-types';
    +import Select from 'react-select';
    +import { AcceptReject, Avatar, Stars, Theme, Wizard } from '../components/index';
    +import { store, rejectCandidate, acceptCandidate, fetchAllMe } from '../actions.js';
    +import queryString from 'query-string';
    +import { TIME_FORMAT, DATE_FORMAT } from '../components/utils.js';
    +import moment from 'moment';
    +import { callback, hasTutorial } from '../utils/tutorial';
    +//gets the querystring and creats a formData object to be used when opening the rightbar
    +export const getApplicationsInitialFilters = (catalog) => {
    +    let query = queryString.parse(window.location.search);
    +    if (typeof query == 'undefined') return {};
    +    if (!Array.isArray(query.positions)) query.positions = (typeof query.positions == 'undefined') ? [] : [query.positions];
    +    if (!Array.isArray(query.venues)) query.venues = (typeof query.venues == 'undefined') ? [] : [query.venues];
    +    return {
    +        positions: query.positions.map(pId => catalog.positions.find(pos => pos.value == pId)),
    +        venues: query.venues.map(bId => catalog.venues.find(b => b.value == bId))
    +    };
    +};
    +
    +export const Application = (data) => {
    +
    +    const _defaults = {
    +        //foo: 'bar',
    +        serialize: function () {
    +
    +            const newEntity = {
    +                //foo: 'bar'
    +                // favoritelist_set: data.favoriteLists.map(fav => fav.value)
    +            };
    +
    +            return Object.assign(this, newEntity);
    +        }
    +    };
    +
    +    let _entity = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +
    +            return _entity;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        },
    +        getFormData: () => {
    +            // const _formShift = {
    +            //     id: _entity.id,
    +            //     favoriteLists: _entity.favoriteLists.map(fav => ({ label: fav.title, value: fav.id }))
    +            // };
    +            // return _formShift;
    +        },
    +        filters: () => {
    +            const _filters = {
    +                positions: _entity.positions.map(item => item.value),
    +                minimum_hourly_rate: _entity.minimum_hourly_rate,
    +                venues: _entity.venues.map(item => item.value)
    +            };
    +            for (let key in _entity) if (typeof _entity[key] == 'function') delete _entity[key];
    +            return Object.assign(_entity, _filters);
    +        }
    +    };
    +};
    +
    +export class ManageApplicantions extends Flux.DashView {
    +
    +    constructor() {
    +        super();
    +        this.state = {
    +            applicants: [],
    +            runTutorial: hasTutorial(),
    +            steps: [
    +                {
    +                    target: '#applicant_details_header',
    +                    content: 'Here is everyone that has applied to your shifts but you haven\'t accepted or rejected',
    +                    placement: 'right'
    +                },
    +                {
    +                    target: '#filter_applicants',
    +                    content: 'You can also filter this list of applicants by any desired criteria',
    +                    placement: 'left'
    +                }
    +            ]
    +        };
    +    }
    +
    +    componentDidMount() {
    +
    +        this.filter();
    +        this.subscribe(store, 'applications', (applicants) => {
    +            this.filter(applicants);
    +        });
    +
    +        this.props.history.listen(() => {
    +            this.filter();
    +        });
    +        this.setState({ runTutorial: true });
    +        fetchAllMe(['applications']);
    +
    +    }
    +
    +    filter(applicants = null) {
    +        let filters = this.getFilters();
    +        if (!applicants) applicants = store.getState('applications');
    +        if (applicants) {
    +            this.setState({
    +                applicants: applicants.filter((applicant) => {
    +                    for (let f in filters) {
    +                        const matches = filters[f].matches(applicant);
    +                        if (!matches) return false;
    +                    }
    +
    +                    return true;
    +                }).sort((applicant) => moment().diff(applicant.created_at, 'minutes'))
    +            });
    +        }
    +        else this.setState({ applicant: [] });
    +    }
    +
    +    getFilters() {
    +        let filters = queryString.parse(window.location.search);
    +        for (let f in filters) {
    +            switch (f) {
    +                case "positions":
    +                    filters[f] = {
    +                        value: filters[f],
    +                        matches: (application) => {
    +                            if (!filters.positions || typeof filters.positions == undefined) return true;
    +                            else if (!Array.isArray(filters.positions.value)) {
    +                                return filters.positions.value == application.shift.position.id;
    +                            }
    +                            else {
    +                                if (filters.positions.value.length == 0) return true;
    +                                return filters.positions.value.find(posId => application.shift.position.id == posId) !== null;
    +                            }
    +                        }
    +                    };
    +                    break;
    +                case "minimum_hourly_rate":
    +                    filters[f] = {
    +                        value: filters[f],
    +                        matches: (application) => {
    +                            if (!filters.minimum_hourly_rate.value) return true;
    +                            if (isNaN(filters.minimum_hourly_rate.value)) return true;
    +                            return parseInt(application.shift.minimum_hourly_rate, 10) >= filters.minimum_hourly_rate.value;
    +                        }
    +                    };
    +                    break;
    +                case "venues":
    +                    filters[f] = {
    +                        value: filters[f],
    +                        matches: (application) => {
    +                            if (!filters.venues || typeof filters.venues == undefined) return true;
    +                            else if (!Array.isArray(filters.venues.value)) {
    +                                return filters.venues.value == application.shift.venue.id;
    +                            }
    +                            else {
    +                                if (filters.venues.value.length == 0) return true;
    +                                return filters.venues.value.find(posId => application.shift.venue.id == posId) !== null;
    +                            }
    +                        }
    +                    };
    +                    break;
    +                case "date":
    +                    filters[f] = {
    +                        value: filters[f],
    +                        matches: (shift) => {
    +                            const fdate = moment(filters.date.value);
    +                            return shift.date.diff(fdate, 'days') == 0;
    +                        }
    +                    };
    +                    break;
    +            }
    +        }
    +        return filters;
    +    }
    +
    +    render() {
    +        const applicansHTML = this.state.applicants.map((a, i) => (<ApplicantExtendedCard key={i} applicant={a} shift={a.shift} hover={true} />));
    +        return (<div className="p-1 listcontents">
    +            <Theme.Consumer>
    +                {({ bar }) => (<span>
    +                    {/* <Wizard continuous
    +                        steps={this.state.steps}
    +                        run={this.state.runTutorial}
    +                        callback={callback}
    +                    /> */}
    +                    <h1><span id="applicant_details_header">Applicant Details</span></h1>
    +                    {
    +                        (applicansHTML.length == 0) ?
    +                            <p>No applicants were found</p>
    +                            :
    +                            applicansHTML
    +                    }
    +                </span>)}
    +            </Theme.Consumer>
    +           
    +        </div>);
    +    }
    +}
    +
    +
    +/**
    + * Applican Card
    + */
    +export const ApplicantExtendedCard = (props) => {
    +    const startDate = props.shift.starting_at.format('ll');
    +    const startTime = props.shift.starting_at.format('LT');
    +    const endTime = props.shift.ending_at.format('LT');
    +    return (<Theme.Consumer>
    +        {({ bar }) =>
    +            (<li className="aplicantcard"
    +                onClick={() => bar.show({ slug: "show_single_applicant", data: props.applicant.employee, title: "Application Details" })}
    +            >
    +                <Avatar url={props.applicant.employee.user.profile.picture} />
    +                <AcceptReject
    +                    onAccept={() => acceptCandidate(props.shift.id, props.applicant.employee).then(() => props.onAccept ? props.onAccept() : null)}
    +                    onReject={() => rejectCandidate(props.shift.id, props.applicant.employee).then(() => props.onReject ? props.onReject() : null)}
    +                />
    +                <p>
    +                    <a href="#" className="shift-position">{props.applicant.employee.user.first_name + " " + props.applicant.employee.user.last_name} </a>
    +                    is applying for the {props.shift.position.title} position
    +                    at the <a href="#" className="shift-location"> {props.shift.venue.title}</a>
    +                    <span className="shift-date"> {startDate} from {startTime} to {endTime} </span>
    +                    {
    +                        (typeof props.shift.price == 'string') ?
    +                            <span className="shift-price"> ${props.shift.price}/hr.</span>
    +                            :
    +                            <span className="shift-price"> {props.shift.price.currencySymbol}{props.shift.price.amount}/{props.shift.price.timeframe}.</span>
    +                    }
    +                </p>
    +                <Stars rating={Number(props.applicant.employee.rating)} />
    +            </li>)}
    +    </Theme.Consumer>);
    +};
    +ApplicantExtendedCard.propTypes = {
    +    applicant: PropTypes.object.isRequired,
    +    onAccept: PropTypes.func,
    +    onReject: PropTypes.func,
    +    shift: PropTypes.object.isRequired
    +};
    +ApplicantExtendedCard.defaultProps = {
    +    onAccept: null,
    +    onReject: null
    +};
    +
    +/**
    + * Application Details
    + */
    +export const ApplicationDetails = (props) => {
    +    const applicant = props.catalog.applicant.employee || props.catalog.applicant;
    +    return (<Theme.Consumer>
    +        {({ bar }) =>
    +            (<li className="aplication-details">
    +                <Avatar url={applicant.user.profile.picture} />
    +                <p>{applicant.user.first_name + " " + applicant.user.last_name}</p>
    +                <Stars rating={Number(applicant.rating)} />
    +                <span>Doing 4 jobs</span>
    +                <p>$ 13 /hr Minimum Rate</p>
    +                <p>{applicant.user.profile.bio}</p>
    +            </li>)}
    +    </Theme.Consumer>);
    +};
    +ApplicationDetails.propTypes = {
    +    onSave: PropTypes.func.isRequired,
    +    catalog: PropTypes.object.isRequired
    +};
    +
    +
    +/**
    + * Filter Applications
    + */
    +export const FilterApplications = ({ onSave, onCancel, onChange, catalog, formData }) => {
    +return(
    +    <form>
    +        <div className="row">
    +            <div className="col">
    +                <label>Looking for</label>
    +                <Select isMulti
    +                    value={formData.positions}
    +                    options={catalog.positions}
    +                    onChange={(selection) => onChange({ positions: selection })}
    +                />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col">
    +                <label>Price / hour</label>
    +                <input type="number" className="form-control" onChange={(e) => onChange({ minimum_hourly_rate: e.target.value })} value={formData.minimum_hourly_rate} />
    +            </div>
    +            <div className="col">
    +                <label>Date</label>
    +                <input type="date" className="form-control" onChange={(e) => onChange({ date: e.target.value })} />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col">
    +                <label>Venue</label>
    +                <Select isMulti
    +                    value={formData.venues}
    +                    options={catalog.venues}
    +                    onChange={(selection) => onChange({ venues: selection })}
    +                />
    +            </div>
    +        </div>
    +        <div className="btn-bar">
    +            <button type="button" className="btn btn-primary" onClick={() => onSave()}>Apply Filters</button>
    +            <button type="button" className="btn btn-secondary" onClick={() => {
    +                    formData.venues = [];
    +                    formData.positions = [];
    +                    formData.date = '';
    +                    formData.minimum_hourly_rate = '';
    +                    onSave(false);
    +            }}>Clear Filters</button>
    +        </div>
    +    </form>
    +);
    +};
    +FilterApplications.propTypes = {
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    onChange: PropTypes.func.isRequired,
    +    formData: PropTypes.object, //contains the data needed for the form to load
    +    catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_deductions.js.html b/docs/views_deductions.js.html new file mode 100644 index 0000000..078af15 --- /dev/null +++ b/docs/views_deductions.js.html @@ -0,0 +1,252 @@ + + + + + JSDoc: Source: views/deductions.js + + + + + + + + + + +
    + +

    Source: views/deductions.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { validator, ValidationError } from '../utils/validation';
    +import PropTypes from 'prop-types';
    +import Select from 'react-select';
    +
    +export const Deduction = (data = {}) => {
    +
    +    const _defaults = {
    +        id: null,
    +        name: '',
    +        value: null,
    +        description: '',
    +        type: '',
    +        lock: false,
    +        serialize: function () {
    +
    +            const newDeduction = {
    +            };
    +
    +            return Object.assign(this, newDeduction);
    +        }
    +    };
    +
    +    let _deduction = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +            if (validator.isEmpty(_deduction.name)) throw new ValidationError('The deduction name cannot be empty');
    +            if (!_deduction.value) throw new ValidationError('Deduction cannot be empty');
    +            if (validator.isEmpty(_deduction.description)) throw new ValidationError('The deduction description cannot be empty');
    +            if (!_deduction.type) throw new ValidationError('The deduction type cannot be empty');
    +            return _deduction;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        }
    +    };
    +};
    +/**
    + * Create deduction
    + */
    +export const CreateDeduction = ({ 
    +    onSave, 
    +    onCancel, 
    +    onChange, 
    +    catalog, 
    +    formData, 
    +    bar, 
    +    error
    + }) => {
    +        return ( <form>
    +            <div className="row">
    +                <div className="col-6">
    +                    <label>Name:</label>
    +                    <input className="form-control"
    +                        value={formData.name}
    +                        onChange={(e)=> onChange({ name: e.target.value })}
    +                    />
    +                </div>
    +                <div className="col-6">
    +                    <label>Deduction</label>
    +                    <input 
    +                        type="number" 
    +                        className="form-control"
    +                        placeholder='0.00'
    +                        value={formData.value}
    +                        onChange={(e)=> onChange({value: e.target.value > 0 ? Number(e.target.value).toFixed(2) : ''})}
    +                    />
    +                </div>
    +            </div>
    +            <div className="row">
    +                <div className="col-12">
    +                    <label>Description:</label>
    +                    <input className="form-control"
    +                        value={formData.description}
    +                        onChange={(e)=> onChange({ description: e.target.value })}
    +                    />
    +                </div>
    +            </div>
    +            {/* <div className="row">
    +                <div className="col-1" style={{ flexDirection: "row", display: "flex", alignItems: "center" }}>
    +                    <input type="checkbox"
    +                        style={{ marginRight: "6px" }}
    +                        checked={formData.lock}
    +                        onChange={(e)=> onChange({ lock: e.target.checked})}
    +                    />
    +                    <label>Lock </label>
    +                </div>
    +            </div> */}
    +            <div className="row">
    +                <div className="col-12">
    +                    <label>Deduction type:</label>
    +                    <Select
    +                        value={catalog.deductionsTypes.find((a) => a.value === formData.type)}
    +                        onChange={(selection) => onChange({ type: selection.value.toString() })}
    +                        options={catalog.deductionsTypes}
    +                    />
    +                </div>
    +            </div>
    +            <div className="btn-bar">
    +                <button 
    +                type="button"
    +                className="btn btn-success" 
    +                onClick={() => onSave({
    +                    name: formData.name,
    +                    value: formData.value,
    +                    type: formData.type,
    +                    description: formData.description
    +                })}
    +                >
    +                    Create
    +                </button>
    +            </div>
    +        </form>);
    +};
    +
    +CreateDeduction.propTypes = {
    +    error: PropTypes.string,
    +    action: PropTypes.string,
    +    bar: PropTypes.object,
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    onChange: PropTypes.func.isRequired,
    +    formData: PropTypes.object,
    +    catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +/**
    + * Edit deduction
    + */
    +export const UpdateDeduction = ({
    +    onSave, 
    +    onCancel, 
    +    onChange, 
    +    catalog, 
    +    formData, 
    +    bar, 
    +    error
    + }) => {
    +        return ( <form>
    +            <div className="row">
    +                <div className="col-6">
    +                    <label>Name:</label>
    +                    <input className="form-control"
    +                        value={formData.name}
    +                        onChange={(e)=> onChange({ name: e.target.value })}
    +                    />
    +                </div>
    +                <div className="col-6">
    +                    <label>Deduction</label>
    +                    <input type="number" className="form-control"
    +                        value={formData.value}
    +                        onChange={(e)=> onChange({ value: Number(e.target.value)})}
    +                    />
    +                </div>
    +            </div>
    +            <div className="row">
    +                <div className="col-12">
    +                    <label>Description:</label>
    +                    <input className="form-control"
    +                        value={formData.description}
    +                        onChange={(e)=> onChange({ description: e.target.value })}
    +                    />
    +                </div>
    +            </div>
    +            <div className="row">
    +                <div className="col-1" style={{ flexDirection: "row", display: "flex", alignItems: "center" }}>
    +                    <input 
    +                        type="checkbox"
    +                        style={{ marginRight: "6px" }}
    +                        checked={formData.lock}
    +                        onChange={(e)=> onChange({ lock: e.target.checked})}
    +                    />
    +                    <label>Lock </label>
    +                </div>
    +            </div>
    +            <div className="btn-bar">
    +                <button 
    +                type="button"
    +                className="btn btn-success" 
    +                onClick={() => onSave({
    +                    id: formData.id,
    +                    name: formData.name,
    +                    value: formData.value,
    +                    lock: formData.lock,
    +                    description: formData.description
    +                })}
    +                >
    +                    Save
    +                </button>
    +            </div>
    +        </form>);
    +};
    +
    +UpdateDeduction.propTypes = {
    +    error: PropTypes.string,
    +    action: PropTypes.string,
    +    bar: PropTypes.object,
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    onChange: PropTypes.func.isRequired,
    +    formData: PropTypes.object,
    +    catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_favorites.js.html b/docs/views_favorites.js.html new file mode 100644 index 0000000..7ee612a --- /dev/null +++ b/docs/views_favorites.js.html @@ -0,0 +1,566 @@ + + + + + JSDoc: Source: views/favorites.js + + + + + + + + + + +
    + +

    Source: views/favorites.js

    + + + + + + +
    +
    +
    import React from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from "prop-types";
    +import {
    +  store,
    +  update,
    +  remove,
    +  updateTalentList,
    +  fetchAllMe,
    +  searchMe,
    +} from "../actions.js";
    +import { callback, hasTutorial } from "../utils/tutorial";
    +import {
    +  ListCard,
    +  EmployeeExtendedCard,
    +  Button,
    +  Theme,
    +  Wizard,
    +  SearchCatalogSelect,
    +  GenericCard,
    +} from "../components/index";
    +import Select from "react-select";
    +import { Session } from "bc-react-session";
    +import { Notify } from "bc-react-notifier";
    +import { GET } from "../utils/api_wrapper";
    +
    +export const Favlist = (data) => {
    +  const _defaults = {
    +    title: "",
    +    auto_accept_employees_on_this_list: true,
    +    employees: [],
    +    employer: Session.getPayload().user.profile.employer,
    +    serialize: function (filters = []) {
    +      const newEntity = {
    +        id: data.id,
    +        employees: _defaults.employees.map((emp) => emp.id || emp),
    +      };
    +      let response = Object.assign(this, newEntity);
    +
    +      filters.forEach((property) => delete response[property]);
    +      return response;
    +    },
    +  };
    +
    +  let _entity = Object.assign(_defaults, data);
    +  return {
    +    validate: () => {
    +      // if(!validator.isEmail(_entity.email)) throw new ValidationError('Please specify the email');
    +      // if(validator.isEmpty(_entity.first_name)) throw new ValidationError('Please specify the first name');
    +      // if(validator.isEmpty(_entity.last_name)) throw new ValidationError('Please specify the last name');
    +      return _entity;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      let _formShift = {
    +        id: _entity.id,
    +        title: _entity.title,
    +        auto_accept_employees_on_this_list:
    +          _entity.auto_accept_employees_on_this_list,
    +        employees: _entity.employees,
    +      };
    +      return _formShift;
    +    },
    +    filters: () => {
    +      const _filters = {
    +        // positions: _entity.positions.map( item => item.value ),
    +      };
    +      return Object.assign(_entity, _filters);
    +    },
    +  };
    +};
    +
    +export class ManageFavorites extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      lists: [],
    +    };
    +  }
    +
    +  componentDidMount() {
    +    const lists = store.getState("favlists");
    +    this.subscribe(store, "favlists", (lists) => {
    +      this.setState({ lists });
    +    });
    +    this.setState({ lists: lists ? lists : [], runTutorial: true });
    +
    +    fetchAllMe(["favlists"]);
    +  }
    +
    +  render() {
    +    return (
    +      <div className="p-1 listcontents">
    +        <h1>
    +          <span id="your-favorites-heading">Your Favorite List:</span>
    +        </h1>
    +        <Theme.Consumer>
    +          {({ bar }) =>
    +            this.state.lists.length == 0 ? (
    +              <p>
    +                You have no favorite lists yet,{" "}
    +                <a
    +                  href="#"
    +                  className="text-primary"
    +                  onClick={() => bar.show({ slug: "create_favlist" })}
    +                >
    +                  click here
    +                </a>{" "}
    +                to create your first
    +              </p>
    +            ) : (
    +              this.state.lists
    +                .sort((a, b) => a.title.localeCompare(b.title))
    +                .map((list, i) => (
    +                  <ListCard
    +                    key={i}
    +                    list={list}
    +                    onClick={() =>
    +                      bar.show({
    +                        slug: "favlist_employees",
    +                        data: list,
    +                        title: "List Details",
    +                      })
    +                    }
    +                  >
    +                    <button
    +                      type="button"
    +                      className="btn btn-secondary"
    +                      style={{ background: "transparent" }}
    +                      onClick={(e) => {
    +                        e.stopPropagation();
    +                        bar.show({
    +                          slug: "update_favlist",
    +                          data: list,
    +                          title: "List Details",
    +                        });
    +                      }}
    +                    >
    +                      <i className="fas fa-pencil-alt"></i>
    +                    </button>
    +                    <button
    +                      type="button"
    +                      className="btn btn-secondary"
    +                      style={{ background: "transparent" }}
    +                      onClick={(e) => {
    +                        e.stopPropagation();
    +                        const noti = Notify.info("Are you sure?", (answer) => {
    +                          if (answer) remove("favlists", list);
    +                          noti.remove();
    +                        });
    +                      }}
    +                    >
    +                      <i className="fas fa-trash-alt"></i>
    +                    </button>
    +                  </ListCard>
    +                ))
    +            )
    +          }
    +        </Theme.Consumer>
    +        
    +      </div>
    +    );
    +  }
    +}
    +
    +/**
    + * Add To Favorite List
    + */
    +export const AddFavlistsToTalent = ({
    +  onChange,
    +  formData,
    +  onSave,
    +  onCancel,
    +  catalog,
    +}) => {
    +  return (
    +    <Theme.Consumer>
    +      {({ bar }) => (
    +        <form>
    +          <div className="row">
    +            <div className="col-12">
    +              <label>Pick your favorite lists:</label>
    +              <Select
    +                isMulti
    +                className="select-favlists"
    +                value={formData.favoriteLists}
    +                options={[
    +                  {
    +                    label: "Add new favorite list",
    +                    value: "new_favlist",
    +                    component: AddorUpdateFavlist,
    +                  },
    +                ].concat(catalog.favlists)}
    +                onChange={(selection) => {
    +                  const create = selection.find(
    +                    (opt) => opt.value == "new_favlist"
    +                  );
    +                  if (create)
    +                    bar.show({ slug: "create_favlist", allowLevels: true });
    +                  else onChange({ favoriteLists: selection });
    +                }}
    +              />
    +            </div>
    +          </div>
    +          <p>Click on invite add the talent to your favorite lists</p>
    +          <div className="btn-bar">
    +            <Button color="primary" onClick={() => onSave()}>
    +              Save
    +            </Button>
    +            <Button color="secondary" onClick={() => onCancel()}>
    +              Cancel
    +            </Button>
    +          </div>
    +        </form>
    +      )}
    +    </Theme.Consumer>
    +  );
    +};
    +
    +AddFavlistsToTalent.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +export const AddorUpdateFavlist = ({
    +  formData,
    +  onChange,
    +  onSave,
    +  onCancel,
    +}) => (
    +  <form>
    +    <div className="row">
    +      <div className="col-12">
    +        <label>List name:</label>
    +        <input
    +          type="text"
    +          className="form-control"
    +          placeholder="List name"
    +          value={formData.title}
    +          onChange={(e) => onChange({ title: e.target.value })}
    +        />
    +      </div>
    +    </div>
    +    <div className="row mt-1">
    +      <div className="col-1 text-center">
    +        <input
    +          type="checkbox"
    +          placeholder="List name"
    +          checked={formData.auto_accept_employees_on_this_list}
    +          onChange={(e) =>
    +            onChange({ auto_accept_employees_on_this_list: e.target.checked })
    +          }
    +        />
    +      </div>
    +      <div className="col-11">
    +        Talents on this list do not require approval to join shifts
    +      </div>
    +    </div>
    +    <div className="btn-bar">
    +      <Button color="light" onClick={() => onCancel()}>
    +        Cancel
    +      </Button>
    +      <Button color="success" className="ml-2" onClick={() => onSave()}>
    +        Save
    +      </Button>
    +    </div>
    +  </form>
    +);
    +AddorUpdateFavlist.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  formData: PropTypes.object.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +export const FavlistEmployees = ({ formData, onChange, onSave, catalog }) => {
    +  const favlist = store.get("favlists", formData.id);
    +  return (
    +    <form>
    +      <Theme.Consumer>
    +        {({ bar }) => (
    +          <span>
    +            <div className="top-bar">
    +              <button
    +                type="button"
    +                className="btn btn-primary btn-sm rounded"
    +                onClick={() =>
    +                  bar.show({
    +                    slug: "update_favlist",
    +                    data: formData,
    +                    allowLevels: true,
    +                  })
    +                }
    +              >
    +                <i className="fas fa-pencil-alt"></i>
    +              </button>
    +              <button
    +                type="button"
    +                className="btn btn-secondary btn-sm rounded"
    +                onClick={() => onChange({ _mode: "add_talent" })}
    +              >
    +                <i className="fas fa-plus"></i>
    +              </button>
    +            </div>
    +            <div className="row">
    +              <div className="col-12">
    +                <label>Title: </label>
    +                <span>{favlist.title}</span>
    +              </div>
    +            </div>
    +            {typeof formData._mode == "undefined" ||
    +            formData._mode == "default" ? (
    +              <div className="row">
    +                <div className="col-12">
    +                  <label>Talents: </label>
    +                  {!favlist || favlist.employees.length == 0 ? (
    +                    <span>
    +                      There are no talents in this list yet,{" "}
    +                      <span
    +                        className="anchor"
    +                        onClick={() => onChange({ _mode: "add_talent" })}
    +                      >
    +                        click here to add a new one
    +                      </span>
    +                    </span>
    +                  ) : (
    +                    ""
    +                  )}
    +                </div>
    +              </div>
    +            ) : (
    +              ""
    +            )}
    +            {typeof formData._mode != "undefined" &&
    +            formData._mode == "add_talent" ? (
    +              <div className="row mb-2">
    +                <div className="col-12">
    +                  <label>Search for the talents you want to add</label>
    +                  <SearchCatalogSelect
    +                    isMulti={false}
    +                    onChange={(selection) => {
    +                      if (selection.value == "invite_talent_to_jobcore")
    +                        bar.show({
    +                          allowLevels: true,
    +                          slug: "invite_talent_to_jobcore",
    +                        });
    +                      else
    +                        updateTalentList("add", selection.value, formData.id)
    +                          .then(() => onChange({ _mode: "default" }))
    +                          .catch((error) => alert(error));
    +                    }}
    +                    searchFunction={(search) =>
    +                      new Promise((resolve, reject) =>
    +                        GET("catalog/employees?full_name=" + search)
    +                          .then((talents) =>
    +                            resolve(
    +                              [
    +                                {
    +                                  label: `${
    +                                    talents.length == 0 ? "No one found: " : ""
    +                                  }Invite "${search}" to JobCore?`,
    +                                  value: "invite_talent_to_jobcore",
    +                                },
    +                              ].concat(talents)
    +                            )
    +                          )
    +                          .catch((error) => reject(error))
    +                      )
    +                    }
    +                  />
    +                </div>
    +              </div>
    +            ) : (
    +              ""
    +            )}
    +            {favlist && favlist.employees.length > 0 ? (
    +              <div className="row">
    +                <div className="col-12">
    +                  <ul
    +                    className="scroll"
    +                    style={{
    +                      maxHeight: "600px",
    +                      overflowY: "auto",
    +                      padding: "10px",
    +                      margin: "-10px",
    +                    }}
    +                  >
    +                    {favlist.employees.map((em, i) => (
    +                      <EmployeeExtendedCard
    +                        key={i}
    +                        employee={em}
    +                        hover={false}
    +                        showFavlist={false}
    +                        onClick={() =>
    +                          bar.show({
    +                            slug: "show_single_talent",
    +                            data: em,
    +                            allowLevels: true,
    +                          })
    +                        }
    +                      >
    +                        <Button
    +                          className="mt-0"
    +                          icon="trash"
    +                          label="Delete"
    +                          onClick={() => {
    +                            updateTalentList("delete", em.id, formData.id);
    +                            //.then(() => onChange);
    +                          }}
    +                        />
    +                      </EmployeeExtendedCard>
    +                    ))}
    +                  </ul>
    +                </div>
    +              </div>
    +            ) : (
    +              ""
    +            )}
    +          </span>
    +        )}
    +      </Theme.Consumer>
    +    </form>
    +  );
    +};
    +FavlistEmployees.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  formData: PropTypes.object.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +export class ManagePayrates extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      locations: [],
    +    };
    +  }
    +
    +  componentDidMount() {
    +    this.filter();
    +    this.subscribe(store, "venues", (locations) => {
    +      this.setState({ locations });
    +    });
    +
    +    this.props.history.listen(() => {
    +      this.filter();
    +      this.setState({ firstSearch: false });
    +    });
    +  }
    +
    +  filter(locations = null) {
    +    searchMe("venues", window.location.search);
    +  }
    +
    +  render() {
    +    if (this.state.firstSearch) return <p>Search for any location</p>;
    +    const allowLevels = window.location.search != "";
    +    return (
    +      <div className="p-1 listcontents">
    +        <Theme.Consumer>
    +          {({ bar }) => (
    +            <span>
    +              <h1>
    +                <span id="talent_search_header">Payrates Search</span>
    +              </h1>
    +              {this.state.locations.map((l, i) => (
    +                <GenericCard
    +                  key={i}
    +                  hover={true}
    +                  onClick={() =>
    +                    bar.show({ slug: "update_location", data: l, allowLevels })
    +                  }
    +                >
    +                  <div className="btn-group">
    +                    <Button
    +                      icon="pencil"
    +                      onClick={() =>
    +                        bar.show({
    +                          slug: "update_location",
    +                          data: l,
    +                          allowLevels,
    +                        })
    +                      }
    +                    ></Button>
    +                    <Button
    +                      icon="trash"
    +                      onClick={() => {
    +                        const noti = Notify.info(
    +                          "Are you sure you want to delete this location?",
    +                          (answer) => {
    +                            if (answer) remove("venues", l);
    +                            noti.remove();
    +                          }
    +                        );
    +                      }}
    +                    ></Button>
    +                  </div>
    +                  <p className="mt-2">{l.title}</p>
    +                </GenericCard>
    +              ))}
    +            </span>
    +          )}
    +        </Theme.Consumer>
    +      </div>
    +    );
    +  }
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_invites.js.html b/docs/views_invites.js.html new file mode 100644 index 0000000..c760763 --- /dev/null +++ b/docs/views_invites.js.html @@ -0,0 +1,332 @@ + + + + + JSDoc: Source: views/invites.js + + + + + + + + + + +
    + +

    Source: views/invites.js

    + + + + + + +
    +
    +
    import React, { useEffect, useState } from 'react';
    +import {validator, ValidationError} from '../utils/validation';
    +import {update, searchMe} from '../actions';
    +import {Session} from 'bc-react-session';
    +import PropTypes from 'prop-types';
    +import {GET} from '../utils/api_wrapper';
    +import Select from 'react-select';
    +import {TIME_FORMAT, DATE_FORMAT, NOW} from '../components/utils.js';
    +import {Button, Theme, ShiftOption, ShiftOptionSelected, SearchCatalogSelect, ShiftCard} from '../components/index';
    +import {Shift} from "./shifts.js";
    +import moment from 'moment';
    +
    +export const Invite = (data) => {
    +
    +    const _defaults = {
    +        first_name: '',
    +        last_name: '',
    +        status: 'PENDING',
    +        created_at: NOW(),
    +        email: '',
    +        include_sms: undefined,
    +        phone_number: '',
    +        serialize: function(){
    +
    +            const newShift = {
    +                sender: Session.getPayload().user.profile.id
    +            };
    +
    +            return Object.assign(this, newShift);
    +        },
    +        unserialize: function(){
    +            const dataType = typeof this.created_at;
    +            //if its already serialized
    +            if((typeof this.position == 'object') && ['number','string'].indexOf(dataType) == -1) return this;
    +            const newInvite = {
    +                shift: Shift(this.shift).defaults().unserialize(),
    +                created_at: (!moment.isMoment(this.created_at)) ? moment(this.created_at) : this.created_at
    +            };
    +
    +            return Object.assign(this, newInvite);
    +        }
    +    };
    +
    +    let _entity = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +            if(!validator.isEmail(_entity.email)) throw new ValidationError('Please specify the email');
    +            if(validator.isEmpty(_entity.first_name)) throw new ValidationError('Please specify the first name');
    +            if(validator.isEmpty(_entity.last_name)) throw new ValidationError('Please specify the last name');
    +            //if(validator.isEmpty(_entity.phone_number.toString())) throw new ValidationError('Please specify the last name');
    +            return _entity;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        },
    +        getFormData: () => {
    +            const _formShift = {
    +                //foo: _entity.bar
    +            };
    +            return _formShift;
    +        },
    +        filters: () => {
    +            const _filters = {
    +                positions: _entity.positions.map( item => item.value ),
    +                badges: _entity.badges.map( item => item.value )
    +            };
    +            for(let key in _entity) if(typeof _entity[key] == 'function') delete _entity[key];
    +            return Object.assign(_entity, _filters);
    +        }
    +    };
    +};
    +
    +/**
    + * AddShift
    + */
    +export const SearchShiftToInviteTalent = (props) => {
    +
    +    const [ shifts, setShifts ] = useState([]);
    +    useEffect(() => {
    +        searchMe('shifts', `?upcoming=true&employee_not=${props.formData.employees.join(',')}&employee_not=${props.formData.employees.join(',')}`)
    +            .then(data => setShifts(data.map(item => ({ value: Shift(item).defaults().unserialize(), label: '' }))));
    +    }, []);
    +
    +    return (<form>
    +        <div className="row">
    +            <div className="col-12">
    +                <label>Pick your shifts:</label>
    +                <Select isMulti className="select-shifts"
    +                    value={props.formData.shifts}
    +                    components={{ Option: ShiftOption, MultiValue: ShiftOptionSelected({ multi: true }) }}
    +                    onChange={(selectedOption)=>props.onChange({ shifts: selectedOption })}
    +                    options={shifts}
    +                >
    +                </Select>
    +            </div>
    +        </div>
    +        <p>Click on invite to invite the talent to your selected shifts</p>
    +        <div className="btn-bar">
    +            <Button color="primary" onClick={() => props.onSave()}>Send Invite</Button>
    +        </div>
    +    </form>);
    +};
    +SearchShiftToInviteTalent.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +/**
    + * Invite Talent To Shift
    + */
    +export const SearchTalentToInviteToShift = ({ formData, onSave, onChange }) => {
    +    return (<Theme.Consumer>
    +        {({bar}) => (<form>
    +            <div className="row">
    +                <div className="col-12">
    +                    <label>Seach the JobCore Database:</label>
    +                    <SearchCatalogSelect
    +                        isMulti={true}
    +                        value={formData.pending_invites}
    +                        onChange={(selections)=> {
    +                            const invite = selections.find(opt => opt.value == 'invite_talent_to_jobcore');
    +                            if(invite) bar.show({
    +                                allowLevels: true,
    +                                slug: "invite_talent_to_jobcore",
    +                                onSave: (emp) => onChange({ pending_jobcore_invites: formData.pending_jobcore_invites.concat(emp) })
    +                            });
    +                            else onChange({ pending_invites: selections, employees: selections.map(opt => opt.value) });
    +                        }}
    +                        searchFunction={(search) => new Promise((resolve, reject) =>
    +                            GET('catalog/employees?full_name='+search)
    +                                .then(talents => resolve([
    +                                    { label: `${(talents.length==0) ? 'No one found: ':''}Invite "${search}" to jobcore`, value: 'invite_talent_to_jobcore' }
    +                                ].concat(talents)))
    +                                .catch(error => reject(error))
    +                        )}
    +                    />
    +                </div>
    +            </div>
    +            <p>Click on invite to invite the talent to your selected shifts</p>
    +            <div className="btn-bar">
    +                <Button color="primary" onClick={() => onSave()}>Send Invite</Button>
    +            </div>
    +        </form>)}
    +    </Theme.Consumer>);
    +};
    +SearchTalentToInviteToShift.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +/**
    + * ShiftDetails
    + */
    +export const InviteTalentToJobcore = ({ onSave, onCancel, onChange, catalog, formData }) => (<Theme.Consumer>
    +    {({bar}) => (
    +        <form id="invite_talent_jobcore">
    +            <div className="row">
    +                <div className="col-12">
    +                    <p>
    +                        <span>Invite someone into yor talent pool or </span>
    +                        <span className="anchor"
    +                            onClick={() => bar.show({ slug: "show_pending_jobcore_invites", allowLevels: true })}
    +                        >review previous invites</span>:
    +                    </p>
    +                </div>
    +            </div>
    +            <div className="row">
    +                <div className="col-12">
    +                    <label>Talent First Name</label>
    +                    <input type="text" className="form-control"
    +                        onChange={(e)=>onChange({first_name: e.target.value})}
    +                    />
    +                </div>
    +                <div className="col-12">
    +                    <label>Talent Last Name</label>
    +                    <input type="text" className="form-control"
    +                        onChange={(e)=>onChange({last_name: e.target.value})}
    +                    />
    +                </div>
    +                <div className="col-12">
    +                    <label>Talent email</label>
    +                    <input type="email" className="form-control"
    +                        onChange={(e)=>onChange({email: e.target.value})}
    +                    />
    +                </div>
    +                <div className="col-12">
    +                    <label>Talent Phone</label>
    +                    <input type="tel" className="form-control"
    +                        onChange={(e)=>onChange({phone_number: e.target.value})}
    +                    />
    +                    <div className="form-group text-left">
    +                        <input type="checkbox" className="mr-1"
    +                            onChange={(e) => onChange({ include_sms: !formData.include_sms })} checked={formData.include_sms}
    +                        />
    +                        Send invite throught SMS as well.
    +                    </div>
    +                </div>
    +            </div>
    +            <div className="btn-bar">
    +                <Button color="success" onClick={() => onSave()}>Send Invite</Button>
    +                <Button color="secondary" onClick={() => onCancel()}>Cancel</Button>
    +            </div>
    +        </form>
    +    )}
    +</Theme.Consumer>);
    +InviteTalentToJobcore.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  width: PropTypes.number,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    +/**
    + * ShiftDetails
    + */
    +export const PendingJobcoreInvites = ({ catalog, formData }) => (<div>
    +    <div className="row">
    +        <div className="col">
    +            <h2>These are your pending invites</h2>
    +            <ul className="li-white">
    +                { (catalog.jcInvites.length > 0) ?
    +                    catalog.jcInvites.map((inv, i) =>{
    +                        if(inv.status == "PENDING" || inv.status == "COMPANY"){
    +                            return(<li key={i}>
    +                                <button
    +                                    className="btn btn-primary float-right mt-2 btn-sm"
    +                                    onClick={() => update('jobcore-invites', inv)}
    +                                >Resend</button>
    +                                <p className="p-0 m-0">
    +                                    <span>{inv.first_name} {inv.last_name} {inv.employer && " (company)"} </span>
    +                                </p>
    +                                <span className="badge">{moment(inv.updated_at).fromNow()}</span>
    +                                <small>{inv.email}</small>
    +                            </li>);
    +                        }
    +                    }
    +                    ):
    +                    <p className="text-center">No pending invites</p>
    +                }
    +            </ul>
    +        </div>
    +    </div>
    +</div>);
    +PendingJobcoreInvites.propTypes = {
    +  width: PropTypes.number,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +/**
    + * ShiftDetails
    + */
    +export const PendingInvites = ({ catalog, formData }) => {
    +    return (<div>
    +        <div className="row">
    +            <div className="col">
    +                <h2>Pending invites for {formData.talent.user.first_name}</h2>
    +                <ul className="li-white mx-1">
    +                    { (catalog.invites.length > 0) ?
    +                        catalog.invites.map((inv, i) =>
    +                            (<ShiftCard key={i} shift={inv.shift} showStatus={false} hoverEffect={false} />)
    +                        ):
    +                        <p className="text-center">No pending invites</p>
    +                    }
    +                </ul>
    +            </div>
    +        </div>
    +    </div>);
    +};
    +PendingInvites.propTypes = {
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_locations.js.html b/docs/views_locations.js.html new file mode 100644 index 0000000..8fd4c06 --- /dev/null +++ b/docs/views_locations.js.html @@ -0,0 +1,559 @@ + + + + + JSDoc: Source: views/locations.js + + + + + + + + + + +
    + +

    Source: views/locations.js

    + + + + + + +
    +
    +
    import React from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from 'prop-types';
    +import {store, searchMe, remove,updateProfileMe} from '../actions.js';
    +import { GenericCard, Theme, Button,Wizard } from '../components/index';
    +import Select from 'react-select';
    +import queryString from 'query-string';
    +import {hasTutorial } from '../utils/tutorial';
    +import {Link} from "react-router-dom";
    +import { Session } from 'bc-react-session';
    +
    +import {validator, ValidationError} from '../utils/validation';
    +
    +import {Notify} from 'bc-react-notifier';
    +
    +import GoogleMapReact from 'google-map-react';
    +import markerURL from '../../img/marker.png';
    +import PlacesAutocomplete, {geocodeByAddress, getLatLng} from 'react-places-autocomplete';
    +
    +const ENTITIY_NAME = 'venues';
    +
    +//gets the querystring and creats a formData object to be used when opening the rightbar
    +export const getTalentInitialFilters = (catalog) => {
    +    let query = queryString.parse(window.location.search);
    +    if(typeof query == 'undefined') return {};
    +    if(!Array.isArray(query.positions)) query.positions = (typeof query.positions == 'undefined') ? [] : [query.positions];
    +    if(!Array.isArray(query.badges)) query.badges = (typeof query.badges == 'undefined') ? [] : [query.badges];
    +    return {
    +        positions: query.positions.map(pId => catalog.positions.find(pos => pos.value == pId)),
    +        badges: query.badges.map(bId => catalog.badges.find(b => b.value == bId)),
    +        rating: catalog.stars.find(rate => rate.value == query.rating)
    +    };
    +};
    +
    +export const Location = (data) => {
    +
    +    const _defaults = {
    +        id: '',
    +        title: '',
    +        street_address: '',
    +        country: '',
    +        latitude: 25.7617,
    +        longitude: 80.1918,
    +        state: '',
    +        zip_code: '',
    +        serialize: function(){
    +
    +            const newLocation = {
    +                latitude: this.latitude.toFixed(6),
    +                longitude: this.longitude.toFixed(6)
    +//                status: (this.status == 'UNDEFINED') ? 'DRAFT' : this.status,
    +            };
    +
    +            return Object.assign(this, newLocation);
    +        }
    +    };
    +
    +    let _location = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +            if(validator.isEmpty(_location.title)) throw new ValidationError('The location title cannot be empty');
    +            if(validator.isEmpty(_location.street_address)) throw new ValidationError('The location address cannot be empty');
    +            if(validator.isEmpty(_location.country)) throw new ValidationError('The location country cannot be empty');
    +            if(validator.isEmpty(_location.state)) throw new ValidationError('The location state cannot be empty');
    +            if(validator.isEmpty(_location.zip_code)) throw new ValidationError('The location zip_code cannot be empty');
    +            return _location;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        },
    +        getFormData: () => {
    +            const _formShift = {
    +                id: _location.id.toString(),
    +                title: _location.title,
    +                street_address: _location.street_address,
    +                latitude: parseFloat(_location.latitude),
    +                longitude: parseFloat(_location.longitude),
    +                country: _location.country,
    +                state: _location.state,
    +                date: _location.zip_code
    +            };
    +            return _formShift;
    +        }
    +    };
    +};
    +
    +export class ManageLocations extends Flux.DashView {
    +
    +    constructor(){
    +        super();
    +        this.state = {
    +            locations: [],
    +            currentUser: Session.getPayload().user.profile,
    +            runTutorial: hasTutorial(),
    +            steps: [
    +                {
    +                    content: <div><h2>Location page</h2><p>Here you will find all your company locations. You always can add new locations later.</p></div>,
    +                    placement: "center",   
    +                    disableBeacon: true,
    +
    +                    styles: {
    +                        options: {
    +                            zIndex: 10000
    +                        },
    +                        buttonClose: {
    +                            display: "none"
    +                        }
    +                    },
    +                    locale: { skip: "Skip tutorial" },
    +                    target: "body",
    +                    disableCloseOnEsc: true,
    +                    spotlightClicks: true
    +                    },
    +                {
    +                    target: '#create_location',
    +                    content: "Let's start by creating your new location",
    +                    placement: 'right',
    +                    styles: {
    +                        buttonClose: {
    +                            display: "none"
    +                        },
    +                        buttonNext: {
    +                            display: 'none'
    +                        }
    +                    },
    +                    spotlightClicks: true
    +
    +                },
    +                {
    +                    target: '#payroll',
    +                    content: 'Set up the company payroll',
    +                    placement: 'right',
    +                    styles: {
    +                        buttonClose: {
    +                            display: "none"
    +                        },
    +                        buttonNext: {
    +                            display: 'none'
    +                        }
    +                    },
    +                    spotlightClicks: true
    +                }
    +            ]
    +        };
    +    }
    +
    +    componentDidMount(){
    +
    +        this.filter();
    +        this.subscribe(store, ENTITIY_NAME, (locations) => {
    +            this.setState({ locations });
    +        });
    +
    +        this.props.history.listen(() => {
    +            this.filter();
    +            this.setState({ firstSearch: false });
    +        });
    +    }
    +
    +    filter(locations=null){
    +        searchMe(ENTITIY_NAME, window.location.search);
    +    }
    +
    +    render() {
    +        if(this.state.firstSearch) return <p>Search for any location</p>;
    +        const allowLevels = (window.location.search != '');
    +        console.log(this.state);
    +        return (<div className="p-1 listcontents">
    +            <Theme.Consumer>
    +                {({bar}) => (<span>
    +                    <Wizard continuous
    +                            steps={this.state.steps}
    +                            run={this.state.runTutorial}
    +                            callback={callback}
    +                            allowClicksThruHole= {true}
    +                            disableOverlay= {true}
    +                            spotlightClicks= {true}
    +                            styles={{
    +                                options: {
    +                                  primaryColor: '#000'
    +                                }
    +                              }}
    +                        />
    +                    <h1><span id="talent_search_header">Location Search</span></h1>
    +                    {this.state.locations.map((l,i) => (
    +                        <GenericCard key={i} hover={true} onClick={() => bar.show({ slug: "update_location", data: l, allowLevels })}>
    +                            <div className="btn-group">
    +                                <Button  icon="pencil" onClick={() => bar.show({ slug: "update_location", data: l, allowLevels })}></Button>
    +                                <Button  icon="trash" onClick={() => {
    +                                    const noti = Notify.info("Are you sure you want to delete this location?",(answer) => {
    +                                        if(answer) remove('venues', l);
    +                                        noti.remove();
    +                                    });
    +                                }}></Button>
    +                            </div>
    +                            <p className="mt-2">{l.title}</p>
    +                        </GenericCard>
    +                    ))}
    +                </span>)}
    +            </Theme.Consumer>
    +        </div>);
    +    }
    +}
    +
    +/**
    + * AddShift
    + */
    +export const FilterLocations = (props) => {
    +    return (<form>
    +        <div className="row">
    +            <div className="col-6">
    +                <label>First Name:</label>
    +                <input className="form-control"
    +                    value={props.formData.first_name}
    +                    onChange={(e)=>props.onChange({ first_name: e.target.value })}
    +                />
    +            </div>
    +            <div className="col-6">
    +                <label>Last Name:</label>
    +                <input className="form-control"
    +                    value={props.formData.last_name}
    +                    onChange={(e)=>props.onChange({ last_name: e.target.value })}
    +                />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-12">
    +                <label>Experience in past positions:</label>
    +                <Select isMulti
    +                    value={props.formData.positions}
    +                    onChange={(selectedOption)=>props.onChange({positions: selectedOption})}
    +                    options={props.catalog.positions}
    +                />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-12">
    +                <label>Badges:</label>
    +                <Select isMulti
    +                    value={props.formData.badges}
    +                    onChange={(selectedOption)=>props.onChange({badges: selectedOption})}
    +                    options={props.catalog.badges}
    +                />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-12">
    +                <label>Minimum start rating</label>
    +                <Select
    +                    value={props.formData.rating}
    +                    onChange={(opt)=>props.onChange({rating: opt})}
    +                    options={props.catalog.stars}
    +                />
    +            </div>
    +        </div>
    +        <div className="btn-bar">
    +            <Button color="primary" onClick={() => props.onSave()}>Apply Filters</Button>
    +            <Button color="secondary" onClick={() => props.onSave(false)}>Clear Filters</Button>
    +        </div>
    +    </form>);
    +};
    +FilterLocations.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +function callback (data) {
    +    console.log('DATA', data);
    + 
    +    // if(data.action == 'next' && data.index == 0){
    +    //     this.props.history.push("/payroll");
    +
    +    // }
    +    // if(data.type == 'tour:end'){
    +    //     const session = Session.get();
    +    //     updateProfileMe({show_tutorial: false});
    +        
    +    //     const profile = Object.assign(session.payload.user.profile, { show_tutorial: false });
    +    //     const user = Object.assign(session.payload.user, { profile });
    +    //     Session.setPayload({ user });
    +    // }
    +    if(data.action == 'skip'){
    +        const session = Session.get();
    +        updateProfileMe({show_tutorial: false});
    +        
    +        const profile = Object.assign(session.payload.user.profile, { show_tutorial: false });
    +        const user = Object.assign(session.payload.user, { profile });
    +        Session.setPayload({ user });
    +    }
    +}
    +/**
    + * Add a Location
    + */
    +function createMapOptions(maps) {
    +  // next props are exposed at maps
    +  // "Animation", "ControlPosition", "MapTypeControlStyle", "MapTypeId",
    +  // "NavigationControlStyle", "ScaleControlStyle", "StrokePosition", "SymbolPath", "ZoomControlStyle",
    +  // "DirectionsStatus", "DirectionsTravelMode", "DirectionsUnitSystem", "DistanceMatrixStatus",
    +  // "DistanceMatrixElementStatus", "ElevationStatus", "GeocoderLocationType", "GeocoderStatus", "KmlLayerStatus",
    +  // "MaxZoomStatus", "StreetViewStatus", "TransitMode", "TransitRoutePreference", "TravelMode", "UnitSystem"
    +  return {
    +    zoomControlOptions: {
    +      position: maps.ControlPosition.RIGHT_CENTER,
    +      style: maps.ZoomControlStyle.SMALL
    +    },
    +    zoomControl: true,
    +    scaleControl: false,
    +    fullscreenControl: false,
    +    mapTypeControl: false
    +  };
    +}
    +const Marker = ({ text }) => (<div><img style={{maxWidth: "25px"}} src={markerURL} /></div>);
    +Marker.propTypes = {
    +    text: PropTypes.string
    +};
    +export const AddOrEditLocation = ({onSave, onCancel, onChange, catalog, formData}) => (<Theme.Consumer>
    +    {({bar}) => (<div>
    +  
    +        <div className="row">
    +            <Wizard continuous
    +                            steps={ [
    +                                {
    +                                    target: '#address',
    +                                    content: 'Start by entering your first address here. This is where your employee will report to work. Note employees will be able to clock in/out within a certain radius of this location.',
    +                                    placement: 'left',
    +                                    disableBeacon: true,
    +                                    styles: {
    +                                        options: {
    +                                            zIndex: 10000
    +                                        },
    +                                        buttonClose: {
    +                                            display: "none"
    +                                        }
    +                                    },
    +                                    spotlightClicks: true
    +
    +                                },
    +                                {
    +                                    target: '#location_nickname',
    +                                    content: 'Add a nickname for your location. Note this only for your reference employee will not see the nickname.',
    +                                    placement: 'left',
    +                                    styles: {
    +                                        options: {
    +                                            zIndex: 10000
    +                                        },
    +                                        buttonClose: {
    +                                            display: "none"
    +                                        }
    +                                    },
    +                                    spotlightClicks: true
    +                                },
    +                                {
    +                                    target: '#location_details',
    +                                    content: 'Finalize location details',
    +                                    placement: 'left',
    +                                    styles: {
    +                                        options: {
    +                                            zIndex: 10000
    +                                        },
    +                                        buttonClose: {
    +                                            display: "none"
    +                                        }
    +                                    },
    +                                    spotlightClicks: true
    +                                },
    +                                {
    +                                    target: '#button_save',
    +                                    content: "Save your location information",
    +                                    placement: 'left',
    +                                    styles: {
    +                                        options: {
    +                                            zIndex: 10000
    +                                        },
    +                                        buttonClose: {
    +                                            display: "none"
    +                                        },
    +                                        buttonNext: {
    +                                            display: 'none'
    +                                        }
    +                                    },
    +                                     spotlightClicks: true
    +                                }
    +                            ]}
    +                            run={hasTutorial()}
    +                            callback={callback}
    +                            spotlightClicks={true}
    +                            styles={{
    +                                options: {
    +                                  primaryColor: '#000'
    +                                }
    +                              }}
    +
    +                        />  
    +            <div className="col-12" id="address">
    +                <label>Address</label>
    +                <PlacesAutocomplete
    +                    value={formData.street_address || ''}
    +                    onChange={(value)=>onChange({ street_address: value })}
    +                    onSelect={(address) => {
    +                        onChange({ street_address: address });
    +                        geocodeByAddress(address)
    +                          .then(results => {
    +                                const title = address.split(',')[0];
    +                                const pieces = results[0].address_components;
    +                                const getPiece = (name) => pieces.find((comp) => typeof comp.types.find(type => type == name) != 'undefined');
    +                                const country = getPiece('country');
    +                                const state = getPiece('administrative_area_level_1');
    +                                const zipcode = getPiece('postal_code');
    +                                onChange({ title, country: country.long_name, state: state.long_name, zip_code: zipcode.long_name });
    +                                return getLatLng(results[0]);
    +                          })
    +                          .then(coord => onChange({ latitude: coord.lat, longitude: coord.lng }))
    +                          .catch(error => Notify.error('There was an error obtaining the location coordinates'));
    +                    }}
    +                >
    +                    {({ getInputProps, getSuggestionItemProps, suggestions, loading }) => (
    +                        <div className="autocomplete-root">
    +                            <input {...getInputProps()} className="form-control" autoComplete="new-jobcore-location-employer"/>
    +                            <div className="autocomplete-dropdown-container bg-white">
    +                                {loading && <div>Loading...</div>}
    +                                {suggestions.map((suggestion,i) => (
    +                                    <div key={i} {...getSuggestionItemProps(suggestion)} className="p-2">
    +                                        <span>{suggestion.description}</span>
    +                                    </div>
    +                                ))}
    +                            </div>
    +                        </div>
    +                    )}
    +                </PlacesAutocomplete>
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-12" id="location_nickname">
    +                <label>Location nickname</label>
    +                <input type="text" className="form-control"
    +                    value={formData.title}
    +                    onChange={(e)=>onChange({title: e.target.value})}
    +                />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-6 pr-0">
    +                <label>Location</label>
    +                <div className="location-map">
    +                    <GoogleMapReact
    +                        bootstrapURLKeys={{ key: process.env.GOOGLE_MAPS_WEB_KEY }}
    +                        defaultCenter={{
    +                          lat: 25.7617,
    +                          lng: -80.1918
    +                        }}
    +                        width="100%"
    +                        height="100%"
    +                        center={{
    +                          lat: formData.latitude,
    +                          lng: formData.longitude
    +                        }}
    +                        options={createMapOptions}
    +                        defaultZoom={12}
    +                    >
    +                        <Marker
    +                            lat={formData.latitude}
    +                            lng={formData.longitude}
    +                            text={'Jobcore'}
    +                        />
    +                    </GoogleMapReact>
    +                </div>
    +            </div>
    +            <div className="col-6" id="location_details">
    +                <label>Country</label>
    +                <input type="text" className="form-control"
    +                    value={formData.country}
    +                    onChange={(e)=>onChange({country: e.target.value})}
    +                />
    +                <label>State</label>
    +                <input type="text" className="form-control"
    +                    value={formData.state}
    +                    onChange={(e)=>onChange({state: e.target.value})}
    +                />
    +                <label>Zip</label>
    +                <input type="number" className="form-control"
    +                    value={formData.zip_code}
    +                    onChange={(e)=>onChange({zip_code: e.target.value})}
    +                />
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-12">
    +                <div className="btn-bar">
    +                    {hasTutorial() == true ? (
    +                        <Link to ="/payroll/settings" onClick={()=> onSave()}>
    +                            <button id="button_save"type="button" className="btn btn-success">Save</button>
    +                        </Link>
    +                    ): (<button id="button_save"type="button" className="btn btn-success" onClick={() => onSave()}>Save</button>)}
    +                    <button type="button" className="btn btn-default" onClick={() => bar.close()}>Cancel</button>
    +                </div>
    +            </div>
    +        </div>
    +    </div>)}
    +</Theme.Consumer>);
    +AddOrEditLocation.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_metrics_charts.js.html b/docs/views_metrics_charts.js.html new file mode 100644 index 0000000..ecb122d --- /dev/null +++ b/docs/views_metrics_charts.js.html @@ -0,0 +1,112 @@ + + + + + JSDoc: Source: views/metrics/charts.js + + + + + + + + + + +
    + +

    Source: views/metrics/charts.js

    + + + + + + +
    +
    +
    import React from 'react';
    +import { Pie, Bar } from 'react-chartjs-2';
    +import { Chart as ChartJS } from 'chart.js/auto';
    +
    +/**
    + * @function
    + * @description Creates a pie chart with the data passed as an argument.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires Pie
    + * @param {object} pieData - Object with data like colors, labels, and values needed for the chart.
    + */
    +export const PieChart = ({ pieData }) => {
    +  return (
    +    <Pie
    +      data={pieData}
    +      options={{
    +        responsive: true,
    +        maintainAspectRatio: false,
    +        cutout: "0%",
    +        animation: {
    +          animateScale: true,
    +          animateRotate: true,
    +        }
    +      }}
    +    />
    +  )
    +}
    +
    +/**
    + * @function
    + * @description Creates a bar chart with the data passed as an argument
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires Bar
    + * @param {object} barData - Object with data like colors, labels, and values needed for the chart.
    + */
    +export const BarChart = ({ barData }) => {
    +  
    +  let delayed;
    +  return (
    +    <Bar
    +      data={barData}
    +      options={{
    +        responsive: true,
    +        maintainAspectRatio: false,
    +        animation: {
    +          onComplete: () => {
    +            delayed = true;
    +          },
    +          delay: (context) => {
    +            let delay = 0;
    +            if (context.type === 'data' && context.mode === 'default' && !delayed) {
    +              delay = context.dataIndex * 150 + context.datasetIndex * 100;
    +            }
    +            return delay;
    +          },
    +        }
    +      }}
    +    />
    +  )
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_metrics_general-stats_GeneralStats.js.html b/docs/views_metrics_general-stats_GeneralStats.js.html new file mode 100644 index 0000000..d31ac4b --- /dev/null +++ b/docs/views_metrics_general-stats_GeneralStats.js.html @@ -0,0 +1,115 @@ + + + + + JSDoc: Source: views/metrics/general-stats/GeneralStats.js + + + + + + + + + + +
    + +

    Source: views/metrics/general-stats/GeneralStats.js

    + + + + + + +
    +
    +
    import React from "react";
    +import { JobSeekers } from "./JobSeekers/JobSeekers";
    +import { Hours } from "./Hours/Hours";
    +import { Shifts } from "./Shifts/Shifts";
    +
    +/**
    + * @function
    + * @description Creates a page with 3 tabs that show metrics about Shifts, Punctuality, and Hours.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires Hours
    + * @requires Shifts
    + * @requires JobSeekers
    + * @param {object} props - Contains an array of all shifts, and an array of all the workers.
    + */
    +export const GeneralStats = (props) => {
    +
    +    // Setting up main data sources
    +    let workers = props.workers
    +    let shifts = props.shifts
    +    
    +    // Return ----------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <>
    +          <div className="row d-flex flex-column mx-2">
    +                    {/* Tabs Controller Starts */}
    +                    <nav>
    +                        <div className="nav nav-tabs nav-fill" id="nav-tab" role="tablist">
    +                            <a className="nav-item nav-link active" id="nav-shifts-tab" data-toggle="tab" href="#nav-shifts" role="tab" aria-controls="nav-shifts" aria-selected="true"><h2>Shifts</h2></a>
    +                            <a className="nav-item nav-link" id="nav-hours-tab" data-toggle="tab" href="#nav-hours" role="tab" aria-controls="nav-hours" aria-selected="false"><h2>Hours</h2></a>
    +                            <a className="nav-item nav-link" id="nav-job-seekers-tab" data-toggle="tab" href="#nav-job-seekers" role="tab" aria-controls="nav-job-seekers" aria-selected="false"><h2>Job Seekers</h2></a>
    +                        </div>
    +                    </nav>
    +                    {/* Tabs Controller Ends */}
    +
    +                    {/* Tabs Content Starts */}
    +                    <div
    +                        className="tab-content mt-5"
    +                        id="nav-tabContent"
    +                    >
    +                        {/* Shifts Tab Starts */}
    +                        <div className="tab-pane fade show active" id="nav-shifts" role="tabpanel" aria-labelledby="nav-shifts-tab">
    +                            <Shifts shifts={shifts} />
    +                        </div>
    +                        {/* Shifts Tab Ends */}
    +
    +                        {/* Hours Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-hours" role="tabpanel" aria-labelledby="nav-hours-tab">
    +                            <Hours shifts={shifts} />
    +                        </div>
    +                        {/* Hours Tab Ends */}
    +
    +                        {/* Job Seekers Tab Starts */}
    +                        <div className="tab-pane fade" id="nav-job-seekers" role="tabpanel" aria-labelledby="nav-job-seekers-tab">
    +                            <JobSeekers shifts={shifts} workers={workers} />
    +                        </div>
    +                        {/* Job Seekers Tab Ends */}
    +                    </div>
    +                    {/* Tabs Content Ends */}
    +                </div>
    +        </>
    +    )
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Hours.js.html b/docs/views_metrics_general-stats_Hours_Hours.js.html similarity index 53% rename from out/Hours.js.html rename to docs/views_metrics_general-stats_Hours_Hours.js.html index d2bbe7c..24da0ce 100644 --- a/out/Hours.js.html +++ b/docs/views_metrics_general-stats_Hours_Hours.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: Hours.js + JSDoc: Source: views/metrics/general-stats/Hours/Hours.js @@ -17,7 +17,7 @@
    -

    Source: Hours.js

    +

    Source: views/metrics/general-stats/Hours/Hours.js

    @@ -28,14 +28,8 @@

    Source: Hours.js

    import React from "react";
     import { PieChart } from "../../charts";
    -import { HoursData } from "./HoursData";
    +import { HoursDataGenerator } from "./HoursData";
     
    -// Colors
    -const purple = "#5c00b8";
    -const lightTeal = "#00ebeb";
    -const darkTeal = "#009e9e";
    -const lightPink = "#eb00eb";
    -const darkPink = "#b200b2";
     
     /**
      * @function
    @@ -45,10 +39,20 @@ 

    Source: Hours.js

    * @requires PieChart * @requires HoursData */ -export const Hours = () => { +export const Hours = (props) => { + + // Setting up main data source + let HoursData = HoursDataGenerator(props.shifts) // Data for pie chart ------------------------------------------------------------------------------------- + // Colors + const purple = "#5c00b8"; + const lightTeal = "#00ebeb"; + const darkTeal = "#009e9e"; + const lightPink = "#eb00eb"; + const darkPink = "#b200b2"; + // Preparing data to be passed to the chart component const hoursData = { labels: HoursData.map((data) => data.description), @@ -135,13 +139,13 @@

    Source: Hours.js


    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:28:27 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_Hours_HoursData.js.html b/docs/views_metrics_general-stats_Hours_HoursData.js.html new file mode 100644 index 0000000..efa3b44 --- /dev/null +++ b/docs/views_metrics_general-stats_Hours_HoursData.js.html @@ -0,0 +1,410 @@ + + + + + JSDoc: Source: views/metrics/general-stats/Hours/HoursData.js + + + + + + + + + + +
    + +

    Source: views/metrics/general-stats/Hours/HoursData.js

    + + + + + + +
    +
    +
    import moment from "moment";
    +
    +/**
    + * @function
    + * @description Takes in list a of shifts and generates data of all the hours trends for Hours.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @param {object} props - Contains an array of all the shifts.
    + */
    +export const HoursDataGenerator = (props) => {
    +
    +  // Assigning props to variable
    +  let shifts = props
    +  
    +  // 1st - Separation of shifts ----------------------------------------------------------
    +
    +  // Array for shifts with multiple clock-ins
    +  let multipleClockIns = [];
    +
    +  // Array for single clock-ins made by single workers
    +  let singleClockInSingleWorker = [];
    +
    +  // Gathering both clock-ins and clock-outs in a formatted 
    +  // way to keep at the useful data handy at all times.
    +  shifts.forEach((shift) => {
    +    if (shift.clockin.length > 1) {
    +      multipleClockIns.push({
    +        starting_at: shift.starting_at,
    +        ending_at: shift.ending_at,
    +        clockin: shift.clockin,
    +        id: shift.id,
    +        employees: shift.employees
    +      });
    +    } else if (shift.clockin.length === 1) {
    +      shift.clockin.forEach((clockIn) => {
    +        singleClockInSingleWorker.push({
    +          id: shift.id,
    +          starting_at: shift.starting_at,
    +          ending_at: shift.ending_at,
    +          started_at: clockIn.started_at,
    +          ended_at: clockIn.ended_at
    +        });
    +      });
    +    }
    +  });
    +
    +  // Setting up arrays for shifts with multiple
    +  // clock-ins but different amount of workers
    +  let multipleClockInsMultipleWorkers = [];
    +  let multipleClockInsSingleWorker = [];
    +
    +  // Separating shifts based on the number of workers present
    +  multipleClockIns.forEach((shift) => {
    +    if (shift.employees.length > 1) {
    +      // Adding shifts to 'multipleClockInsMultipleWorkers'
    +      multipleClockInsMultipleWorkers.push(shift.clockin);
    +    } else if (shift.employees.length === 1) {
    +      // Adding shifts to 'multipleClockInsSingleWorker'
    +      multipleClockInsSingleWorker.push(shift.clockin);
    +    }
    +  });
    +
    +  // Array of multiple clock-ins with multiple workers, but organized
    +  let MCIMWOrganized = [];
    +
    +  // Adding shifts to 'MCIMWOrganized'
    +  multipleClockInsMultipleWorkers.forEach((shift) => {
    +    // Unifying shifts that have the same worker
    +    let newObj = shift.reduce((obj, value) => {
    +      let key = value.employee;
    +      if (obj[key] == null) obj[key] = [];
    +
    +      obj[key].push(value);
    +      return obj;
    +    }, []);
    +
    +    newObj.forEach((shift) => {
    +      MCIMWOrganized.push(shift);
    +    });
    +  });
    +
    +  // Array for the polished version of 'multipleClockInsMultipleWorkers'
    +  let MCIMWPolished = [];
    +
    +  // Array for single clock-ins made by single workers
    +  // inside shifts with multiple workers present
    +  let singleClockinsMultipleWorkers = [];
    +
    +  // Separating shifts of multiple workers based on
    +  // how many clock-ins each worker has
    +  MCIMWOrganized.forEach((shift) => {
    +    if (shift.length === 1) {
    +      shift.forEach((clockIn) => {
    +        singleClockinsMultipleWorkers.push(clockIn);
    +      });
    +    } else if (shift.length > 1) {
    +      MCIMWPolished.push(shift);
    +    }
    +  });
    +
    +  // Array for polished version of 'singleClockinsMultipleWorkers'
    +  let SCIMWPolished = [];
    +
    +  // Adding shifts to 'SCIMWPolished' in a formatted 
    +  // way to keep at the useful data handy at all times.
    +  shifts.forEach((originalShift) => {
    +    singleClockinsMultipleWorkers.forEach((filteredShift) => {
    +      if (originalShift.id === filteredShift.shift) {
    +        SCIMWPolished.push({
    +          id: originalShift.id,
    +          started_at: filteredShift.started_at,
    +          ended_at: filteredShift.ended_at,
    +          starting_at: originalShift.starting_at,
    +          ending_at: originalShift.ending_at
    +        });
    +      }
    +    });
    +  });
    +
    +  // Combining all shifts with single clock-ins. These will not have break times.
    +  let singleClockInsCombined = [...singleClockInSingleWorker, ...SCIMWPolished];
    +
    +  // Combining all shifts with multiple clock-ins. These will have break times.
    +  let multipleClockInsCombined = [
    +    ...multipleClockInsSingleWorker,
    +    ...MCIMWPolished
    +  ];
    +
    +  // 2nd - Calculation of Hours and Minutes ----------------------------------------------
    +
    +  // Calculating scheduled hours of all single clock-in shifts ---------------------------
    +
    +  let SCICScheduledHours = singleClockInsCombined.reduce(
    +    (total, { starting_at, ending_at }) =>
    +      total +
    +      moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(),
    +    0
    +  );
    +
    +  // Total scheduled hours
    +  let SCICScheduledHoursF = parseInt(
    +    (Math.round(SCICScheduledHours * 4) / 4).toFixed(0),
    +    10
    +  );
    +
    +  // Calculating worked hours of all single clock-in shifts ------------------------------
    +
    +  let SCICWorkedHours = singleClockInsCombined.reduce(
    +    (total, { started_at, ended_at }) =>
    +      total +
    +      moment.duration(moment(ended_at).diff(moment(started_at))).asHours(),
    +    0
    +  );
    +
    +  // Total worked hours
    +  let SCICWorkedHoursF = parseInt(
    +    (Math.round(SCICWorkedHours * 4) / 4).toFixed(0),
    +    10
    +  );
    +
    +  // Extra worked hours
    +  let extraWorkedHoursSingleClockIns = SCICWorkedHoursF - SCICScheduledHoursF;
    +
    +  // Calculating scheduled hours of all multiple clock-in shifts -------------------------
    +
    +  // Array for scheduled minutes
    +  let MCICScheduledMinutes = [];
    +
    +  // Adding shifts to 'MCICScheduledMinutes'
    +  multipleClockInsCombined.forEach((shift) => {
    +    let shiftStart = moment(shift[0].started_at);
    +    let shiftEnd = moment(shift[shift.length - 1].ended_at);
    +    let id = shift[0].shift;
    +    let diff = moment.duration(shiftEnd.diff(shiftStart)).asMinutes();
    +
    +    MCICScheduledMinutes.push({
    +      id: id,
    +      employee: shift[0].employee,
    +      scheduled_mins: diff
    +    });
    +  });
    +
    +  // Total scheduled minutes
    +  let TotalMCICScheduledMinutes = MCICScheduledMinutes.reduce((acc, obj) => {
    +    return acc + obj.scheduled_mins;
    +  }, 0);
    +
    +  // Total scheduled hours
    +  let MCICScheduledHours =
    +    Math.floor(TotalMCICScheduledMinutes / 60) + SCICScheduledHoursF;
    +
    +  // Calculating worked hours of all multiple clock-in shifts ----------------------------
    +
    +  // Array for worked minutes
    +  let MCICWorkedMinutes = [];
    +
    +  // Adding shifts to 'MCICWorkedMinutes'
    +  multipleClockInsCombined.forEach((shift) => {
    +    shift.forEach((clockIn) => {
    +      let start = moment(clockIn.started_at);
    +      let end = moment(clockIn.ended_at);
    +      let id = clockIn.shift;
    +
    +      let diff = moment.duration(end.diff(start)).asMinutes();
    +
    +      MCICWorkedMinutes.push({
    +        id: id,
    +        employee: clockIn.employee,
    +        worked_mins: diff
    +      });
    +    });
    +  });
    +
    +  // Polished version of 'MCICWorkedMinutes'
    +  let MCICWorkedMinutesPolished = MCICWorkedMinutes.reduce(
    +    (result, { id, employee, worked_mins }) => {
    +      let temp = result.find((o) => {
    +        return o.id === id && o.employee === employee;
    +      });
    +      if (!temp) {
    +        result.push({ id, employee, worked_mins });
    +      } else {
    +        temp.worked_mins += worked_mins;
    +      }
    +      return result;
    +    },
    +    []
    +  );
    +
    +  // Total worked minutes
    +  let TotalMCICWorkedMinutes = MCICWorkedMinutesPolished.reduce((acc, obj) => {
    +    return acc + obj.worked_mins;
    +  }, 0);
    +
    +  // Total worked hours
    +  let MCICWorkedHours =
    +    Math.floor(TotalMCICWorkedMinutes / 60) + SCICWorkedHoursF;
    +
    +  // Extra worked hours
    +  let extraWorkedHoursMultipleClockIns = MCICWorkedHours - MCICScheduledHours;
    +
    +  // Extra calculations -----------------------------------------------------------------
    +
    +  // Array for the break times
    +  let breakTimes = [];
    +
    +  // Calculating break times of every shift
    +  MCICScheduledMinutes.forEach((scheduledShift) => {
    +    MCICWorkedMinutesPolished.forEach((workedShift) => {
    +      if (
    +        scheduledShift.id === workedShift.id &&
    +        scheduledShift.employee === workedShift.employee
    +      ) {
    +        let scheduled = scheduledShift.scheduled_mins;
    +        let worked = workedShift.worked_mins;
    +
    +        let diff = scheduled - worked;
    +
    +        breakTimes.push({
    +          id: scheduledShift.id,
    +          break_mins: diff
    +        });
    +      }
    +    });
    +  });
    +
    +  // Array for long breaks
    +  let longBreaks = [];
    +
    +  // Adding shifts to 'longBreaks'
    +  breakTimes.forEach((shift) => {
    +    if (shift.break_mins > 30) {
    +      longBreaks.push(shift);
    +    }
    +  });
    +
    +  // Calculating worked hours
    +  let workedHours = MCICWorkedHours + SCICWorkedHoursF;
    +
    +  // Calculating scheduled hours
    +  let scheduledHours = MCICScheduledHours + SCICScheduledHoursF;
    +
    +  // Calculating extra worked hours
    +  let extraWorkedHours =
    +    extraWorkedHoursSingleClockIns + extraWorkedHoursMultipleClockIns;
    +
    +  // Setting up conditional rendering of extra worked hours
    +  let qtyOfExtraWorkedHours = () => {
    +    if (extraWorkedHours > 0) {
    +      return extraWorkedHours;
    +    } else {
    +      return 0;
    +    }
    +  };
    +
    +  // 3rd - Setting up objects -----------------------------------------------------------
    +
    +  // THIS IS A PLACEHOLDER, this number should be
    +  // the total hours available of all employees
    +  let availableHours = 300;
    +
    +  // Creating object for scheduled hours
    +  let scheduledHoursObj = {
    +    description: "Scheduled Hours",
    +    qty: scheduledHours
    +  };
    +
    +  // Creating object for worked hours
    +  let workedHoursObj = {
    +    description: "Worked Hours",
    +    qty: workedHours
    +  };
    +
    +  // Creating object for extra worked hours
    +  let extraWorkedHoursObj = {
    +    description: "Extra Worked Hours",
    +    qty: qtyOfExtraWorkedHours()
    +  };
    +
    +  // Creating object for long breaks
    +  let longBreaksObj = {
    +    description: "Long Breaks",
    +    qty: `${longBreaks.length}`
    +  };
    +
    +  // Generate semi-final list
    +  let semiFinalList = [];
    +
    +  // Adding objects to semi-final list
    +  semiFinalList.push(scheduledHoursObj);
    +  semiFinalList.push(workedHoursObj);
    +  semiFinalList.push(extraWorkedHoursObj);
    +  semiFinalList.push(longBreaksObj);
    +
    +  // Generating final array with percentages as new properties
    +  let finalList = semiFinalList.map(({ description, qty }) => ({
    +    description,
    +    qty,
    +    pct: ((qty * 100) / availableHours).toFixed(0)
    +  }));
    +
    +  // Generating object of available hours
    +  let availableHoursObj = {
    +    description: "Available Hours",
    +    qty: availableHours,
    +    pct: "100"
    +  };
    +
    +  // Adding object of available hours to final list
    +  finalList.push(availableHoursObj);
    +
    +  // Adding IDs to each object in the array
    +  finalList.forEach((item, i) => {
    +    item.id = i + 1;
    +  });
    +
    +  // Returning the final array
    +  return finalList;
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html b/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html new file mode 100644 index 0000000..6337d09 --- /dev/null +++ b/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html @@ -0,0 +1,258 @@ + + + + + JSDoc: Source: views/metrics/general-stats/JobSeekers/JobSeekers.js + + + + + + + + + + +
    + +

    Source: views/metrics/general-stats/JobSeekers/JobSeekers.js

    + + + + + + +
    +
    +
    import React, { useEffect, useState } from "react";
    +import { PieChart, BarChart } from '../../charts';
    +import { JobSeekersDataGenerator, NewJobSeekersDataGenerator } from "./JobSeekersData";
    +
    +/**
    + * @function
    + * @description Creates a page with 2 graphs and 2 charts showing trends on active, inactive, and new job seekers.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires PieChart
    + * @requires BarChart
    + * @requires NewJobSeekersDataGenerator
    + * @requires JobSeekersDataGenerator
    + * @param {object} props - Contains an array of all the shifts, and also an array of all the workers.
    + */
    +export const JobSeekers = (props) => {
    +
    +    // Use state to hold list of workers and list of shifts
    +    const [workersList, setWorkersList] = useState([])
    +    const [shifsList, setShiftsList] = useState([])
    +
    +    // Receiving the props that contain the lists we need
    +    const handleProps = async () => {
    +
    +        // Catching the props when they arrive
    +        let propsObj = await props
    +
    +        // Checking length of lists before save them
    +        if (propsObj.workers.length > 0) {
    +            // Saving list of workers
    +            setWorkersList(propsObj.workers)
    +            // Saving list of shifts
    +            setShiftsList(propsObj.shifts)
    +        } else {
    +            // Handling error with a message
    +            console.log("Waiting for props to arrive")
    +        }
    +    }
    +
    +    // Triggering handleProps when props change/arrive
    +    useEffect(() => {
    +        handleProps()
    +    }, [props])
    +
    +    if (workersList.length > 0) {
    +
    +        // Setting up main data sources
    +        let JobSeekersData = JobSeekersDataGenerator(shifsList, workersList)
    +        let NewJobSeekersData = NewJobSeekersDataGenerator(workersList)
    +
    +        // Data for pie chart -------------------------------------------------------------------------------------
    +
    +        // Taking out the "Totals" from the chart view
    +        let pieData = JobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view
    +
    +        // Preparing data to be passed to the chart component
    +        const jobSeekersData = {
    +            labels: pieData.map((data) => data.description),
    +            datasets: [{
    +                label: "Job Seekers",
    +                data: pieData.map((data) => data.qty),
    +                backgroundColor: [
    +                    purple, lightPink
    +                ],
    +            }]
    +        }
    +
    +        // Data for bar chart -------------------------------------------------------------------------------------
    +
    +        // Colors
    +        const purple = "#5c00b8";
    +        const lightPink = "#eb00eb";
    +        const darkTeal = "#009e9e";
    +        const green = "#06ff05";
    +
    +        // Preparing data to be passed to the chart component
    +        const newJobSeekersData = {
    +            labels: NewJobSeekersData.map((data) => data.description),
    +            datasets: [{
    +                label: "New Job Seekers",
    +                data: NewJobSeekersData.map((data) => data.qty),
    +                backgroundColor: [
    +                    darkTeal, green
    +                ],
    +            }]
    +        }
    +
    +        // Return ----------------------------------------------------------------------------------------------------
    +
    +        return (
    +            <div className="row d-flex d-inline-flex justify-content-between w-100">
    +                {/* Left Column Starts */}
    +                <div className="col">
    +                    <div className="row d-flex flex-column justify-content-between mb-5">
    +                        {/* Job Seekers Table Starts */}
    +                        <div className="col text-center">
    +                            <h2 className="mb-4">Job Seekers Table</h2>
    +
    +                            <table className="table table-bordered text-center">
    +                                <thead className="thead-dark">
    +                                    {/* Table columns */}
    +                                    <tr>
    +                                        <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                        <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                        <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                    </tr>
    +                                </thead>
    +
    +                                <tbody>
    +                                    {/* Mapping the data to diplay it as table rows */}
    +                                    {JobSeekersData.map((item, i) => {
    +                                        return item.description === "Total Job Seekers" ? (
    +                                            <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                                <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                                <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                            </tr>
    +                                        ) :
    +                                            (
    +                                                <tr key={i}>
    +                                                    <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                                    <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                    <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                                </tr>
    +                                            )
    +                                    })}
    +                                </tbody>
    +                            </table>
    +                        </div>
    +                        {/* Job Seekers Table Ends */}
    +                    </div>
    +
    +                    <div className="row d-flex flex-column justify-content-between mb-5">
    +                        {/* New Job Seekers Table Starts */}
    +                        <div className="col text-center">
    +                            <h2 className="mb-4">New Job Seekers Table</h2>
    +
    +                            <table className="table table-bordered text-center">
    +                                <thead className="thead-dark">
    +                                    {/* Table columns */}
    +                                    <tr>
    +                                        <th scope="col"><h3 className="m-0">Description</h3></th>
    +                                        <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                        <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                    </tr>
    +                                </thead>
    +
    +                                <tbody>
    +                                    {/* Mapping the data to diplay it as table rows */}
    +                                    {NewJobSeekersData.map((item, i) => {
    +                                        return item.description === "Total Job Seekers" ? (
    +                                            <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                                <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                                <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                            </tr>
    +                                        ) :
    +                                            (
    +                                                <tr key={i}>
    +                                                    <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    +                                                    <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                    <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                                </tr>
    +                                            )
    +                                    })}
    +                                </tbody>
    +                            </table>
    +                        </div>
    +                        {/* New Job Seekers Table Ends */}
    +                    </div>
    +                </div>
    +                {/* Left Column Ends */}
    +
    +                {/* Right Column Starts */}
    +                <div className="col">
    +                    <div className="row">
    +                        {/* Job Seekers Chart Starts*/}
    +                        <div className="col text-center mb-5">
    +                            <h2 className="mb-3">Job Seekers Chart</h2>
    +
    +                            <div style={{ height: '13.90rem' }} className="mx-auto">
    +                                <PieChart pieData={jobSeekersData} />
    +                            </div>
    +                        </div>
    +                        {/* Job Seekers Chart Ends*/}
    +                    </div>
    +
    +                    <div className="row">
    +                        {/* New Job Seekers Chart Starts*/}
    +                        <div className="col text-center">
    +                            <h2 className="mb-3">New Job Seekers Chart</h2>
    +
    +                            <div style={{ height: '13.90rem' }} className="mx-auto">
    +                                <BarChart barData={newJobSeekersData} />
    +                            </div>
    +                        </div>
    +                        {/* New Job Seekers Chart Ends*/}
    +                    </div>
    +                </div>
    +                {/* Right Column Ends */}
    +            </div>
    +        )
    +    } else {
    +        return (
    +            <h1>Loading</h1>
    +        )
    +    }
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html b/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html new file mode 100644 index 0000000..d9d36f4 --- /dev/null +++ b/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html @@ -0,0 +1,225 @@ + + + + + JSDoc: Source: views/metrics/general-stats/JobSeekers/JobSeekersData.js + + + + + + + + + + +
    + +

    Source: views/metrics/general-stats/JobSeekers/JobSeekersData.js

    + + + + + + +
    +
    +
    import moment from "moment";
    +
    +// Today
    +const now = moment().format("YYYY-MM-DD")
    +
    +// Today, four weeks in the past
    +const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD")
    +
    +// Job Seekers Data ------------------------------------------------------------------
    +
    +/**
    + * @function
    + * @description Takes in list a of shifts and job seekers and generates data of inactive/active job seekers for JobSeekers.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @param {object} props - Contains an array of all the shifts, and also an array of all the workers.
    + */
    +export const JobSeekersDataGenerator = (shiftsProp, workersProp) => {
    +
    +    // Assigning props to variables
    +    let shifts = shiftsProp
    +    let workers = workersProp
    +
    +    // Array for all clock-ins
    +    let clockInsList = []
    +
    +    // Gathering the clock-ins of all the shifts
    +    shifts.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            // Keeping all clockins array with at
    +            // least one object inside
    +            if (shift.clockin.length > 0) {
    +                clockInsList.push(clockIn);
    +            }
    +        })
    +    })
    +
    +    // Array for all recent clock-ins
    +    let recentClockIns = []
    +
    +    // Filtering out clock-ins that happened longer than 4 weeks ago
    +    clockInsList.forEach((clockIn) => {
    +        let clockInStart = moment(clockIn.started_at).format("YYYY-MM-DD");
    +
    +        if (clockInStart > fourWeeksBack && clockInStart < now) {
    +            recentClockIns.push(clockIn)
    +        }
    +    })
    +
    +    // Array for worker ids
    +    let workerIDs = []
    +
    +    // Gethering all worker ids from recent clock-ins
    +    recentClockIns.forEach((clockIn) => {
    +        workerIDs.push(clockIn.employee)
    +    })
    +
    +    // Filtering out repeated worker ids
    +    let filteredWorkerIDs = [...new Set(workerIDs)];
    +
    +    // Calculating total, active, and inactive workers
    +    let totalWorkers = workers.length
    +    let totalActiveWorkers = filteredWorkerIDs.length
    +    let totalInactiveWorkers = totalWorkers - totalActiveWorkers
    +
    +    // Setting up objects for the semi-final array
    +    let activeWorkers = {
    +        id: 1,
    +        description: "Active Job Seekers",
    +        qty: totalActiveWorkers
    +    }
    +    let inactiveWorkers = {
    +        id: 2,
    +        description: "Inactive Job Seekers",
    +        qty: totalInactiveWorkers
    +    }
    +
    +    // Creating the semi-final array
    +    let semiFinalList = []
    +
    +    // Adding objects to the semi-final array
    +    semiFinalList.push(activeWorkers)
    +    semiFinalList.push(inactiveWorkers)
    +
    +    // Generating final array with percentages as new properties
    +    let finalList = semiFinalList.map(({ id, description, qty }) => ({
    +        id,
    +        description,
    +        qty,
    +        pct: ((qty * 100) / totalWorkers).toFixed(0)
    +    }));
    +
    +    // Generating the object of total workers
    +    let totalJobSeekers = {
    +        id: 3,
    +        description: "Total Job Seekers",
    +        qty: totalWorkers,
    +        pct: "100"
    +    }
    +
    +    // Adding the object of total workers to the final array
    +    finalList.push(totalJobSeekers)
    +
    +    // Returning the final array
    +    return finalList
    +}
    +
    +// New Job Seekers Data ------------------------------------------------------------------
    +
    +/**
    + * @function
    + * @description Takes in list a of job seekers and generates data of new job seekers for JobSeekers.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @param {object} props - Contains an array of all the shifts, and also an array of all the workers.
    + */
    +export const NewJobSeekersDataGenerator = (props) => {
    +
    +    // Assigning props to variable
    +    // Here we only need the array of workers
    +    let workers = props
    +
    +    // Array for new workers
    +    let newWorkersList = []
    +
    +    // Adding workers to 'newWorkersList' based on their creation date
    +    workers.forEach((worker) => {
    +        let creation_date = moment(worker.created_at).format("YYYY-MM-DD");
    +
    +        if (creation_date > fourWeeksBack && creation_date < now) {
    +            newWorkersList.push(worker)
    +        }
    +    })
    +
    +    // Setting up some variables for the objects
    +    let totalWorkers = workers.length
    +    let totalNewWorkers = newWorkersList.length
    +
    +    // Setting up objects for the semi-final array
    +    let newWorkers = {
    +        id: 0,
    +        description: "New Job Seekers",
    +        qty: totalNewWorkers
    +    }
    +
    +    // Creating the semi-final array
    +    let semiFinalList = []
    +
    +    // Adding objects to the semi-final array
    +    semiFinalList.push(newWorkers)
    +
    +    // Generating final array with percentages as new properties
    +    let finalList = semiFinalList.map(({ id, description, qty }) => ({
    +        id,
    +        description,
    +        qty,
    +        pct: ((qty * 100) / totalWorkers).toFixed(0)
    +    }));
    +
    +    // Generating the object of total workers
    +    let totalJobSeekers = {
    +        id: 1,
    +        description: "Total Job Seekers",
    +        qty: totalWorkers,
    +        pct: "100"
    +    }
    +
    +    // Adding the object of total workers to the final array
    +    finalList.push(totalJobSeekers)
    +
    +    // Returning the final array
    +    return finalList
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Shifts.js.html b/docs/views_metrics_general-stats_Shifts_Shifts.js.html similarity index 52% rename from out/Shifts.js.html rename to docs/views_metrics_general-stats_Shifts_Shifts.js.html index 5ef4a2e..8124c07 100644 --- a/out/Shifts.js.html +++ b/docs/views_metrics_general-stats_Shifts_Shifts.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: Shifts.js + JSDoc: Source: views/metrics/general-stats/Shifts/Shifts.js @@ -17,7 +17,7 @@
    -

    Source: Shifts.js

    +

    Source: views/metrics/general-stats/Shifts/Shifts.js

    @@ -28,29 +28,33 @@

    Source: Shifts.js

    import React from "react";
     import { BarChart } from "../../charts";
    -import { ShiftsData } from "./ShiftsData";
    -
    -// Colors
    -const purple = "#5c00b8";
    -const lightTeal = "#00ebeb";
    -const darkTeal = "#009e9e";
    -const lightPink = "#eb00eb";
    -const darkPink = "#b200b2";
    +import { ShiftsDataGenerator } from "./ShiftsData";
     
     /**
      * @function
      * @description Creates a page with a table and a graph of all the shift statuses.
      * @since 09.29.22 by Paola Sanchez
      * @author Paola Sanchez
    - * @requires ShiftsData
    + * @requires ShiftsDataGenerator
      * @requires BarChart
    + * @param {object} props - Contains an array of all the shifts.
      */
    -export const Shifts = () => {
    +export const Shifts = (props) => {
    +
    +    // Setting up main data source
    +    let ShiftsData = ShiftsDataGenerator(props.shifts)
     
         // Data for bar chart -------------------------------------------------------------------------------------
     
    -    // Taking out the "Totals" from the chart vie
    -    let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted "}) // Taking out the "Totals" from the chart view
    +    // Colors
    +    const purple = "#5c00b8";
    +    const lightTeal = "#00ebeb";
    +    const darkTeal = "#009e9e";
    +    const lightPink = "#eb00eb";
    +    const darkPink = "#b200b2";
    +
    +    // Taking out the "Totals" from the chart view
    +    let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted " }) // Taking out the "Totals" from the chart view
     
         // Preparing data to be passed to the chart component
         const shiftsData = {
    @@ -138,13 +142,13 @@ 

    Source: Shifts.js


    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:41:46 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html b/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html new file mode 100644 index 0000000..ba990f3 --- /dev/null +++ b/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html @@ -0,0 +1,146 @@ + + + + + JSDoc: Source: views/metrics/general-stats/Shifts/ShiftsData.js + + + + + + + + + + +
    + +

    Source: views/metrics/general-stats/Shifts/ShiftsData.js

    + + + + + + +
    +
    +
    /**
    + * @function
    + * @description Takes in list a of shifts and generates data of shift statuses for Shifts.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @param {object} props - Contains a list of all shifts.
    + */
    +export const ShiftsDataGenerator = (props) => {
    +
    +    // Assigning props to variable
    +    let shifts = props
    +
    +    // First array
    +    let shiftsList = [];
    +
    +    // Gathering all the existing shifts
    +    shifts.forEach((shift) => {
    +        shiftsList.push({
    +            status: shift.status,
    +            clockin: shift.clockin,
    +            employees: shift.employees
    +        });
    +    });
    +
    +    // Setting up counters
    +    let open = 0
    +    let filled = 0
    +    let completed = 0
    +    let rejected = 0
    +    let total = shiftsList.length
    +
    +    // Adding values to each counter based on certain shift conditions
    +    shiftsList.forEach((item) => {
    +        if (item.status === "EXPIRED" && item.clockin.length === 0 && item.employees.length === 0) {
    +            rejected++
    +        } else if (item.status === "FILLED") {
    +            filled++
    +        } else if (item.status === "COMPLETED") {
    +            completed++
    +        } else if (item.status === "OPEN") {
    +            open++
    +        }
    +    })
    +
    +    // Creating shift objects
    +    let openShifts = {
    +        description: "Open Shifts",
    +        qty: open
    +    }
    +    let filledShifts = {
    +        description: "Filled Shifts",
    +        qty: filled
    +    }
    +    let workedShifts = {
    +        description: "Worked Shifts",
    +        qty: completed
    +    }
    +    let rejectedShifts = {
    +        description: "Rejected Shifts",
    +        qty: rejected
    +    }
    +
    +    // Setting up base array for all shift objects
    +    let cleanedArray = []
    +
    +    // Pushing shift objects to base array
    +    cleanedArray.push(openShifts)
    +    cleanedArray.push(filledShifts)
    +    cleanedArray.push(workedShifts)
    +    cleanedArray.push(rejectedShifts)
    +
    +    // Generating final array with percentages as new properties
    +    let percentagesArray = cleanedArray.map(({ description, qty }) => ({
    +        description,
    +        qty,
    +        pct: ((qty * 100) / total).toFixed(0)
    +    }));
    +
    +    // Generating the object of total shifts
    +    let totalShifs = {
    +        description: "Total Shifts Posted",
    +        qty: total,
    +        pct: "100"
    +    }
    +
    +    // Adding the object of total shifts to the final array
    +    percentagesArray.push(totalShifs)
    +
    +    // Adding id's to each object in the final array
    +    percentagesArray.forEach((item, i) => {
    +        item.id = i + 1;
    +    });
    +
    +    // Returning the final array
    +    return percentagesArray
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/metrics.js.html b/docs/views_metrics_metrics.js.html similarity index 62% rename from out/metrics.js.html rename to docs/views_metrics_metrics.js.html index b2bff95..be489aa 100644 --- a/out/metrics.js.html +++ b/docs/views_metrics_metrics.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: metrics.js + JSDoc: Source: views/metrics/metrics.js @@ -17,7 +17,7 @@
    -

    Source: metrics.js

    +

    Source: views/metrics/metrics.js

    @@ -38,16 +38,18 @@

    Source: metrics.js

    import { store, search } from "../../actions"; /** - * @description Creates the view for Metrics page, which renders tabs with different modules being called inside each one. + * @description Creates the view for Metrics page, which renders 4 tabs with different components being called inside each one. * @since 09.28.22 by Paola Sanchez * @author Paola Sanchez - * * @requires Punctuality * @requires Ratings * @requires GeneralStats * @requires Queue + * @requires store + * @requires search + * @requires Session + * @requries Flux */ - export class Metrics extends Flux.DashView { constructor() { @@ -59,7 +61,7 @@

    Source: metrics.js

    employees: [], // This will hold all the shifts. DocStatus: "", //This is needed to check the verification status of employees. empStatus: "unverified", //This is needed to filter out unverified employees. - + // Variables for the Shifts' List allShifts: [], // This will hold all the shifts. session: Session.get(), // This is needed to crate a user/employer session. @@ -130,10 +132,10 @@

    Source: metrics.js

    render() { // List of workers with verified documents - const verifiedEmpList = this.state.employees.filter((employees) => employees.employment_verification_status === "APPROVED") + let verifiedEmpList = this.state.employees.filter((employees) => employees.employment_verification_status === "APPROVED") // List of all shifts - const listOfShifts = this.state.allShifts; + let listOfShifts = this.state.allShifts; // --------------------------------------------- // Filtering expired shifts @@ -174,25 +176,25 @@

    Source: metrics.js

    > {/* General Stats Tab Starts */} <div className="tab-pane fade show active" id="nav-general-stats" role="tabpanel" aria-labelledby="nav-general-stats-tab"> - <GeneralStats /> + <GeneralStats workers={verifiedEmpList} shifts={listOfShifts} /> </div> {/* General Stats Tab Ends */} {/* Punctuality Tab Starts */} <div className="tab-pane fade" id="nav-punctuality" role="tabpanel" aria-labelledby="nav-punctuality-tab"> - <Punctuality /> + <Punctuality shifts={listOfShifts} /> </div> {/* Punctuality Tab Ends */} {/* Ratings Tab Starts */} <div className="tab-pane fade" id="nav-ratings" role="tabpanel" aria-labelledby="nav-ratings-tab"> - <Ratings /> + <Ratings workers={verifiedEmpList} /> </div> {/* Ratings Tab Ends */} {/* Queue Tab Starts */} <div className="tab-pane fade" id="nav-queue" role="tabpanel" aria-labelledby="nav-queue-tab"> - <Queue shifts={listOfShifts} workers={verifiedEmpList} /> + <Queue workers={verifiedEmpList} shifts={listOfShifts} /> </div> {/* Queue Tab Ends */} </div> @@ -211,13 +213,13 @@

    Source: metrics.js


    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:00:24 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time)
    diff --git a/out/Punctuality.js.html b/docs/views_metrics_punctuality_Punctuality.js.html similarity index 56% rename from out/Punctuality.js.html rename to docs/views_metrics_punctuality_Punctuality.js.html index cdc4a5a..8461472 100644 --- a/out/Punctuality.js.html +++ b/docs/views_metrics_punctuality_Punctuality.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: Punctuality.js + JSDoc: Source: views/metrics/punctuality/Punctuality.js @@ -17,7 +17,7 @@
    -

    Source: Punctuality.js

    +

    Source: views/metrics/punctuality/Punctuality.js

    @@ -28,60 +28,71 @@

    Source: Punctuality.js

    import React from "react";
     import { PieChart } from '../charts';
    -import { ClockInsData, ClockOutsData } from "./PunctualityData";
    -
    -// Colors
    -const purple = "#5c00b8";
    -const lightTeal = "#00ebeb";
    -const darkTeal = "#009e9e";
    -const green = "#06ff05";
    +import { ClockInsDataGenerator, ClockOutsDataGenerator } from "./PunctualityData";
     
     /**
      * @function
    - * @description Creates a page with 2 tables and 2 graphs of all the clock-in and clock-out trends.
    + * @description Creates a page with 2 tables and 2 graphs of the clock-in and clock-out trends.
      * @since 09.29.22 by Paola Sanchez
      * @author Paola Sanchez
      * @requires PieChart
    - * @requires ClockInsData
    - * @requires ClockOutsData
    + * @requires ClockInsDataGenerator
    + * @requires ClockOutsDataGenerator
    + * @param {object} props - Contains an array of all the shifts.
      */
    -export const Punctuality = () => {
    +export const Punctuality = (props) => {
    +
    +    // Setting up main data sources
    +    let ClockInsData = ClockInsDataGenerator(props.shifts)
    +    let ClockOutsData = ClockOutsDataGenerator(props.shifts)
     
         // Data for pie charts -------------------------------------------------------------------------------------
     
    -    // Clock-Ins
    +    // Colors
    +    const purple = "#5c00b8";
    +    const lightTeal = "#00ebeb";
    +    const darkTeal = "#009e9e";
    +    const green = "#06ff05";
    +    const lightPink = "#eb00eb";
    +    const darkPink = "#b200b2";
    +
    +    // Clock-Ins ------------------------------------------------------------------------------------------------
     
         // Taking out the "Totals" from the chart view
    -    let dataCI = ClockInsData.filter((item) => { return item.description !== "Total Clock-Ins" }) // Taking out the "Totals" from the chart view
    +    let dataCI = ClockInsData.filter((item) => {
    +        return item.description !== "Total Clock-Ins";
    +    });
     
    -    // Preparing data to be passed to the chart component
    +    // Preparing the data to be passed to the chart component
         const clockInsData = {
             labels: dataCI.map((data) => data.description),
    -        datasets: [{
    -            label: "Clock-Ins",
    -            data: dataCI.map((data) => data.qty),
    -            backgroundColor: [
    -                purple, lightTeal
    -            ],
    -        }]
    -    }
    +        datasets: [
    +            {
    +                label: "Clock-Ins",
    +                data: dataCI.map((data) => data.qty),
    +                backgroundColor: [green, lightTeal, darkPink]
    +            }
    +        ]
    +    };
     
    -    // Clock-Outs
    +    // Clock-Outs ------------------------------------------------------------------------------------------------
     
         // Taking out the "Totals" from the chart view
    -    let dataCO = ClockOutsData.filter((item) => { return item.description !== "Total Clock-Outs" })
    +    let dataCO = ClockOutsData.filter((item) => {
    +        return item.description !== "Total Clock-Outs";
    +    });
     
    -    // Preparing data to be passed to the chart component
    +    // Preparing the data to be passed to the chart component
         const clockOutsData = {
             labels: dataCO.map((data) => data.description),
    -        datasets: [{
    -            label: "Clock-Outs",
    -            data: dataCO.map((data) => data.qty),
    -            backgroundColor: [
    -                green, darkTeal, purple
    -            ],
    -        }]
    -    }
    +        datasets: [
    +            {
    +                label: "Clock-Outs",
    +                data: dataCO.map((data) => data.qty),
    +                backgroundColor: [purple, darkTeal, lightTeal, lightPink]
    +            }
    +        ]
    +    };
     
         // Return ----------------------------------------------------------------------------------------------------
     
    @@ -105,7 +116,7 @@ 

    Source: Punctuality.js

    </thead> <tbody> - {/* Mapping the data to diplay it as table rows */} + {/* Mapping the data to diplay it as table rows */} {ClockInsData.map((item, i) => { return item.description === "Total Clock-Ins" ? ( <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}> @@ -171,7 +182,7 @@

    Source: Punctuality.js

    <div className="col"> <div className="row mb-5"> {/* Clock-Ins Chart Starts */} - <div className="col text-center"> + <div className="col text-center mb-5"> <h2 className="mb-3">Clock-Ins Chart</h2> <div style={{ height: '13.905rem' }}> @@ -207,13 +218,13 @@

    Source: Punctuality.js


    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:37:24 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_punctuality_PunctualityData.js.html b/docs/views_metrics_punctuality_PunctualityData.js.html new file mode 100644 index 0000000..23b9276 --- /dev/null +++ b/docs/views_metrics_punctuality_PunctualityData.js.html @@ -0,0 +1,282 @@ + + + + + JSDoc: Source: views/metrics/punctuality/PunctualityData.js + + + + + + + + + + +
    + +

    Source: views/metrics/punctuality/PunctualityData.js

    + + + + + + +
    +
    +
    import moment from "moment";
    +
    +// Clock-Ins Data ------------------------------------------------------------------
    +
    +/**
    + * @function
    + * @description Generates array of objects with clock-in trends for Punctuality.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @param {object} props - Contains an array of all the shifts.
    + */
    +export const ClockInsDataGenerator = (props) => {
    +
    +    // Assigning props to variables
    +    let shifts = props
    +
    +    // Array for the clock-ins
    +    let clockIns = [];
    +
    +    // Sorting the shifts
    +    shifts.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            // Keeping all clockins array with at
    +            // least one object inside
    +            if (shift.clockin.length > 0) {
    +                // Formatting each object to keep
    +                // both the scheduled clock-in and 
    +                // the actual "registered" clock-in.
    +                clockIns.push({
    +                    starting_at: shift.starting_at,
    +                    started_at: clockIn.started_at
    +                });
    +            }
    +        });
    +    });
    +
    +    // Setting up counters
    +    let earlyClockins = 0;
    +    let lateClockins = 0;
    +    let onTimeClockins = 0;
    +
    +    // Increasing counters based on clock-in times
    +    clockIns.forEach((shift) => {
    +        let start1 = moment(shift.starting_at);
    +        let start2 = moment(shift.started_at);
    +
    +        let startDiff = moment.duration(start2.diff(start1)).asMinutes();
    +
    +        if (startDiff >= 15) {
    +            lateClockins++;
    +        } else if (startDiff <= -30) {
    +            earlyClockins++;
    +        } else {
    +            onTimeClockins++;
    +        }
    +    });
    +
    +    // Creating clock-in objects
    +    let earlyClockinsObj = {
    +        description: "Early Clock-Ins",
    +        qty: earlyClockins
    +    };
    +    let lateClockinsObj = {
    +        description: "Late Clock-Ins",
    +        qty: lateClockins
    +    };
    +    let onTimeClockinsObj = {
    +        description: "On Time Clock-Ins",
    +        qty: onTimeClockins
    +    };
    +
    +    // Setting up base array for all objects
    +    let cleanedClockIns = [];
    +
    +    // Pushing objects to base array
    +    cleanedClockIns.push(earlyClockinsObj);
    +    cleanedClockIns.push(lateClockinsObj);
    +    cleanedClockIns.push(onTimeClockinsObj);
    +
    +    // Setting up totals
    +    let totalClockIns = clockIns.length;
    +
    +    // Generating percentages as new properties
    +    let pctClockIns = cleanedClockIns.map(({ description, qty }) => ({
    +        description,
    +        qty,
    +        pct: ((qty * 100) / totalClockIns).toFixed(0)
    +    }));
    +
    +    // Setting up object for totals
    +    let totalClockInsObj = {
    +        description: "Total Clock-Ins",
    +        qty: totalClockIns,
    +        pct: "100"
    +    };
    +
    +    // Adding totals to the array with percentages
    +    pctClockIns.push(totalClockInsObj);
    +
    +    // Addind IDs to each object
    +    pctClockIns.forEach((item, i) => {
    +        item.id = i + 1;
    +    });
    +
    +    // Returning clock-ins array
    +    return pctClockIns;
    +};
    +
    +// Clock-Outs Data -----------------------------------------------------------------
    +
    +/**
    + * @function
    + * @description Generates array of objects with clock-out trends for Punctuality.js.
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @param {object} props - Contains an array of all the shifts.
    + */
    +export const ClockOutsDataGenerator = (props) => {
    +
    +    // Assigning props to variables
    +    let shifts = props
    +
    +    // Array for the clock-outs
    +    let clockOuts = [];
    +
    +    // Sorting the shifts
    +    shifts.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            // Keeping all clockins array with at
    +            // least one object inside
    +            if (shift.clockin.length > 0) {
    +                // Formatting each object to keep
    +                // both the scheduled clock-out, the 
    +                // actual "registered" clock-out, and
    +                // whether it closed automatically or not.
    +                clockOuts.push({
    +                    ending_at: shift.ending_at,
    +                    ended_at: clockIn.ended_at,
    +                    automatically_closed: clockIn.automatically_closed
    +                });
    +            }
    +        });
    +    });
    +
    +    // Setting up counters
    +    let earlyClockouts = 0;
    +    let lateClockouts = 0;
    +    let onTimeClockouts = 0;
    +    let forgotClockOut = 0;
    +
    +    // Increasing counters based on clock-out times
    +    clockOuts.forEach((shift) => {
    +        let end1 = moment(shift.ending_at);
    +        let end2 = moment(shift.ended_at);
    +
    +        let endDiff = moment.duration(end2.diff(end1)).asMinutes();
    +
    +        if (endDiff >= 30) {
    +            lateClockouts++;
    +        } else if (endDiff <= -30) {
    +            earlyClockouts++;
    +        } else {
    +            onTimeClockouts++;
    +        }
    +    });
    +
    +    // Increasing the "forgotClockOut" counter only
    +    clockOuts.forEach((shift) => {
    +        // Note: When a shif get automatically closed, it means
    +        // that the worker forgot to clock-out.
    +        if (shift.automatically_closed === true) {
    +            forgotClockOut++;
    +        }
    +    });
    +
    +    // Creating clock-out objects
    +    let earlyClockoutsObj = {
    +        description: "Early Clock-Outs",
    +        qty: earlyClockouts
    +    };
    +    let lateClockoutsObj = {
    +        description: "Late Clock-Outs",
    +        qty: lateClockouts
    +    };
    +    let onTimeClockoutsObj = {
    +        description: "On Time Clock-Outs",
    +        qty: onTimeClockouts
    +    };
    +    let forgotClockOutObj = {
    +        description: "Forgotten Clock-Outs",
    +        qty: forgotClockOut
    +    };
    +
    +    // Setting up base array for all objects
    +    let cleanedClockOuts = [];
    +
    +    // Pushing objects to base array
    +    cleanedClockOuts.push(earlyClockoutsObj);
    +    cleanedClockOuts.push(lateClockoutsObj);
    +    cleanedClockOuts.push(onTimeClockoutsObj);
    +    cleanedClockOuts.push(forgotClockOutObj);
    +
    +    // Setting up totals
    +    let totalClockOuts = clockOuts.length;
    +
    +    // Generating percentages as new properties
    +    let pctClockOuts = cleanedClockOuts.map(({ description, qty }) => ({
    +        description,
    +        qty,
    +        pct: ((qty * 100) / totalClockOuts).toFixed(0)
    +    }));
    +
    +    // Setting up object for totals
    +    let totalClockOutsObj = {
    +        description: "Total Clock-Outs",
    +        qty: totalClockOuts,
    +        pct: "100"
    +    };
    +
    +    // Adding totals to the array with percentages
    +    pctClockOuts.push(totalClockOutsObj);
    +
    +    // Addind IDs to each object
    +    pctClockOuts.forEach((item, i) => {
    +        item.id = i + 1;
    +    });
    +
    +    // Returning clock-outs array
    +    return pctClockOuts;
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/out/Queue.js.html b/docs/views_metrics_queue_Queue.js.html similarity index 53% rename from out/Queue.js.html rename to docs/views_metrics_queue_Queue.js.html index 18ca1d8..03e45fd 100644 --- a/out/Queue.js.html +++ b/docs/views_metrics_queue_Queue.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: Queue.js + JSDoc: Source: views/metrics/queue/Queue.js @@ -17,7 +17,7 @@
    -

    Source: Queue.js

    +

    Source: views/metrics/queue/Queue.js

    @@ -31,10 +31,8 @@

    Source: Queue.js

    import DatePicker from "react-datepicker"; import moment from "moment"; -import { WorkerStats } from "./WorkerStats"; +import { QueueData } from "./QueueData"; import { Button } from "../../../components/index"; -import { DummyDataShifts } from "./DummyDataShifts"; -import { DummyDataWorkers } from "./DummyDataWorkers"; /** * @function @@ -43,16 +41,18 @@

    Source: Queue.js

    * @author Paola Sanchez * @requires moment * @requires DatePicker - * @requires WorkerStats + * @requires QueueData * @requires Button - * @requires DummyDataShifts - * @requires DummyDataWorkers - * @param {object} props - Contains an array of all shifts, and an array of all workers + * @param {object} props - Contains an array of all shifts, and an array of all workers. */ export const Queue = (props) => { // Setting up my variables --------------------------------------------------------------------------------- + // Setting up main data source + let allShifts = props.shifts; + let workers = props.workers; + // Date selected through the DatePicker const [selectedDate, setSelectedDate] = useState(new Date()); @@ -60,23 +60,20 @@

    Source: Queue.js

    const [start, setStart] = useState( moment().startOf("isoWeek").format("YYYY-MM-DD") ); - + // Sunday of X week (default: current week) const [end, setEnd] = useState( moment().endOf("isoWeek").format("YYYY-MM-DD") ); - //const allShifts = props.shifts; - const allShifts = DummyDataShifts; - - //const workers = props.workers; - const workers = DummyDataWorkers - // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- const filterShifts = () => { + + // Array for filtered shifts let filteredShifts = []; + // Keeping shifts that exist within the selected dates allShifts.forEach((shift) => { let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); @@ -91,23 +88,28 @@

    Source: Queue.js

    } }); + // Returning filtered shifts return filteredShifts; }; // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- useEffect(() => { + + // Setting up the new Monday let formattedStart = moment(selectedDate) .startOf("isoWeek") .format("YYYY-MM-DD"); setStart(formattedStart); + // Setting up the new Sunday let formattedEnd = moment(selectedDate) .endOf("isoWeek") .format("YYYY-MM-DD"); setEnd(formattedEnd); + }, [selectedDate]); // Return ---------------------------------------------------------------------------------------------------- @@ -129,6 +131,7 @@

    Source: Queue.js

    <h3 className="m-0">Select a day of the desired week: </h3> </div> + {/* Calendar/DatePicker */} <div> <DatePicker selected={selectedDate} @@ -160,7 +163,7 @@

    Source: Queue.js

    <div className="col rounded mt-2 m-0 p-0 text-center mx-auto"> {workers?.map((singleWorker, i) => { return ( - <WorkerStats + <QueueData key={i} worker={singleWorker} shifts={filterShifts()} @@ -181,13 +184,13 @@

    Source: Queue.js


    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:58:02 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_queue_QueueData.js.html b/docs/views_metrics_queue_QueueData.js.html new file mode 100644 index 0000000..a61510b --- /dev/null +++ b/docs/views_metrics_queue_QueueData.js.html @@ -0,0 +1,218 @@ + + + + + JSDoc: Source: views/metrics/queue/QueueData.js + + + + + + + + + + +
    + +

    Source: views/metrics/queue/QueueData.js

    + + + + + + +
    +
    +
    import React from "react";
    +import moment from "moment";
    +import Avatar from "../../../components/avatar/Avatar";
    +import { Button, Theme } from "../../../components/index";
    +
    +// This is needed to render the button "Invite to Shift"
    +const allowLevels = window.location.search != "";
    +
    +/**
    + * @function
    + * @description Creates a table of all employees with their worked/scheduled hours for Queue.js
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires moment
    + * @requires Avatar
    + * @requires Button
    + * @requires Theme
    + * @param {object} props - Contains an array of all shifts, and an object with information of a single worker, previously mapped in Queue.js
    + */
    +export const QueueData = (props) => {
    +
    +    //Assigning props to variables ----------------------------
    +
    +    // This is a single worker brought from the mapping
    +    // of all workers back in Queue.js
    +    const worker = props.worker;
    +
    +    // These are all the shifts, with different workers
    +    const shifts = props.shifts;
    +
    +    // Worker Shifts ------------------------------------------
    +
    +    // Array to hold shifts from the single worker
    +    const workerShifts = [];
    +
    +    // Keeping the shifts whose worker id matches
    +    // the id of the single worker
    +    shifts.forEach((shift) => {
    +        shift.employees.forEach((employee) => {
    +            if (employee === worker.id) {
    +                workerShifts.push(shift);
    +            }
    +        });
    +    });
    +
    +    // Worker Clock-ins ---------------------------------------
    +
    +    // Array to hold clock-ins from the single worker
    +    let workerClockIns = [];
    +
    +    // Keeping the clock-ins whose worker id matches
    +    // the id of the single worker
    +    workerShifts.forEach((shift) => {
    +        shift.clockin.forEach((clockIn) => {
    +            if (clockIn.employee === worker.id) {
    +                workerClockIns.push(clockIn);
    +            }
    +        });
    +    });
    +
    +    // Scheduled Hours ---------------------------------------
    +
    +    // Array to hold scheduled hours from the single worker
    +    let scheduledHours = [];
    +
    +    // Calculating the scheduled hours of each shift and
    +    // passing that data as an object to "scheduledHours"
    +    workerShifts.forEach((shift) => {
    +        let start = moment(shift.starting_at);
    +        let end = moment(shift.ending_at);
    +
    +        let diff = moment.duration(end.diff(start)).asHours();
    +
    +        scheduledHours.push({
    +            id: shift.id,
    +            scheduled_hours: diff
    +        });
    +    });
    +
    +    // Adding all the scheduled hours to form a total
    +    let totalScheduledHours = scheduledHours.reduce((accumulator, shift) => {
    +        return accumulator + shift.scheduled_hours;
    +    }, 0);
    +
    +    // Formatting the total to 2 decimal places
    +    let totalScheduledHoursF = totalScheduledHours.toFixed(2)
    +
    +    // Worked Hours ----------------------------------------
    +
    +    // Array to hold worked hours from the single worker
    +    let workedHours = [];
    +
    +    // Calculating the worked hours of each shift and
    +    // passing that data as an object to "workedHours"
    +    workerClockIns.forEach((shift) => {
    +        let start = moment(shift.started_at);
    +        let end = moment(shift.ended_at);
    +
    +        let diff = moment.duration(end.diff(start)).asHours();
    +
    +        workedHours.push({
    +            id: shift.id,
    +            worked_hours: diff
    +        });
    +    });
    +
    +    // Adding all the worked hours to form a total
    +    let totalWorkedHours = workedHours.reduce((accumulator, shift) => {
    +        return accumulator + shift.worked_hours;
    +    }, 0);
    +
    +    // Formatting the total to 2 decimal places
    +    let totalWorkedHoursF = totalWorkedHours.toFixed(2)
    +
    +    // Return ------------------------------------------------------------------------------------------------------
    +
    +    return (
    +        <>
    +            <Theme.Consumer>
    +                {({ bar }) => (
    +                    <div className="row d-flex border d-inline-flex justify-content-between py-4 w-100">
    +                        {/* Employee Image/Name/Rating Starts */}
    +                        <div className="col p-0 d-flex justify-content-center">
    +                            <div className="my-auto mr-2">
    +                                <Avatar url={worker.user.profile.picture} />
    +                            </div>
    +                            <div className="ms-2 text-start my-auto d-flex flex-column">
    +                                <h5 className="m-0 p-0" align="left">{`${worker.user.first_name} ${worker.user.last_name}`}</h5>
    +                                <h5 className="m-0 p-0" align="left">{worker.rating == null ? "No rating available" : worker.rating > 1 ? `Rating: ${worker.rating} stars` : `Rating: ${worker.rating} star`}</h5>
    +                            </div>
    +                        </div>
    +                        {/* Employee Image/Name/Rating Ends */}
    +
    +                        {/* Scheduled Hours Starts */}
    +                        <div className="col p-0 my-auto d-flex justify-content-center">
    +                            <h3 className="m-0 p-0">{`Scheduled Hours: ${totalScheduledHoursF}`}</h3>
    +                        </div>
    +                        {/* Scheduled Hours Ends */}
    +
    +                        {/* Worked Hours Starts */}
    +                        <div className="col p-0 my-auto d-flex justify-content-center">
    +
    +                            <h3 className="m-0 p-0">{`Worked Hours: ${totalWorkedHoursF}`}</h3>
    +                        </div>
    +                        {/* Worked Hours Ends */}
    +
    +                        {/* Invite Button Starts */}
    +                        <div className="col p-0 my-auto d-flex justify-content-center">
    +                            <Button
    +                                className="btn btn-dark bg-dark"
    +                                onClick={() =>
    +                                    bar.show({
    +                                        slug: "invite_talent_to_shift",
    +                                        data: worker,
    +                                        allowLevels,
    +                                    })
    +                                }
    +                            >
    +                                <h5 className="m-0">Invite to Shift</h5>
    +                            </Button>
    +                        </div>
    +                        {/* Invite Button Ends */}
    +                    </div>
    +                )}
    +            </Theme.Consumer>
    +        </>
    +    );
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_metrics_ratings_Ratings.js.html b/docs/views_metrics_ratings_Ratings.js.html new file mode 100644 index 0000000..e5d4fa7 --- /dev/null +++ b/docs/views_metrics_ratings_Ratings.js.html @@ -0,0 +1,261 @@ + + + + + JSDoc: Source: views/metrics/ratings/Ratings.js + + + + + + + + + + +
    + +

    Source: views/metrics/ratings/Ratings.js

    + + + + + + +
    +
    +
    import React, { useEffect, useState } from "react"
    +import { PieChart } from "../charts"
    +
    +/**
    + * @function
    + * @description Creates a pie chart and a table reflecting how many job seekers are in each category of star ratings (1 to 5 stars.)
    + * @since 09.29.22 by Paola Sanchez
    + * @author Paola Sanchez
    + * @requires PieChart
    + * @param {object} props - Contains an array of all shifts, and an array of all workers.
    + */
    +export const Ratings = (props) => {
    +
    +    // Use state to hold list of workers
    +    const [workersList, setWorkersList] = useState([])
    +
    +    // Receiving the props that contain the list of workers
    +    const handleProps = async () => {
    +
    +        // Catching the props when they arrive
    +        let workersObj = await props
    +
    +        // Checking length of list before saving it
    +        if (workersObj.workers.length > 0) {
    +            // Saving list of workers
    +            setWorkersList(workersObj.workers)
    +        } else {
    +            // Handling error with a message
    +            console.log("Waiting for props to arrive")
    +        }
    +    }
    +
    +    // Triggering handleProps when props change/arrive
    +    useEffect(() => {
    +        handleProps()
    +    }, [props])
    +
    +    // Rendering based on length of workersList
    +    if (workersList.length > 0) {
    +
    +        // Preparing the list for the chart data ---------------------------------------
    +
    +        // Array to hold list of ratings
    +        let ratingsList = []
    +
    +        // Gathering ratings of each worker
    +        workersList.forEach((eachWorker) => {
    +            ratingsList.push(eachWorker.rating)
    +        })
    +
    +        // Function to make an array of rating quantities
    +        const findQuantities = (passedArray) => {
    +
    +            // Array to hold rating results
    +            const results = [];
    +
    +            // Counting how many times each star rating appears
    +            passedArray?.forEach((item) => {
    +
    +                // Generating indexes
    +                const index = results.findIndex((obj) => {
    +                    return obj["rating"] === item;
    +                });
    +
    +                // Using the indexes to count rating instances
    +                if (index === -1) {
    +                    results.push({
    +                        rating: item,
    +                        qty: 1
    +                    });
    +
    +                } else {
    +                    results[index]["qty"]++;
    +                }
    +            });
    +
    +            // Returning array
    +            return results;
    +        };
    +
    +        // Generating array of rating quantities
    +        let ratingsQty = findQuantities(ratingsList);
    +
    +        // Calculating total of all the quantities
    +        let total = ratingsQty.reduce((s, { qty }) => s + qty, 0);
    +
    +        // Generating and adding percentages as new properties
    +        let ratingsPct = ratingsQty.map(({ rating, qty }) => ({
    +            rating,
    +            qty,
    +            pct: ((qty * 100) / total).toFixed(0)
    +        }));
    +
    +        // Organizing objects by numerical order of the "rating" properties
    +        let ratingsFinal = ratingsPct.sort((a, b) => (a.rating - b.rating))
    +
    +        // Moving the first object ("Unavailable Rating") to the last position of the array
    +        ratingsFinal.push(ratingsFinal.shift());
    +
    +        // Generating an object with the totals
    +        let totalsObj = { rating: "Total Employees", qty: total, pct: "100" }
    +
    +        // Adding object with totals to the array
    +        ratingsFinal.push(totalsObj)
    +
    +        // Adding id's to every object in the array
    +        ratingsFinal.forEach((item, i) => {
    +            item.id = i + 1;
    +        });
    +
    +        // Preparing the chart data ----------------------------------------------------
    +
    +        // Colors
    +        const purple = "#5c00b8";
    +        const lightTeal = "#00ebeb";
    +        const darkTeal = "#009e9e";
    +        const green = "#06ff05";
    +        const lightPink = "#eb00eb";
    +        const darkPink = "#b200b2";
    +
    +        // Taking out the "Totals" from the pie chart view
    +        let pieData = ratingsFinal.filter((item) => { return item.rating !== "Total Employees" })
    +
    +        // Preparing data to be passed to the chart component
    +        const ratingsData = {
    +            labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }),
    +            datasets: [{
    +                label: "Employee Ratings",
    +                data: pieData.map((data) => data.qty),
    +                backgroundColor: [
    +                    green, darkTeal, lightPink,
    +                    purple, lightTeal, darkPink
    +                ],
    +            }]
    +        }
    +
    +        // Return ----------------------------------------------------------------------
    +
    +        return (
    +            <div className="row d-flex d-inline-flex justify-content-between w-100">
    +                {/* Left Column Starts */}
    +                <div className="col">
    +                    <div className="row d-flex flex-column justify-content-between">
    +                        {/* Ratings Table Starts */}
    +                        <div className="col text-center">
    +                            <h2 className="mb-4">Employee Ratings Table</h2>
    +
    +                            <table className="table table-bordered text-center">
    +                                <thead className="thead-dark">
    +                                    {/* Table columns */}
    +                                    <tr>
    +                                        <th scope="col"><h3 className="m-0">Star Rating</h3></th>
    +                                        <th scope="col"><h3 className="m-0">Quantity</h3></th>
    +                                        <th scope="col"><h3 className="m-0">Percentages</h3></th>
    +                                    </tr>
    +                                </thead>
    +
    +                                <tbody>
    +                                    {/* Mapping the data to diplay it as table rows */}
    +                                    {ratingsFinal.map((item, i) => {
    +                                        return item.rating === null ? (
    +                                            <tr key={i}>
    +                                                <th scope="row"><h3 className="m-0">Unavailable Rating</h3></th>
    +                                                <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                            </tr>
    +                                        ) : item.rating === "Total Employees" ? (
    +                                            <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    +                                                <th scope="row"><h3 className="m-0">{item.rating}</h3></th>
    +                                                <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                            </tr>
    +                                        ) : (
    +                                            <tr key={i}>
    +                                                <th scope="row"><h3 className="m-0">{`${item.rating} Star Employees`}</h3></th>
    +                                                <td><h3 className="m-0">{item.qty}</h3></td>
    +                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    +                                            </tr>
    +                                        )
    +                                    })}
    +                                </tbody>
    +                            </table>
    +                        </div>
    +                        {/* Ratings Table Ends */}
    +                    </div>
    +                </div>
    +                {/* Left Column Ends */}
    +
    +                {/* Right Column Starts */}
    +                <div className="col">
    +                    <div className="row">
    +                        {/* Ratings Chart Starts */}
    +                        <div className="col text-center">
    +                            <h2 className="mb-3">Employee Ratings Chart</h2>
    +
    +                            <div style={{ height: '26.05rem' }}>
    +                                <PieChart pieData={ratingsData} />
    +                            </div>
    +                        </div>
    +                        {/* Ratings Chart Ends */}
    +                    </div>
    +                </div>
    +                {/* Right Column Ends */}
    +            </div>
    +        )
    +    } else {
    +        return (
    +            <h1>Loading</h1>
    +        )
    +    }
    +}
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_payments.js.html b/docs/views_payments.js.html new file mode 100644 index 0000000..e36708f --- /dev/null +++ b/docs/views_payments.js.html @@ -0,0 +1,235 @@ + + + + + JSDoc: Source: views/payments.js + + + + + + + + + + +
    + +

    Source: views/payments.js

    + + + + + + +
    +
    +
    import React from "react";
    +import PropTypes from 'prop-types';
    +import { Button } from '../components/index';
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import { Notify } from 'bc-react-notifier';
    +import { makeEmployeePayment } from "../actions";
    +import CustomModal from "../components/custom-modal/CustomModal";
    +
    +export const Payment = (data = {}) => {
    +
    +    const _defaults = {
    +        pay: null,
    +        total: null,
    +        serialize: function () {
    +
    +            const newDeduction = {
    +            };
    +
    +            return Object.assign(this, newDeduction);
    +        }
    +    };
    +
    +    let _payment = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +            // if (validator.isEmpty(_payment.name)) throw new ValidationError('The deduction name cannot be empty');
    +            // if (!_payment.value) throw new ValidationError('Deduction cannot be empty');
    +            // if (validator.isEmpty(_payment.description)) throw new ValidationError('The deduction description cannot be empty');
    +            // if (!_payment.type) throw new ValidationError('The deduction type cannot be empty');
    +            return _payment;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        }
    +    };
    +};
    +
    +/**
    + * Make Payment
    + */
    +export class MakePayment extends Flux.DashView {
    +    
    +    render() {
    +        const { 
    +            onSave, 
    +            onCancel, 
    +            onChange, 
    +            catalog, 
    +            formData,
    +            error
    +         } = this.props;
    +         const { pay, paymentInfo, bar } = formData;
    +         const employerBankAccounts = paymentInfo && paymentInfo.employer ? paymentInfo.employer.bank_accounts : null;
    +         const employeeBankAccounts = pay && pay.employee.bank_accounts && pay.employee.bank_accounts.length > 0;
    +        console.log('MakePayment pay: ', pay);
    +        console.log('MakePayment error: ', error);
    +        console.log('MakePayment paymentInfo: ', paymentInfo);
    +
    +        console.log("formdata", pay.deduction_list);
    +        return (
    +        <>
    +            {paymentInfo && pay
    +        ? <form>
    +            <div className="row mt-3">
    +                <div className="col-12">
    +                    <h4>{`Payment to `}{` ${pay.employee.last_name}, ${pay.employee.first_name}`}</h4>
    +                </div>
    +            </div>
    +            <div className="row">
    +                <div className="col-12">
    +                    <label>Employee:</label>{` ${pay.employee.last_name}, ${pay.employee.first_name}`}
    +                    {/* <p className="m-0 p-0"><span className="badge">{formData.total.status.toLowerCase()}</span></p> */}
    +                </div>
    +                <div className="col-12">
    +                    <label>Regular hours:</label>{` ${Math.round((Number(pay.regular_hours) + Number(pay.over_time)) * 100) / 100 > 40 ? 40 : Math.round((Number(pay.regular_hours) + Number(pay.over_time)) * 100)/100}`}
    +                </div>
    +                <div className="col-12">
    +                    <label>Over time:</label>{` ${Math.round((Number(pay.regular_hours) + Number(pay.over_time)) * 100) / 100 > 40 ? Math.round((Number(pay.regular_hours) + Number(pay.over_time)- 40) * 100 )  / 100  : "-" }`}
    +                </div>
    +                <div className="col-12">
    +                    <label>Earnings:</label>{` ${pay.earnings}`}
    +                </div>
    +                <div className="col-12">
    +                    <label>Taxes:</label>{` ${pay.deductions.toFixed(2)}`}
    +                </div>
    +                <div className="col-12">
    +                    <label>Amount:</label>{` ${pay.amount.toFixed(2)}`}
    +                </div>
    +            </div>
    +            {!pay.paid
    +                    ? <div className="row">
    +                        <div className="col-12">
    +                            <label>Payment methods</label>
    +                        </div>
    +                        <div className="col-12 payment-cell">
    +                            <Button
    +                                style={{ width: '200px' }}
    +                                color="success"
    +                                size="small"
    +                                onClick={() => {
    +                                    const noti = Notify.add("info", ({ onConfirm }) => <CustomModal onConfirm={onConfirm} title={"Are you sure to pay ?"} />, async (answer) => {
    +                                        if(answer){
    +                                            try{
    +                                                await makeEmployeePayment(
    +                                                    pay.id, 
    +                                                    "CHECK", 
    +                                                    "", 
    +                                                    "",
    +                                                    pay.deduction_list,
    +                                                    pay.deductions
    +                                                    );
    +                                                noti.remove();
    +                                                bar.close();
    +                                            }catch(error){
    +                                                Notify.error(error.message || error);
    +                                            }
    +                                        } else{
    +                                            noti.remove();
    +                                        }
    +                                    });
    +                                }}>
    +                                Check payment
    +                            </Button>
    +                        </div>
    +                        {employeeBankAccounts
    +                            ? employerBankAccounts && employerBankAccounts.length > 0
    +                                ? employerBankAccounts.map((bankaccount, i) =>
    +                                    <div className="col-12 payment-cell" key={i}>
    +                                        <Button
    +                                            style={{ width: '200px' }}
    +                                            color="success"
    +                                            size="small"
    +                                            onClick={() => {
    +                                                const noti = Notify.add("info", ({ onConfirm }) => <CustomModal onConfirm={onConfirm} title={"Are you sure to pay ?"} />, async (answer) => {
    +                                                    if(answer){
    +                                                        try{
    +                                                            await makeEmployeePayment(
    +                                                                pay.id, 
    +                                                                "ELECTRONIC TRANSFERENCE",
    +                                                                bankaccount.id, 
    +                                                                pay.employee.bank_accounts[0].id,
    +                                                                pay.deduction_list,
    +                                                                pay.deductions
    +                                                                );
    +                                                            noti.remove();
    +                                                            bar.close();
    +                                                        }catch(error){
    +                                                            Notify.error(error.message || error);
    +                                                        }
    +                                                    } else{
    +                                                        noti.remove();
    +                                                    }
    +                                                });
    +                                            }}
    +                                            >
    +                                            {`${bankaccount.institution_name} ${bankaccount.name}`}
    +                                        </Button>
    +                                    </div>
    +                                )
    +                            : <div className="col-12"><label>Employer doesn{`'`}t have any bank accounts</label></div>
    +                        : <div className="col-12"><label>Employee doesn{`'`}t have any bank accounts</label></div>}                   
    +                    </div>
    +        : <div className="row">
    +            <div className="col-12">
    +                <label>Status:</label>{` Paid`}
    +            </div>
    +        </div>
    +        }
    +        </form>
    +    : null}
    +    </>
    +        );
    +    }
    +}
    +
    +MakePayment.propTypes = {
    +    error: PropTypes.string,
    +    action: PropTypes.string,
    +    bar: PropTypes.object,
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    onChange: PropTypes.func.isRequired,
    +    formData: PropTypes.object,
    +    catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_payroll.js.html b/docs/views_payroll.js.html new file mode 100644 index 0000000..6a609fe --- /dev/null +++ b/docs/views_payroll.js.html @@ -0,0 +1,4888 @@ + + + + + JSDoc: Source: views/payroll.js + + + + + + + + + + +
    + +

    Source: views/payroll.js

    + + + + + + +
    +
    +
    import React, { useState, useEffect, useContext } from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from "prop-types";
    +import {
    +  store,
    +  search,
    +  update,
    +  fetchSingle,
    +  searchMe,
    +  processPendingPayrollPeriods,
    +  updateProfileMe,
    +  updatePayments,
    +  createPayment,
    +  fetchAllMe,
    +  fetchTemporal,
    +  remove,
    +  create,
    +  fetchPeyrollPeriodPayments,
    +} from "../actions.js";
    +import { GET } from "../utils/api_wrapper";
    +import { Session } from "bc-react-session";
    +
    +import { PDFDocument, rgb } from "pdf-lib";
    +import { saveAs } from "file-saver";
    +import fw4 from "../../img/fw4.pdf";
    +import i9form from "../../img/i92.pdf";
    +import loadingURL from "../../img/loading2.gif";
    +
    +import DateTime from "react-datetime";
    +import moment from "moment";
    +import {
    +  DATETIME_FORMAT,
    +  TIME_FORMAT,
    +  NOW,
    +  TODAY,
    +  haversineDistance,
    +} from "../components/utils.js";
    +import Select from "react-select";
    +import { hasTutorial } from "../utils/tutorial";
    +
    +import { Notify } from "bc-react-notifier";
    +
    +import { Shift, EditOrAddShift } from "./shifts.js";
    +import { Employer } from "./profile.js";
    +import { ManageLocations, AddOrEditLocation, Location } from "./locations.js";
    +import {
    +  EmployeeExtendedCard,
    +  ShiftOption,
    +  ShiftCard,
    +  DeductionExtendedCard,
    +  Theme,
    +  Button,
    +  ShiftOptionSelected,
    +  GenericCard,
    +  SearchCatalogSelect,
    +  Avatar,
    +  Toggle,
    +  Wizard,
    +  StarRating,
    +  ListCard,
    +} from "../components/index";
    +import queryString, { parse } from "query-string";
    +
    +import TimePicker from "rc-time-picker";
    +import "rc-time-picker/assets/index.css";
    +
    +import Tooltip from "rc-tooltip";
    +import "rc-tooltip/assets/bootstrap_white.css";
    +
    +import GoogleMapReact from "google-map-react";
    +
    +import { PDFDownloadLink } from "@react-pdf/renderer";
    +import { Document, Page } from "react-pdf";
    +import TextareaAutosize from "react-textarea-autosize";
    +import { PayrollPeriodReport } from "./reports/index.js";
    +import { ConsoleView } from "react-device-detect";
    +
    +const ENTITIY_NAME = "payroll";
    +
    +//gets the querystring and creats a formData object to be used when opening the rightbar
    +export const getPayrollInitialFilters = (catalog) => {
    +  let query = queryString.parse(window.location.search);
    +  if (typeof query == "undefined")
    +    return {
    +      starting_at: TODAY(),
    +      ending_at: new Date().setDate(TODAY().getDate() - 7),
    +    };
    +  return {
    +    starting_at: query.starting_at,
    +    ending_at: query.ending_at,
    +  };
    +};
    +
    +export const Clockin = (data) => {
    +  const _defaults = {
    +    author: null,
    +    employee: null,
    +    shift: null,
    +    created_at: null,
    +    updated_at: null,
    +    started_at: TODAY(),
    +    ended_at: TODAY(),
    +    distance_in_miles: 0,
    +    distance_out_miles: 0,
    +    latitude: [],
    +    longitude: [],
    +    status: "PENDING",
    +    serialize: function () {
    +      const newObj = {
    +        shift:
    +          !this.shift || typeof this.shift.id === "undefined"
    +            ? this.shift
    +            : this.shift.id,
    +        employee:
    +          !this.employee || typeof this.employee.id === "undefined"
    +            ? this.employee
    +            : this.employee.id,
    +      };
    +
    +      return Object.assign(this, newObj);
    +    },
    +    unserialize: function () {
    +      const dataType = typeof this.started_at;
    +      //if its already serialized
    +      if (
    +        typeof this.shift == "object" &&
    +        ["number", "string"].indexOf(dataType) == -1
    +      )
    +        return this;
    +
    +      const newObject = {
    +        shift:
    +          typeof this.shift != "object"
    +            ? store.get("shift", this.shift)
    +            : Shift(this.shift).defaults().unserialize(),
    +        employee:
    +          typeof this.employee != "object"
    +            ? store.get("employees", this.employee)
    +            : this.employee,
    +        started_at:
    +          this.started_at && !moment.isMoment(this.started_at)
    +            ? moment(this.started_at)
    +            : this.started_at,
    +        ended_at:
    +          this.ended_at && !moment.isMoment(this.ended_at)
    +            ? moment(this.ended_at)
    +            : this.ended_at,
    +        latitude_in: parseFloat(this.latitude_in),
    +        longitude_in: parseFloat(this.longitude_in),
    +        latitude_out: parseFloat(this.latitude_out),
    +        longitude_out: parseFloat(this.longitude_out),
    +        distance_in_miles: parseFloat(this.distance_in_miles),
    +        distance_out_miles: parseFloat(this.distance_out_miles),
    +      };
    +
    +      return Object.assign(this, newObject);
    +    },
    +  };
    +
    +  let _checkin = Object.assign(_defaults, data);
    +  return {
    +    get: () => {
    +      return _checkin;
    +    },
    +    validate: () => {
    +      const start = _checkin.stared_at;
    +      const finish = _checkin.ended_at;
    +
    +      //if(SHIFT_POSSIBLE_STATUS.indexOf(_shift.status) == -1) throw new Error('Invalid status "'+_shift.status+'" for shift');
    +
    +      return _checkin;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      const _formCheckin = {
    +        id: _checkin.id.toString(),
    +      };
    +      return _formCheckin;
    +    },
    +  };
    +};
    +
    +export const PayrollPeriod = (data) => {
    +  const _defaults = {
    +    employer: null,
    +    id: null,
    +    length: 0,
    +    length_type: "DAYS",
    +    payments: [],
    +    starting_at: null,
    +    status: null,
    +    serialize: function () {
    +      const newObj = {
    +        employer:
    +          !this.employer || typeof this.employer.id === "undefined"
    +            ? this.employer
    +            : this.employer.id,
    +      };
    +
    +      return Object.assign(this, newObj);
    +    },
    +    unserialize: function () {
    +      const newObject = {
    +        //shift: (typeof this.shift != 'object') ? store.get('shift', this.shift) : Shift(this.shift).defaults().unserialize(),
    +      };
    +
    +      return Object.assign(this, newObject);
    +    },
    +  };
    +
    +  let _payment = Object.assign(_defaults, data);
    +  return {
    +    get: () => {
    +      return _payment;
    +    },
    +    validate: () => {
    +      const start = _payment.starting_at;
    +      const finish = _payment.ending_at;
    +
    +      //if(SHIFT_POSSIBLE_STATUS.indexOf(_shift.status) == -1) throw new Error('Invalid status "'+_shift.status+'" for shift');
    +
    +      return _payment;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      const _formCheckin = {
    +        id: _payment.id.toString(),
    +      };
    +      return _formCheckin;
    +    },
    +  };
    +};
    +
    +export const Payment = (data) => {
    +  const _defaults = {
    +    //employer: null,
    +    //id: null,
    +    serialize: function () {
    +      const newObj = {
    +        id: this.id,
    +        regular_hours: this.regular_hours,
    +        over_time: this.over_time,
    +        hourly_rate: this.hourly_rate,
    +        total_amount: this.total_amount,
    +        breaktime_minutes: 0,
    +        status: this.status,
    +        splited_payment: this.splited_payment,
    +        payroll_period:
    +          !this.employer || typeof this.employer.id === "undefined"
    +            ? this.employer
    +            : this.employer.id,
    +        employer:
    +          !this.employer || typeof this.employer.id === "undefined"
    +            ? this.employer
    +            : this.employer.id,
    +        employee:
    +          !this.employee || typeof this.employee.id === "undefined"
    +            ? this.employee
    +            : this.employee.id,
    +        shift:
    +          !this.shift || typeof this.shift.id === "undefined"
    +            ? this.shift
    +            : this.shift.id,
    +        clockin:
    +          !this.clockin || typeof this.clockin.id === "undefined"
    +            ? this.clockin
    +            : this.clockin.id,
    +      };
    +
    +      return Object.assign(this, newObj);
    +    },
    +    unserialize: function () {
    +      const newObject = {
    +        //shift: (typeof this.shift != 'object') ? store.get('shift', this.shift) : Shift(this.shift).defaults().unserialize(),
    +        created_at:
    +          this.created_at && !moment.isMoment(this.created_at)
    +            ? moment(this.created_at)
    +            : this.created_at,
    +        updated_at:
    +          this.updated_at && !moment.isMoment(this.updated_at)
    +            ? moment(this.updated_at)
    +            : this.updated_at,
    +      };
    +
    +      return Object.assign(this, newObject);
    +    },
    +  };
    +
    +  let _payment = Object.assign(_defaults, data);
    +  return {
    +    get: () => {
    +      return _payment;
    +    },
    +    validate: () => {
    +      //if(SHIFT_POSSIBLE_STATUS.indexOf(_shift.status) == -1) throw new Error('Invalid status "'+_shift.status+'" for shift');
    +      return _payment;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      const _form = {
    +        id: _payment.id.toString(),
    +      };
    +      return _form;
    +    },
    +  };
    +};
    +
    +export class PayrollSettings extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      employer: Employer().defaults(),
    +      deductions: [],
    +      runTutorial: hasTutorial(),
    +      steps: [
    +        {
    +          content: (
    +            <div>
    +              <h2>This is your payroll setting page</h2>
    +              <p>Lets start by updating your preferences </p>
    +            </div>
    +          ),
    +          placement: "center",
    +
    +          styles: {
    +            options: {
    +              zIndex: 10000,
    +            },
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          locale: { skip: "Skip tutorial" },
    +          target: "body",
    +        },
    +        {
    +          target: "#payroll_run",
    +          content:
    +            "Edit the company payroll period. When it will begin and when it ends",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#payroll_clockin",
    +          content:
    +            "Edit the amount of time you like to give your employees before/after to clokin. This option will help to prevent early clock ins.",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#payroll_automatic",
    +          content: "You can choose to enable automatic checkout",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +
    +        {
    +          target: "#button_save",
    +          content: "Save your progress",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +            buttonNext: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: false,
    +          disableCloseOnEsc: false,
    +          disableOverlayClose: false,
    +          disableScrollParentFix: false,
    +        },
    +      ],
    +    };
    +  }
    +
    +  setEmployer(newEmployer) {
    +    const employer = Object.assign(this.state.employer, newEmployer);
    +    this.setState({ employer });
    +  }
    +
    +  componentDidMount() {
    +    const deductions = store.getState("deduction");
    +    if (!deductions) {
    +      searchMe("deduction");
    +    } else {
    +      this.setState({ deductions });
    +    }
    +    fetchTemporal("employers/me", "current_employer");
    +    this.subscribe(store, "current_employer", (employer) => {
    +      this.setState({ employer });
    +    });
    +    this.subscribe(store, "deduction", (deductions) => {
    +      this.setState({ deductions });
    +    });
    +  }
    +  callback = (data) => {
    +    // if(data.action == 'next' && data.index == 0){
    +    //     this.props.history.push("/payroll");
    +
    +    // }
    +    if (data.status == "skipped") {
    +      const session = Session.get();
    +      updateProfileMe({ show_tutorial: false });
    +
    +      const profile = Object.assign(session.payload.user.profile, {
    +        show_tutorial: false,
    +      });
    +      const user = Object.assign(session.payload.user, { profile });
    +      Session.setPayload({ user });
    +    }
    +    if (data.type == "tour:end") {
    +      const session = Session.get();
    +      updateProfileMe({ show_tutorial: false });
    +
    +      const profile = Object.assign(session.payload.user.profile, {
    +        show_tutorial: false,
    +      });
    +      const user = Object.assign(session.payload.user, { profile });
    +      Session.setPayload({ user });
    +      this.props.history.push("/");
    +    }
    +  };
    +  render() {
    +    const autoClockout =
    +      this.state.employer.maximum_clockout_delay_minutes == null ? false : true;
    +    const weekday =
    +      this.state.employer.payroll_period_starting_time.isoWeekday();
    +    let nextDate = this.state.employer.payroll_period_starting_time.clone();
    +    while (nextDate.isBefore(NOW())) nextDate = nextDate.add(7, "days");
    +
    +    return (
    +      <Theme.Consumer>
    +        {({ bar }) => (
    +          <div className="p-1 listcontents company-payroll-settings">
    +            <Wizard
    +              continuous
    +              steps={this.state.steps}
    +              run={this.state.runTutorial}
    +              callback={(data) => this.callback(data)}
    +              disableCloseOnEsc={true}
    +              disableOverlayClose={true}
    +              disableScrollParentFix={true}
    +              styles={{
    +                options: {
    +                  primaryColor: "#000",
    +                },
    +              }}
    +            />
    +            <h1>
    +              <span id="company_details">Your Payroll Settings</span>
    +            </h1>
    +            <div className="row mt-2">
    +              <div className="col-12">
    +                <h4>
    +                  Next payroll will run on{" "}
    +                  {nextDate.format("dddd, MMMM Do YYYY, h:mm a")}
    +                </h4>
    +              </div>
    +            </div>
    +            <form>
    +              <div className="row mt-2">
    +                <div className="col-12" id="payroll_run">
    +                  <label className="d-block">
    +                    When do you want your payroll to run?
    +                  </label>
    +                  <span>Every </span>
    +                  <select
    +                    className="form-control"
    +                    style={{ width: "100px", display: "inline-block" }}
    +                  >
    +                    <option>Week</option>
    +                  </select>
    +                  <span> starting </span>
    +                  <select
    +                    value={weekday || 1}
    +                    className="form-control"
    +                    style={{ width: "120px", display: "inline-block" }}
    +                    onChange={(e) => {
    +                      const diff = e.target.value - weekday;
    +                      let newDate =
    +                        this.state.employer.payroll_period_starting_time
    +                          .clone()
    +                          .add(diff, "days");
    +                      this.setEmployer({
    +                        payroll_period_starting_time: newDate,
    +                      });
    +                    }}
    +                  >
    +                    <option value={1}>Mondays</option>
    +                    <option value={2}>Tuesdays</option>
    +                    <option value={3}>Wednesdays</option>
    +                    <option value={4}>Thursdays</option>
    +                    <option value={5}>Fridays</option>
    +                    <option value={6}>Saturdays</option>
    +                    <option value={7}>Sundays</option>
    +                  </select>
    +                  <span> at </span>
    +                  <DateTime
    +                    dateFormat={false}
    +                    styles={{ width: "100px", display: "inline-block" }}
    +                    timeFormat={DATETIME_FORMAT}
    +                    timeConstraints={{ minutes: { step: 15 } }}
    +                    value={this.state.employer.payroll_period_starting_time}
    +                    renderInput={(properties) => {
    +                      const { value, ...rest } = properties;
    +                      return (
    +                        <input
    +                          value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                          {...rest}
    +                        />
    +                      );
    +                    }}
    +                    onChange={(value) => {
    +                      const starting = moment(
    +                        this.state.employer.payroll_period_starting_time.format(
    +                          "MM-DD-YYYY"
    +                        ) +
    +                          " " +
    +                          value.format("hh:mm a"),
    +                        "MM-DD-YYYY hh:mm a"
    +                      );
    +                      this.setEmployer({
    +                        payroll_period_starting_time: starting,
    +                      });
    +                    }}
    +                  />
    +                </div>
    +              </div>
    +              <div className="row">
    +                <div className="col-12" id="payroll_clockin">
    +                  <label className="d-block">
    +                    When can talents start clocking in?
    +                  </label>
    +                  <select
    +                    value={this.state.employer.maximum_clockin_delta_minutes}
    +                    className="form-control"
    +                    style={{ width: "100px", display: "inline-block" }}
    +                    onChange={(e) =>
    +                      this.setEmployer({
    +                        maximum_clockin_delta_minutes: isNaN(e.target.value)
    +                          ? null
    +                          : e.target.value,
    +                        timeclock_warning: true,
    +                      })
    +                    }
    +                  >
    +                    <option value={0}>Select</option>
    +                    <option value={5}>5 min</option>
    +                    <option value={10}>10 min</option>
    +                    <option value={15}>15 min</option>
    +                    <option value={30}>30 min</option>
    +                    <option value={45}>45 min</option>
    +                    <option value={60}>1 hour</option>
    +                  </select>
    +                  <span> before or after the starting time of the shift</span>
    +                </div>
    +              </div>
    +              <div id="payroll_automatic">
    +                <div className="row mt-2">
    +                  <div className="col-12">
    +                    <label className="d-block">
    +                      Do you want automatic checkout?
    +                    </label>
    +                    <select
    +                      value={autoClockout}
    +                      className="form-control"
    +                      style={{ width: "450px", display: "inline-block" }}
    +                      onChange={(e) => {
    +                        this.setEmployer({
    +                          maximum_clockout_delay_minutes:
    +                            e.target.value == "true" ? 10 : null,
    +                          timeclock_warning: true,
    +                        });
    +                      }}
    +                    >
    +                      <option value={true}>
    +                        Only if the talent forgets to checkout
    +                      </option>
    +                      <option value={false}>
    +                        No, leave the shift active until the talent checkouts
    +                      </option>
    +                    </select>
    +                    {!autoClockout ? (
    +                      ""
    +                    ) : (
    +                      <span>
    +                        , wait
    +                        <input
    +                          type="number"
    +                          style={{ width: "60px" }}
    +                          className="form-control d-inline-block ml-2 mr-2"
    +                          value={
    +                            this.state.employer.maximum_clockout_delay_minutes
    +                          }
    +                          onChange={(e) =>
    +                            this.setEmployer({
    +                              maximum_clockout_delay_minutes: e.target.value,
    +                              timeclock_warning: true,
    +                            })
    +                          }
    +                        />
    +                        min to auto checkout
    +                      </span>
    +                    )}
    +                  </div>
    +                </div>
    +                {this.state.employer.timeclock_warning && (
    +                  <div className="alert alert-warning p-2 mt-3">
    +                    Apply time clock settings to:
    +                    <select
    +                      value={this.state.employer.retroactive}
    +                      className="form-control w-100"
    +                      style={{ width: "100px", display: "inline-block" }}
    +                      onChange={(e) =>
    +                        this.setEmployer({
    +                          retroactive: e.target.value === "true" ? true : false,
    +                        })
    +                      }
    +                    >
    +                      <option value={false}>
    +                        Only new shifts (from now on)
    +                      </option>
    +                      <option value={true}>
    +                        All shifts (including previously created)
    +                      </option>
    +                    </select>
    +                  </div>
    +                )}
    +              </div>
    +              {/* <div className="row mt-2">
    +                            <div className="col-12" id="payroll_deduction">
    +                                <label>Deductions</label>
    +                                <div className="p-1 listcontents">
    +                                    {this.state.deductions.length > 0
    +                                        ? <table className="table table-striped payroll-summary">
    +                                            <thead>
    +                                                <tr>
    +                                                    <th>Name</th>
    +                                                    <th>Deduction</th>
    +                                                    <th>Status</th>
    +                                                    <th>Description</th>
    +                                                    <th></th>
    +                                                </tr>
    +                                            </thead>
    +                                            <tbody>
    +                                                {this.state.deductions.map((deduction, i) => (
    +                                                    <DeductionExtendedCard
    +                                                        key={i}
    +                                                        deduction={deduction}
    +                                                        onEditClick={() => bar.show({
    +                                                            slug: "update_deduction",
    +                                                            data: deduction
    +                                                        })}
    +                                                        onDelete={() => {
    +                                                            const noti = Notify.info("Are you sure you want to delete this deduction?", (answer) => {
    +                                                                if (answer) remove('deduction', deduction);
    +                                                                noti.remove();
    +                                                            });
    +                                                        }}
    +                                                    >
    +                                                    </DeductionExtendedCard>
    +                                                ))}
    +                                            </tbody>
    +                                        </table>
    +                                        : <p>No deductions yet</p>
    +                                    }
    +                                </div>
    +                                <Button
    +                                    size="small"
    +                                    onClick={() => bar.show({
    +                                        slug: "create_deduction",
    +                                        data: {
    +                                            name: "",
    +                                            active: false,
    +                                            value: null,
    +                                            description: "",
    +                                            type: "PERCENTAGE"
    +                                        }
    +                                    })}
    +                                >
    +                                    Add Deduction
    +                                </Button>
    +                            </div>
    +                        </div> */}
    +              <div className="mt-4 text-right">
    +                <button
    +                  id="button_save"
    +                  type="button"
    +                  className="btn btn-primary"
    +                  onClick={() =>
    +                    update(
    +                      { path: "employers/me", event_name: "current_employer" },
    +                      Employer(this.state.employer).validate().serialize()
    +                    ).catch((e) => Notify.error(e.message || e))
    +                  }
    +                >
    +                  Save Payroll Settings
    +                </button>
    +              </div>
    +            </form>
    +          </div>
    +        )}
    +      </Theme.Consumer>
    +    );
    +  }
    +}
    +
    +/**
    + * EditOrAddExpiredShift
    + */
    +export const EditOrAddExpiredShift = ({
    +  onSave,
    +  onCancel,
    +  onChange,
    +  catalog,
    +  formData,
    +  error,
    +  oldShift,
    +}) => {
    +  const { bar } = useContext(Theme.Context);
    +
    +  useEffect(() => {
    +    const venues = store.getState("venues");
    +    const favlists = store.getState("favlists");
    +    if (!venues || !favlists) fetchAllMe(["venues", "favlists"]);
    +  }, []);
    +  const expired =
    +    moment(formData.starting_at).isBefore(NOW()) ||
    +    moment(formData.ending_at).isBefore(NOW());
    +
    +  const validating_minimum = moment(formData.starting_at).isBefore(
    +    formData.period_starting
    +  );
    +  const validating_maximum = moment(formData.ending_at).isAfter(
    +    formData.period_ending
    +  );
    +
    +  return (
    +    <form>
    +      <div className="row">
    +        <div className="col-12">
    +          {formData.hide_warnings === true ? null : formData.status ==
    +              "DRAFT" && !error ? (
    +            <div className="alert alert-warning d-inline">
    +              <i className="fas fa-exclamation-triangle"></i> This shift is a
    +              draft
    +            </div>
    +          ) : formData.status != "UNDEFINED" && !error ? (
    +            <div className="alert alert-success">
    +              This shift is published, therefore{" "}
    +              <strong>it needs to be unpublished</strong> before it can be
    +              updated
    +            </div>
    +          ) : (
    +            ""
    +          )}
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-12">
    +          <label>Looking for</label>
    +          <Select
    +            value={catalog.positions.find(
    +              (pos) => pos.value == formData.position
    +            )}
    +            onChange={(selection) =>
    +              onChange({
    +                position: selection.value.toString(),
    +                has_sensitive_updates: true,
    +              })
    +            }
    +            options={catalog.positions}
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-6">
    +          <label>How many?</label>
    +          <input
    +            type="number"
    +            className="form-control"
    +            value={formData.maximum_allowed_employees}
    +            onChange={(e) => {
    +              if (parseInt(e.target.value, 10) > 0) {
    +                if (
    +                  oldShift &&
    +                  oldShift.employees.length > parseInt(e.target.value, 10)
    +                )
    +                  Notify.error(
    +                    `${oldShift.employees.length} talents are scheduled to work on this shift already, delete scheduled employees first.`
    +                  );
    +                else onChange({ maximum_allowed_employees: e.target.value });
    +              }
    +            }}
    +          />
    +        </div>
    +        <div className="col-6">
    +          <label>Price / hour</label>
    +          <input
    +            type="number"
    +            className="form-control"
    +            value={formData.minimum_hourly_rate}
    +            onChange={(e) =>
    +              onChange({
    +                minimum_hourly_rate: e.target.value,
    +                has_sensitive_updates: true,
    +              })
    +            }
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-12">
    +          <label className="mb-1">Dates</label>
    +          <div className="input-group">
    +            <DateTime
    +              timeFormat={false}
    +              className="shiftdate-picker"
    +              closeOnSelect={true}
    +              viewDate={formData.starting_at}
    +              value={formData.starting_at}
    +              isValidDate={(current) => {
    +                return (
    +                  current.isSameOrAfter(
    +                    moment(formData.period_starting).startOf("day")
    +                  ) &&
    +                  current.isSameOrBefore(
    +                    moment(formData.period_ending).startOf("day")
    +                  )
    +                );
    +              }}
    +              renderInput={(properties) => {
    +                const { value, ...rest } = properties;
    +                return (
    +                  <input
    +                    value={value.match(/\d{2}\/\d{2}\/\d{4}/gm)}
    +                    {...rest}
    +                  />
    +                );
    +              }}
    +              onChange={(value) => {
    +                const getRealDate = (start, end) => {
    +                  if (typeof start == "string") value = moment(start);
    +
    +                  const starting = moment(
    +                    start.format("MM-DD-YYYY") + " " + start.format("hh:mm a"),
    +                    "MM-DD-YYYY hh:mm a"
    +                  );
    +                  var ending = moment(
    +                    start.format("MM-DD-YYYY") + " " + end.format("hh:mm a"),
    +                    "MM-DD-YYYY hh:mm a"
    +                  );
    +
    +                  if (typeof starting !== "undefined" && starting.isValid()) {
    +                    if (ending.isBefore(starting)) {
    +                      ending = ending.add(1, "days");
    +                    }
    +
    +                    return { starting_at: starting, ending_at: ending };
    +                  }
    +                  return null;
    +                };
    +
    +                const mainDate = getRealDate(value, formData.ending_at);
    +                const multipleDates = !Array.isArray(formData.multiple_dates)
    +                  ? []
    +                  : formData.multiple_dates.map((d) =>
    +                      getRealDate(d.starting_at, d.ending_at)
    +                    );
    +                onChange({
    +                  ...mainDate,
    +                  multiple_dates: multipleDates,
    +                  has_sensitive_updates: true,
    +                });
    +              }}
    +            />
    +          </div>
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-6">
    +          <label>From</label>
    +          <DateTime
    +            dateFormat={false}
    +            timeFormat={DATETIME_FORMAT}
    +            closeOnTab={true}
    +            timeConstraints={{ minutes: { step: 15 } }}
    +            value={formData.starting_at}
    +            renderInput={(properties) => {
    +              const { value, ...rest } = properties;
    +              return (
    +                <input
    +                  value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                  {...rest}
    +                />
    +              );
    +            }}
    +            onChange={(value) => {
    +              if (typeof value == "string") value = moment(value);
    +
    +              const getRealDate = (start, end) => {
    +                const starting = moment(
    +                  start.format("MM-DD-YYYY") + " " + value.format("hh:mm a"),
    +                  "MM-DD-YYYY hh:mm a"
    +                );
    +                var ending = moment(end);
    +                if (typeof starting !== "undefined" && starting.isValid()) {
    +                  if (ending.isBefore(starting)) {
    +                    ending = ending.add(1, "days");
    +                  }
    +
    +                  return { starting_at: starting, ending_at: ending };
    +                }
    +                return null;
    +              };
    +
    +              const mainDate = getRealDate(
    +                formData.starting_at,
    +                formData.ending_at
    +              );
    +              const multipleDates = !Array.isArray(formData.multiple_dates)
    +                ? []
    +                : formData.multiple_dates.map((d) =>
    +                    getRealDate(d.starting_at, d.ending_at)
    +                  );
    +              onChange({
    +                ...mainDate,
    +                multiple_dates: multipleDates,
    +                has_sensitive_updates: true,
    +              });
    +            }}
    +          />
    +        </div>
    +        <div className="col-6">
    +          <label>
    +            To{" "}
    +            {formData.ending_at.isBefore(formData.starting_at) && "(next day)"}
    +          </label>
    +          <DateTime
    +            className="picker-left"
    +            dateFormat={false}
    +            timeFormat={DATETIME_FORMAT}
    +            timeConstraints={{ minutes: { step: 15 } }}
    +            value={formData.ending_at}
    +            renderInput={(properties) => {
    +              const { value, ...rest } = properties;
    +              return (
    +                <input
    +                  value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                  {...rest}
    +                />
    +              );
    +            }}
    +            onChange={(value) => {
    +              if (typeof value == "string") value = moment(value);
    +
    +              const getRealDate = (start, end) => {
    +                const starting = start;
    +                var ending = moment(
    +                  start.format("MM-DD-YYYY") + " " + value.format("hh:mm a"),
    +                  "MM-DD-YYYY hh:mm a"
    +                );
    +
    +                if (typeof starting !== "undefined" && starting.isValid()) {
    +                  if (ending.isBefore(starting)) {
    +                    ending = ending.add(1, "days");
    +                  }
    +
    +                  return { starting_at: starting, ending_at: ending };
    +                }
    +                return null;
    +              };
    +
    +              const mainDate = getRealDate(
    +                formData.starting_at,
    +                formData.ending_at
    +              );
    +              const multipleDates = !Array.isArray(formData.multiple_dates)
    +                ? []
    +                : formData.multiple_dates.map((d) =>
    +                    getRealDate(d.starting_at, d.ending_at)
    +                  );
    +              onChange({
    +                ...mainDate,
    +                multiple_dates: multipleDates,
    +                has_sensitive_updates: true,
    +              });
    +            }}
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-12">
    +          <label>Location</label>
    +          <Select
    +            value={catalog.venues.find((ven) => ven.value == formData.venue)}
    +            options={[
    +              {
    +                label: "Add a location",
    +                value: "new_venue",
    +                component: AddOrEditLocation,
    +              },
    +            ].concat(catalog.venues)}
    +            onChange={(selection) => {
    +              if (selection.value == "new_venue")
    +                bar.show({ slug: "create_location", allowLevels: true });
    +              else
    +                onChange({
    +                  venue: selection.value.toString(),
    +                  has_sensitive_updates: true,
    +                });
    +            }}
    +          />
    +        </div>
    +      </div>
    +      <div className="row mt-3">
    +        <div className="col-12">
    +          <h4>Who was supposed to work on this shift?</h4>
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-12">
    +          {/* <label>Search people in JobCore:</label> */}
    +          <SearchCatalogSelect
    +            isMulti={true}
    +            value={formData.employeesToAdd}
    +            onChange={(selections) => {
    +              onChange({ employeesToAdd: selections });
    +            }}
    +            searchFunction={(search) =>
    +              new Promise((resolve, reject) =>
    +                GET("catalog/employees?full_name=" + search)
    +                  .then((talents) =>
    +                    resolve(
    +                      [
    +                        {
    +                          label: `${
    +                            talents.length == 0 ? "No one found: " : ""
    +                          }`,
    +                        },
    +                      ].concat(talents)
    +                    )
    +                  )
    +                  .catch((error) => reject(error))
    +              )
    +            }
    +          />
    +        </div>
    +      </div>
    +
    +      <div className="btn-bar">
    +        <button
    +          type="button"
    +          className="btn btn-success"
    +          onChange={(value) => {
    +            const getRealDate = (start, end) => {
    +              if (typeof start == "string") value = moment(start);
    +
    +              const starting = moment(
    +                start.format("MM-DD-YYYY") + " " + start.format("hh:mm a"),
    +                "MM-DD-YYYY hh:mm a"
    +              );
    +              var ending = moment(
    +                start.format("MM-DD-YYYY") + " " + end.format("hh:mm a"),
    +                "MM-DD-YYYY hh:mm a"
    +              );
    +
    +              if (typeof starting !== "undefined" && starting.isValid()) {
    +                if (ending.isBefore(starting)) {
    +                  ending = ending.add(1, "days");
    +                }
    +
    +                return { starting_at: starting, ending_at: ending };
    +              }
    +              return null;
    +            };
    +            const mainDate = getRealDate(value, formData.ending_at);
    +            onChange({ ...mainDate, has_sensitive_updates: true });
    +          }}
    +          onClick={() =>
    +            validating_maximum || validating_minimum
    +              ? Notify.error("Cannot create shift before payroll time or after")
    +              : onSave({
    +                  executed_action: "create_expired_shift",
    +                  status: "OPEN",
    +                })
    +          }
    +        >
    +          Save and publish
    +        </button>
    +      </div>
    +    </form>
    +  );
    +};
    +EditOrAddExpiredShift.propTypes = {
    +  error: PropTypes.string,
    +  oldShift: PropTypes.object,
    +  bar: PropTypes.object,
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +EditOrAddExpiredShift.defaultProps = {
    +  oldShift: null,
    +};
    +export const ManagePayroll = () => {
    +  const { bar } = useContext(Theme.Context);
    +
    +  return <div className="p-1 listcontents">Pick a period</div>;
    +};
    +
    +export const PayrollPeriodDetails = ({ match, history }) => {
    +  const [employer, setEmployer] = useState(store.getState("current_employer"));
    +  const [period, setPeriod] = useState(null);
    +  const [form, setForm] = useState("");
    +  const [payments, setPayments] = useState([]);
    +  const [formLoading, setFormLoading] = useState(false);
    +  const [open, setOpen] = useState(true);
    +
    +  const { bar } = useContext(Theme.Context);
    +  useEffect(() => {
    +    const employerSub = store.subscribe("current_employer", (employer) =>
    +      setEmployer(employer)
    +    );
    +    if (match.params.period_id !== undefined)
    +      fetchSingle("payroll-periods", match.params.period_id).then((_period) => {
    +        setPeriod(_period);
    +        setPayments(_period.payments);
    +      });
    +
    +    const removeHistoryListener = history.listen((data) => {
    +      const period = /\/payroll\/period\/(\d+)/gm;
    +      const periodMatches = period.exec(data.pathname);
    +      if (periodMatches)
    +        fetchSingle("payroll-periods", periodMatches[1]).then((_period) => {
    +          setPeriod(_period);
    +          setPayments(_period.payments);
    +        });
    +    });
    +
    +    return () => {
    +      employerSub.unsubscribe();
    +      removeHistoryListener();
    +    };
    +  }, []);
    +
    +  if (!employer || !period) return "Loading...";
    +  if (
    +    !employer.payroll_configured ||
    +    !moment.isMoment(employer.payroll_period_starting_time)
    +  ) {
    +    return (
    +      <div className="p-1 listcontents text-center">
    +        <h3>Please setup your payroll settings first.</h3>
    +        <Button
    +          color="success"
    +          onClick={() => history.push("/payroll/settings")}
    +        >
    +          Setup Payroll Settings
    +        </Button>
    +      </div>
    +    );
    +  }
    +
    +  let groupedPayments = {};
    +  for (let i = 0; i < payments.length; i++) {
    +    const pay = payments[i];
    +    if (typeof groupedPayments[pay.employee.id] === "undefined") {
    +      groupedPayments[pay.employee.id] = {
    +        employee: pay.employee,
    +        payments: [],
    +      };
    +    }
    +    groupedPayments[pay.employee.id].payments.push(pay);
    +  }
    +  groupedPayments = Object.keys(groupedPayments).map(
    +    (id) => groupedPayments[id]
    +  );
    +
    +  function parseToTime(num) {
    +    var decimalTimeString = num;
    +    var decimalTime = parseFloat(decimalTimeString);
    +    decimalTime = decimalTime * 60 * 60;
    +    var hours = Math.floor(decimalTime / (60 * 60));
    +    decimalTime = decimalTime - hours * 60 * 60;
    +    var minutes = Math.floor(decimalTime / 60);
    +
    +    if (hours < 10) {
    +      hours = "0" + hours;
    +    }
    +    if (minutes < 10) {
    +      minutes = "0" + minutes;
    +    }
    +    return "" + hours + ":" + minutes;
    +  }
    +
    +  async function getEmployeeDocumet(emp, type) {
    +    setFormLoading(true);
    +    setForm(null);
    +    const id = emp.employee.id;
    +
    +    const w4form = await GET("employers/me/" + "w4-form" + "/" + id);
    +    const i9form = await GET("employers/me/" + "i9-form" + "/" + id);
    +    const employeeDocument = await GET(
    +      "employers/me/" + "employee-documents" + "/" + id
    +    );
    +
    +    const data = {
    +      w4form: w4form[0],
    +      i9form: i9form[0],
    +      employeeDocument: employeeDocument[0] || "",
    +      employeeDocument2: employeeDocument[1] || "",
    +    };
    +
    +    if (type === "w4") fillForm(data);
    +    else if (type === "i9") fillFormI9(data);
    +  }
    +
    +  async function fillForm(data) {
    +    if (data) {
    +      const signature = data.w4form.employee_signature;
    +      const png = `data:image/png;base64,${signature}`;
    +      const formUrl =
    +        "https://api.vercel.com/now/files/20f93230bb41a5571f15a12ca0db1d5b20dd9ce28ca9867d20ca45f6651cca0f/fw4.pdf";
    +
    +      const formPdfBytes = await fetch(formUrl).then((res) =>
    +        res.arrayBuffer()
    +      );
    +      const pngUrl = png;
    +
    +      var pngImageBytes;
    +      var pdfDoc = await PDFDocument.load(formPdfBytes);
    +      var pngImage;
    +      if (signature) {
    +        pngImageBytes = await fetch(pngUrl).then((res) => res.arrayBuffer());
    +
    +        pngImage = await pdfDoc.embedPng(pngImageBytes);
    +      }
    +      const pages = pdfDoc.getPages();
    +      const firstPage = pages[0];
    +
    +      const { width, height } = firstPage.getSize();
    +
    +      const form = pdfDoc.getForm();
    +
    +      var pngDims;
    +      if (pngImage) pngDims = pngImage.scale(0.18);
    +
    +      const nameField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_01[0]"
    +      );
    +      const lastNameField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_02[0]"
    +      );
    +      const socialSecurityField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_05[0]"
    +      );
    +      const addressField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_03[0]"
    +      );
    +      const cityField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_04[0]"
    +      );
    +      const fillingFieldSingle = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].c1_1[0]"
    +      );
    +      const fillingFieldMarried = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].c1_1[1]"
    +      );
    +      const fillingFieldHead = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].c1_1[2]"
    +      );
    +      const multipleJobsField = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].Step2c[0].c1_2[0]"
    +      );
    +      const step3aField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step3_ReadOrder[0].f1_06[0]"
    +      );
    +      const step3bField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step3_ReadOrder[0].f1_07[0]"
    +      );
    +      const step3cField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_08[0]"
    +      );
    +      const step4aField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_09[0]"
    +      );
    +      const step4bField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_10[0]"
    +      );
    +      const step4cField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_11[0]"
    +      );
    +      const employerField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_13[0]"
    +      );
    +      const employmentDateField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_14[0]"
    +      );
    +      const einField = form.getTextField("topmostSubform[0].Page1[0].f1_15[0]");
    +
    +      nameField.setText(data.i9form.first_name);
    +      lastNameField.setText(data.i9form.last_name);
    +      socialSecurityField.setText(data.i9form.social_security);
    +      addressField.setText(data.i9form.address);
    +      cityField.setText(data.i9form.city);
    +
    +      if (data.w4form.filing_status == "SINGLE") fillingFieldSingle.check();
    +      else if (data.w4form.filing_status == "MARRIED")
    +        fillingFieldMarried.check();
    +      else if (data.w4form.filing_status == "HEAD") fillingFieldHead.check();
    +
    +      if (data.w4form.step2c) multipleJobsField.check();
    +
    +      step3aField.setText(data.w4form.dependant3b);
    +      step3bField.setText(data.w4form.dependant3c);
    +
    +      if (data.w4form.dependant3b && data.w4form.dependant3c) {
    +        var totalDependant =
    +          +data.w4form.dependant3b + +data.w4form.dependant3c;
    +        step3cField.setText(totalDependant.toString());
    +      }
    +
    +      step4aField.setText(data.w4form.step4a);
    +      step4bField.setText(data.w4form.step4b);
    +      step4cField.setText(data.w4form.step4c);
    +      employerField.setText(
    +        "JobCore Inc, 270 Catalonia AveCoral Gables, FL 33134"
    +      );
    +      employmentDateField.setText(
    +        moment(data.w4form.updated_at).format("MM/DD/YYYY")
    +      );
    +      einField.setText("83-1919066");
    +
    +      firstPage.drawText(moment(data.w4form.created_at).format("MM/DD/YYYY"), {
    +        x: 470,
    +        y: firstPage.getHeight() / 5.5,
    +        size: 14,
    +        color: rgb(0, 0, 0),
    +      });
    +
    +      if (pngImage) {
    +        firstPage.drawImage(pngImage, {
    +          x: firstPage.getWidth() / 7 - pngDims.width / 2 + 75,
    +          y: firstPage.getHeight() / 4.25 - pngDims.height,
    +          width: pngDims.width,
    +          height: pngDims.height,
    +        });
    +      }
    +
    +      const pdfBytes = await pdfDoc.save();
    +      var blob = new Blob([pdfBytes], { type: "application/pdf" });
    +
    +      const fileURL = URL.createObjectURL(blob);
    +      setForm(fileURL);
    +      setFormLoading(false);
    +
    +      //   window.open(blob);
    +      //   saveAs(blob, `${data.i9form.first_name + "_" + data.i9form.last_name+"_W4"}.pdf`);
    +    }
    +  }
    +  async function fillFormI9(data) {
    +    if (data) {
    +      const signature = data.i9form.employee_signature;
    +      const png = `data:image/png;base64,${signature}`;
    +      const formUrl =
    +        "https://api.vercel.com/now/files/5032a373d2e112174680444f0aac149210e4ac4c3c3b55144913e319cfa72bd4/i92.pdf";
    +      const formPdfBytes = await fetch(formUrl).then((res) =>
    +        res.arrayBuffer()
    +      );
    +
    +      const pngUrl = png;
    +      var pngImageBytes;
    +      var pngImage;
    +      const pdfDoc = await PDFDocument.load(formPdfBytes);
    +      if (signature) {
    +        pngImageBytes = await fetch(pngUrl).then((res) => res.arrayBuffer());
    +        pngImage = await pdfDoc.embedPng(pngImageBytes);
    +      }
    +
    +      const document = data.employeeDocument.document;
    +      var documentBytes;
    +      var documentImage;
    +      if (document) {
    +        documentBytes = await fetch(document).then((res) => res.arrayBuffer());
    +        documentImage = await pdfDoc.embedJpg(documentBytes);
    +      }
    +      var document2Image = null;
    +      if (data.employeeDocument2) {
    +        const document2 = data.employeeDocument2.document;
    +        const document2Bytes = await fetch(document2).then((res) =>
    +          res.arrayBuffer()
    +        );
    +        document2Image = await pdfDoc.embedJpg(document2Bytes);
    +      }
    +      const newPage = pdfDoc.addPage();
    +
    +      const pages = pdfDoc.getPages();
    +      const firstPage = pages[0];
    +
    +      const { width, height } = firstPage.getSize();
    +
    +      const form = pdfDoc.getForm();
    +
    +      var pngDims;
    +
    +      if (pngImage) pngDims = pngImage.scale(0.1);
    +
    +      const lastname = form.getTextField(
    +        "topmostSubform[0].Page1[0].Last_Name_Family_Name[0]"
    +      );
    +      const name = form.getTextField(
    +        "topmostSubform[0].Page1[0].First_Name_Given_Name[0]"
    +      );
    +      const middle = form.getTextField(
    +        "topmostSubform[0].Page1[0].Middle_Initial[0]"
    +      );
    +      const otherlastname = form.getTextField(
    +        "topmostSubform[0].Page1[0].Other_Last_Names_Used_if_any[0]"
    +      );
    +      const address = form.getTextField(
    +        "topmostSubform[0].Page1[0].Address_Street_Number_and_Name[0]"
    +      );
    +      const apt = form.getTextField("topmostSubform[0].Page1[0].Apt_Number[0]");
    +      const city = form.getTextField(
    +        "topmostSubform[0].Page1[0].City_or_Town[0]"
    +      );
    +      const zip = form.getTextField("topmostSubform[0].Page1[0].ZIP_Code[0]");
    +      const birthday = form.getTextField(
    +        "topmostSubform[0].Page1[0].Date_of_Birth_mmddyyyy[0]"
    +      );
    +      const ssn3 = form.getTextField(
    +        "topmostSubform[0].Page1[0].U\\.S\\._Social_Security_Number__First_3_Numbers_[0]"
    +      );
    +      const ssn2 = form.getTextField(
    +        "topmostSubform[0].Page1[0].U\\.S\\._Social_Security_Number__Next_2_numbers_[0]"
    +      );
    +      const ssn4 = form.getTextField(
    +        "topmostSubform[0].Page1[0].U\\.S\\._Social_Security_Number__Last_4_numbers_[0]"
    +      );
    +      const email = form.getTextField(
    +        "topmostSubform[0].Page1[0].Employees_Email_Address[0]"
    +      );
    +      const tel = form.getTextField(
    +        "topmostSubform[0].Page1[0].Employees_Telephone_Number[0]"
    +      );
    +      const citizen = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._1\\._A_citizen_of_the_United_States[0]"
    +      );
    +      const noncitizen = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._2\\._A_noncitizen_national_of_the_United_States__See_instructions_[0]"
    +      );
    +      const resident = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._3\\._A_lawful_permanent_resident__Alien_Registration_Number_USCIS_Number__[0]"
    +      );
    +      const uscis = form.getTextField(
    +        "topmostSubform[0].Page1[0].Alien_Registration_NumberUSCIS_Number_1[0]"
    +      );
    +      const alien = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._4\\._An_alien_authorized_to_work_until__expiration_date__if_applicable__mmd_dd_yyyy__[0]"
    +      );
    +      const exp = form.getTextField(
    +        "topmostSubform[0].Page1[0].expiration_date__if_applicable__mm_dd_yyyy[0]"
    +      );
    +      const alienuscis = form.getTextField(
    +        "topmostSubform[0].Page1[0]._1_Alien_Registration_NumberUSCIS_Number[0]"
    +      );
    +      const admision = form.getTextField(
    +        "topmostSubform[0].Page1[0]._2_Form_I94_Admission_Number[0]"
    +      );
    +      const foreign = form.getTextField(
    +        "topmostSubform[0].Page1[0]._3_Foreign_Passport_Number[0]"
    +      );
    +      const issuance = form.getTextField(
    +        "topmostSubform[0].Page1[0].Country_of_Issuance[0]"
    +      );
    +      const nottranslator = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].I_did_not_use_a_preparer_or_translator[0]"
    +      );
    +      const translator = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].A_preparer_s__and_or_translator_s__assisted_the_employee_in_completing_Section_1[0]"
    +      );
    +      const lastnamet = form.getTextField(
    +        "topmostSubform[0].Page1[0].Last_Name_Family_Name_2[0]"
    +      );
    +      const firstnamet = form.getTextField(
    +        "topmostSubform[0].Page1[0].First_Name_Given_Name_2[0]"
    +      );
    +      const addresst = form.getTextField(
    +        "topmostSubform[0].Page1[0].Address_Street_Number_and_Name_2[0]"
    +      );
    +      const cityt = form.getTextField(
    +        "topmostSubform[0].Page1[0].City_or_Town_2[0]"
    +      );
    +      const zipcodet = form.getTextField(
    +        "topmostSubform[0].Page1[0].Zip_Code[0]"
    +      );
    +      const statet = form.getDropdown("topmostSubform[0].Page1[0].State[0]");
    +      const state2t = form.getDropdown("topmostSubform[0].Page1[0].State[1]");
    +      const lastname2 = form.getTextField(
    +        "topmostSubform[0].Page2[0].Last_Name_Family_Name_3[0]"
    +      );
    +      const firstname2 = form.getTextField(
    +        "topmostSubform[0].Page2[0].First_Name_Given_Name_3[0]"
    +      );
    +      const middle2 = form.getTextField("topmostSubform[0].Page2[0].MI[0]");
    +
    +      lastname.setText(data.i9form.last_name);
    +      name.setText(data.i9form.first_name);
    +      middle.setText(data.i9form.middle_initial);
    +      otherlastname.setText(data.i9form.other_last_name);
    +      address.setText(data.i9form.address);
    +      apt.setText(data.i9form.apt_number);
    +      city.setText(data.i9form.city);
    +      zip.setText(data.i9form.zipcode);
    +      birthday.setText(data.i9form.date_of_birth);
    +
    +      if (data.i9form.social_security) {
    +        var ssn = data.i9form.social_security.split("-");
    +        ssn3.setText(ssn[0]);
    +        ssn2.setText(ssn[1]);
    +        ssn4.setText(ssn[2]);
    +      }
    +
    +      email.setText(data.i9form.email);
    +      tel.setText(data.i9form.phone);
    +      if (data.i9form.employee_attestation === "CITIZEN") citizen.check();
    +      else if (data.i9form.employee_attestation === "NON_CITIZEN")
    +        noncitizen.check();
    +      else if (data.i9form.employee_attestation === "ALIEN") alien.check();
    +      else if (data.i9form.employee_attestation === "RESIDENT")
    +        resident.check();
    +
    +      uscis.setText(data.i9form.USCIS);
    +      exp.setText("");
    +      alienuscis.setText(data.i9form.USCIS);
    +      admision.setText(data.i9form.I_94);
    +      foreign.setText(data.i9form.passport_number);
    +      issuance.setText(data.i9form.country_issuance);
    +
    +      if (!data.i9form.translator) nottranslator.check();
    +      else if (!data.i9form.translator) translator.check();
    +
    +      lastnamet.setText(data.i9form.translator_last_name);
    +      firstnamet.setText(data.i9form.translator_first_name);
    +      addresst.setText(data.i9form.translator_address);
    +      cityt.setText(data.i9form.translator_city);
    +      zipcodet.setText(data.i9form.translator_zipcode);
    +      if (data.i9form.translator_state) {
    +        statet.select(data.i9form.translator_state);
    +        state2t.select(data.i9form.translator_state);
    +      }
    +      lastname2.setText(data.i9form.last_name);
    +      firstname2.setText(data.i9form.first_name);
    +      middle2.setText(data.i9form.middle_initial);
    +
    +      if (documentImage) {
    +        pages[3].drawImage(documentImage, {
    +          height: 325,
    +          width: 275,
    +          x: 50,
    +          y: 790 - 325,
    +        });
    +      }
    +
    +      if (document2Image) {
    +        pages[3].drawImage(document2Image, {
    +          height: 325,
    +          width: 275,
    +          x: 50,
    +          y: 790 - 325 - 350,
    +        });
    +      }
    +      firstPage.drawText(moment(data.w4form.created_at).format("MM/DD/YYYY"), {
    +        x: 375,
    +        y: 257,
    +        size: 10,
    +        color: rgb(0, 0, 0),
    +      });
    +
    +      if (pngImage) {
    +        firstPage.drawImage(pngImage, {
    +          x: 115,
    +          y: 240,
    +          width: pngDims.width,
    +          height: pngDims.height,
    +        });
    +      }
    +
    +      const pdfBytes = await pdfDoc.save();
    +      var blob = new Blob([pdfBytes], { type: "application/pdf" });
    +      const fileURL = URL.createObjectURL(blob);
    +      setForm(fileURL);
    +      setFormLoading(false);
    +      //   saveAs(blob, `${data.i9form.first_name + "_" + data.i9form.last_name+"_I9"+moment().format("MMDDYYYY")}.pdf`);
    +    }
    +  }
    +
    +  return (
    +    <div className="p-1 listcontents">
    +      <div
    +        className="modal fade"
    +        id="exampleModalCenter"
    +        tabIndex="-1"
    +        role="dialog"
    +        aria-labelledby="exampleModalCenterTitle"
    +        aria-hidden="true"
    +      >
    +        <div className="modal-dialog modal-dialog-centered" role="document">
    +          <div className="modal-content">
    +            {form ? (
    +              <iframe
    +                src={form}
    +                style={{ width: "800px", height: "900px" }}
    +                frameBorder="0"
    +              ></iframe>
    +            ) : (
    +              <div className="spinner-border text-center mx-auto" role="status">
    +                <span className="sr-only">Loading...</span>
    +              </div>
    +            )}
    +          </div>
    +        </div>
    +      </div>
    +      <p className="text-right">
    +        {period.status != "OPEN" ? (
    +          <div>
    +            <Button
    +              className="btn btn-info text-left mr-4"
    +              onClick={() => {
    +                update(
    +                  "payroll-periods",
    +                  Object.assign(period, { status: "OPEN" })
    +                )
    +                  .then((_payment) =>
    +                    setPayments(
    +                      payments.map((_pay) => {
    +                        return {
    +                          ..._pay,
    +                          status: "APPROVED",
    +                        };
    +                      })
    +                    )
    +                  )
    +                  .catch((e) => Notify.error(e.message || e));
    +              }}
    +            >
    +              Undo Period
    +            </Button>
    +            <Button
    +              className="btn btn-info"
    +              onClick={() => history.push("/payroll/report/" + period.id)}
    +            >
    +              Take me to the Payroll Report
    +            </Button>
    +          </div>
    +        ) : (
    +          <Button
    +            icon="plus"
    +            size="small"
    +            onClick={() => {
    +              const isOpen = period.payments.find((p) => p.status === "NEW");
    +              const thereIsAnotherNew = payments.find(
    +                (item) => item.status === "NEW"
    +              );
    +
    +              if (isOpen) return;
    +              if (thereIsAnotherNew)
    +                setPayments(
    +                  payments.map((_pay) => {
    +                    if (_pay.status !== "NEW") return _pay;
    +                    else {
    +                      return {
    +                        ..._pay,
    +                        payments: _pay.payments.filter(
    +                          (p) => p.status == "NEW"
    +                        ),
    +                      };
    +                    }
    +                  })
    +                );
    +
    +              setPayments(
    +                period.payments.concat([
    +                  Payment({
    +                    status: "NEW",
    +                    employee: { id: "new" },
    +                  }).defaults(),
    +                ])
    +              );
    +              bar.close();
    +            }}
    +          >
    +            Add employee to timesheet
    +          </Button>
    +        )}
    +      </p>
    +      {groupedPayments.length == 0 ? (
    +        <p>No clockins to review for this period</p>
    +      ) : (
    +        groupedPayments
    +          .sort((a, b) =>
    +            a.employee.id === "new"
    +              ? -1
    +              : b.employee.id === "new"
    +              ? 1
    +              : a.employee.user.last_name.toLowerCase() >
    +                b.employee.user.last_name.toLowerCase()
    +              ? 1
    +              : -1
    +          )
    +          .map((pay) => {
    +            const total_hours = pay.payments
    +              .filter((p) => p.status === "APPROVED" || p.status === "PAID")
    +              .reduce(
    +                (total, { regular_hours, over_time, breaktime_minutes }) =>
    +                  total + Number(regular_hours) + Number(over_time),
    +                0
    +              );
    +            const total_amount = pay.payments
    +              .filter((p) => p.status === "APPROVED" || p.status === "PAID")
    +              .reduce(
    +                (
    +                  total,
    +                  { regular_hours, over_time, hourly_rate, breaktime_minutes }
    +                ) =>
    +                  total +
    +                  (Number(regular_hours) + Number(over_time)) *
    +                    Number(hourly_rate),
    +                0
    +              );
    +            return (
    +              <table
    +                key={pay.employee.id}
    +                className="table table-striped payroll-summary"
    +              >
    +                <thead>
    +                  <tr>
    +                    <th>
    +                      {!pay.employee || pay.employee.id === "new" ? (
    +                        <SearchCatalogSelect
    +                          onChange={(selection) => {
    +                            const _alreadyExists = !Array.isArray(payments)
    +                              ? false
    +                              : payments.find(
    +                                  (p) => p.employee.id === selection.value
    +                                );
    +                            if (_alreadyExists)
    +                              Notify.error(
    +                                `${selection.label} is already listed on this timesheet`
    +                              );
    +                            else
    +                              GET("employees/" + selection.value)
    +                                .then((emp) => {
    +                                  setPayments(
    +                                    payments.map((p) => {
    +                                      if (p.employee.id != "new") return p;
    +                                      else
    +                                        return Payment({
    +                                          status: "NEW",
    +                                          employee: emp,
    +                                        }).defaults();
    +                                    })
    +                                  );
    +                                })
    +                                .catch((e) => Notify.error(e.message || e));
    +                          }}
    +                          searchFunction={(search) =>
    +                            new Promise((resolve, reject) =>
    +                              GET("catalog/employees?full_name=" + search)
    +                                .then((talents) =>
    +                                  resolve(
    +                                    [
    +                                      {
    +                                        label: `${
    +                                          talents.length == 0
    +                                            ? "No one found: "
    +                                            : ""
    +                                        }Invite "${search}" to jobcore`,
    +                                        value: "invite_talent_to_jobcore",
    +                                      },
    +                                    ].concat(talents)
    +                                  )
    +                                )
    +                                .catch((error) => reject(error))
    +                            )
    +                          }
    +                        />
    +                      ) : (
    +                        <div className="row">
    +                          <div className="col-12 pr-0">
    +                            <EmployeeExtendedCard
    +                              className="pr-2"
    +                              employee={pay.employee}
    +                              showFavlist={false}
    +                              hoverEffect={false}
    +                              showButtonsOnHover={false}
    +                              onClick={() => null}
    +                            />
    +                          </div>
    +                          {/* {
    +                                            pay.employee.employment_verification_status === "APPROVED" && (
    +                                            <div className="col-2 my-auto pl-0">
    +                                                <i style={{fontSize:"16px", cursor:"pointer", color:'#000000'}}className="fas fa-file-alt" onClick={() => getEmployeeDocumet(pay)}></i>
    +                                            </div>
    +                                            )
    +                                        } */}
    +                        </div>
    +                      )}
    +                      <div className="row" style={{ marginLeft: "40px" }}>
    +                        <div className="col-6 pr-0">
    +                          {pay.employee.employment_verification_status ===
    +                          "APPROVED" ? (
    +                            <span
    +                              style={{ cursor: "pointer" }}
    +                              data-toggle="modal"
    +                              data-target="#exampleModalCenter"
    +                              onClick={() => {
    +                                if (!formLoading) getEmployeeDocumet(pay, "w4");
    +                              }}
    +                            >
    +                              <i
    +                                style={{ fontSize: "16px", color: "#43A047" }}
    +                                className="fas fa-file-alt mr-1"
    +                              ></i>
    +                              {!formLoading ? "W-4" : "Loading"}
    +                            </span>
    +                          ) : (
    +                            <span className="text-muted" 
    +                              style={{ cursor: "pointer" }}
    +                              data-toggle="modal"
    +                              data-target="#exampleModalCenter"
    +                              onClick={() => {
    +                                if (!formLoading) getEmployeeDocumet(pay, "w4");
    +                              }}
    +                            >
    +                              <i className="fas fa-exclamation-circle text-danger mr-1"></i>
    +                              W-4
    +                            </span>
    +                          )}
    +                        </div>
    +                        <div className="col-6">
    +                          {pay.employee.employment_verification_status ===
    +                          "APPROVED" ? (
    +                            <span
    +                              style={{ cursor: "pointer" }}
    +                              data-toggle="modal"
    +                              data-target="#exampleModalCenter"
    +                              onClick={() => {
    +                                if (!formLoading) getEmployeeDocumet(pay, "i9");
    +                              }}
    +                            >
    +                              <i
    +                                style={{ fontSize: "16px", color: "#43A047" }}
    +                                className="fas fa-file-alt mr-1"
    +                              ></i>
    +                              {!formLoading ? "I-9" : "Loading"}
    +                            </span>
    +                          ) : (
    +                            <span className="text-muted"
    +                              style={{ cursor: "pointer" }}
    +                              data-toggle="modal"
    +                              data-target="#exampleModalCenter"
    +                              onClick={() => {
    +                                if (!formLoading) getEmployeeDocumet(pay, "i9");
    +                              }}
    +                            >
    +                              <i className="fas fa-exclamation-circle text-danger mr-1"></i>
    +                              I-9
    +                            </span>
    +                          )}
    +                        </div>
    +                      </div>
    +                    </th>
    +                    <th>In</th>
    +                    <th>Out</th>
    +                    <th>Total</th>
    +                    <th>Break</th>
    +                    <th>
    +                      With <br /> Break
    +                    </th>
    +                    <th>Diff</th>
    +                    <th style={{ minWidth: "80px" }}>Message</th>
    +                    <th style={{ minWidth: "80px" }}></th>
    +                  </tr>
    +                </thead>
    +                <tbody>
    +                  {pay.payments
    +                    .sort((a, b) => {
    +                      if (a && b) {
    +                        if (a.shift && b.shift) {
    +                          if (a.shift.starting_at && b.shift.starting_at) {
    +                            return moment(a.shift.starting_at).diff(
    +                              b.shift.starting_at
    +                            );
    +                          } else return -1;
    +                        }
    +                      } else return -1;
    +                    })
    +                    .map((p) => (
    +                      <PaymentRow
    +                        key={p.id}
    +                        payment={p}
    +                        period={period}
    +                        employee={pay.employee}
    +                        readOnly={p.status !== "PENDING" && p.status !== "NEW"}
    +                        onApprove={(payment) => {
    +                          p.status !== "NEW"
    +                            ? update("payment", {
    +                                ...payment,
    +                                status: "APPROVED",
    +                                employee: p.employee.id || p.employee,
    +                                shift: payment.shift
    +                                  ? payment.shift.id
    +                                  : p.shift.id,
    +                                id: p.id,
    +                              }).then((_payment) =>
    +                                setPayments(
    +                                  payments.map((_pay) =>
    +                                    _pay.id !== p.id
    +                                      ? _pay
    +                                      : {
    +                                          ..._pay,
    +                                          status: "APPROVED",
    +                                          breaktime_minutes:
    +                                            payment.breaktime_minutes,
    +                                          over_time: payment.over_time,
    +                                          regular_hours: payment.regular_hours,
    +                                        }
    +                                  )
    +                                )
    +                              )
    +                            : create("payment", {
    +                                ...payment,
    +                                status: "APPROVED",
    +                                employee: p.employee.id || p.employee,
    +                                shift: payment.shift
    +                                  ? payment.shift.id
    +                                  : p.shift.id,
    +                                payroll_period: period.id,
    +                                id: p.id,
    +                              }).then((_payment) =>
    +                                setPayments(
    +                                  payments.map((_pay) =>
    +                                    _pay.id !== payment.id
    +                                      ? _pay
    +                                      : {
    +                                          ...payment,
    +                                          status: "APPROVED",
    +                                          employee: _pay.employee,
    +                                          over_time: payment.over_time,
    +                                          breaktime_minutes:
    +                                            payment.breaktime_minutes,
    +                                          hourly_rate:
    +                                            payment.shift.minimum_hourly_rate,
    +                                          shift: payment.shift,
    +                                          regular_hours: payment.regular_hours,
    +                                          id: p.id || _pay.id || _payment.id,
    +                                        }
    +                                  )
    +                                )
    +                              );
    +                        }}
    +                        onUndo={(payment) =>
    +                          update("payment", {
    +                            status: "PENDING",
    +                            id: p.id,
    +                          }).then((_payment) =>
    +                            setPayments(
    +                              payments.map((_pay) =>
    +                                _pay.id !== payment.id
    +                                  ? _pay
    +                                  : { ..._pay, status: "PENDING" }
    +                              )
    +                            )
    +                          )
    +                        }
    +                        onReject={(payment) => {
    +                          if (p.id === undefined)
    +                            setPayments(
    +                              payments.filter(
    +                                (_pay) => _pay.id !== undefined && _pay.id
    +                              )
    +                            );
    +                          else
    +                            update("payment", {
    +                              id: p.id,
    +                              status: "REJECTED",
    +                            }).then((_payment) =>
    +                              setPayments(
    +                                payments.map((_pay) =>
    +                                  _pay.id !== _payment.id
    +                                    ? _pay
    +                                    : { ..._pay, status: "REJECTED" }
    +                                )
    +                              )
    +                            );
    +                        }}
    +                      />
    +                    ))}
    +                  <tr>
    +                    <td colSpan={5}>
    +                      {period.status === "OPEN" && pay.employee.id !== "new" && (
    +                        <Button
    +                          icon="plus"
    +                          size="small"
    +                          onClick={() => {
    +                            setPayments(
    +                              payments.concat([
    +                                Payment({
    +                                  status: "NEW",
    +                                  employee: pay.employee,
    +                                  regular_hours: "0.00",
    +                                  over_time: "0.00",
    +                                  hourly_rate: "0.00",
    +                                }).defaults(),
    +                              ])
    +                            );
    +                          }}
    +                        >
    +                          Add new clockin
    +                        </Button>
    +                      )}
    +                    </td>
    +                    <td colSpan={4} className="text-right">
    +                      Total: {!isNaN(total_hours) ? total_hours.toFixed(2) : 0}{" "}
    +                      hr
    +                      {!isNaN(total_hours) &&
    +                      Math.round(total_hours * 100) / 100 > 40 ? (
    +                        <Tooltip
    +                          placement="bottom"
    +                          trigger={["hover"]}
    +                          overlay={
    +                            <small>
    +                              This employee has{" "}
    +                              {Math.round((total_hours - 40) * 100) / 100}hr
    +                              overtime{" "}
    +                            </small>
    +                          }
    +                        >
    +                          <i className="fas fa-stopwatch text-danger fa-xs mr-2"></i>
    +                        </Tooltip>
    +                      ) : null}
    +                      <small className="d-block">
    +                        {!isNaN(total_amount) &&
    +                        !isNaN(total_hours) &&
    +                        Math.round(total_hours * 100) / 100 < 40
    +                          ? "$" + Math.round(total_amount * 100) / 100
    +                          : null}
    +                      </small>
    +                      {!isNaN(total_hours) &&
    +                      Math.round(total_hours * 100) / 100 > 40 ? (
    +                        <div>
    +                          <small className="d-block">
    +                            Reg: $
    +                            {!isNaN(total_amount)
    +                              ? Math.round(total_amount * 100) / 100
    +                              : total_amount}
    +                          </small>
    +                          <small className="d-block">
    +                            OT: $
    +                            {(
    +                              ((Math.round(total_amount * 100) /
    +                                100 /
    +                                (Math.round(total_hours * 100) / 100)) *
    +                                0.5 *
    +                                Math.round((total_hours - 40) * 100)) /
    +                              100
    +                            ).toFixed(2)}
    +                          </small>
    +                          <small className="d-block">
    +                            Total: $
    +                            {(
    +                              ((Math.round(total_amount * 100) /
    +                                100 /
    +                                (Math.round(total_hours * 100) / 100)) *
    +                                0.5 *
    +                                Math.round((total_hours - 40) * 100)) /
    +                                100 +
    +                              Math.round(total_amount * 100) / 100
    +                            ).toFixed(2)}
    +                          </small>
    +                        </div>
    +                      ) : null}
    +                    </td>
    +                  </tr>
    +                  {/* <tr> */}
    +
    +                  {/* </tr> */}
    +                </tbody>
    +              </table>
    +            );
    +          })
    +      )}
    +
    +      <div className="btn-bar text-right">
    +        {period.status === "OPEN" ? (
    +          <button
    +            type="button"
    +            className="btn btn-primary"
    +            onClick={() => {
    +              const unapproved = [].concat.apply(
    +                [],
    +                payments.find((p) => p.status === "PENDING")
    +              );
    +
    +              // const unapproved = [].concat.apply([], payments.map(p => p.payments)).find(p => p.status === "PENDING");
    +
    +              // if (unapproved) Notify.error("There are still some payments that need to be approved or rejected");
    +              if (Array.isArray(unapproved) && unapproved.length > 0)
    +                Notify.error(
    +                  "There are still some payments that need to be approved or rejected"
    +                );
    +              else if (Array.isArray(payments) && payments.length === 0)
    +                Notify.error("There are no clockins to review for this period");
    +              // else {history.push('/payroll/rating/' + period.id);}
    +              else
    +                update(
    +                  "payroll-periods",
    +                  Object.assign(period, { status: "FINALIZED" })
    +                )
    +                  .then((res) => {
    +                    if (res) {
    +                      history.push("/payroll/report/" + period.id);
    +                    }
    +                  })
    +                  .catch((e) => Notify.error(e.message || e));
    +            }}
    +          >
    +            Finalize Period
    +          </button>
    +        ) : (
    +          //    else history.push('/payroll/rating/' + period.id);
    +          //                 }}>Finalize Period</button>
    +          <Button
    +            className="btn btn-success"
    +            onClick={() => history.push("/payroll/report/" + period.id)}
    +          >
    +            Take me to the Payroll Report
    +          </Button>
    +        )}
    +      </div>
    +    </div>
    +  );
    +};
    +
    +PayrollPeriodDetails.propTypes = {
    +  history: PropTypes.object.isRequired,
    +  match: PropTypes.object.isRequired,
    +};
    +
    +function createMapOptions(maps) {
    +  // next props are exposed at maps
    +  // "Animation", "ControlPosition", "MapTypeControlStyle", "MapTypeId",
    +  // "NavigationControlStyle", "ScaleControlStyle", "StrokePosition", "SymbolPath", "ZoomControlStyle",
    +  // "DirectionsStatus", "DirectionsTravelMode", "DirectionsUnitSystem", "DistanceMatrixStatus",
    +  // "DistanceMatrixElementStatus", "ElevationStatus", "GeocoderLocationType", "GeocoderStatus", "KmlLayerStatus",
    +  // "MaxZoomStatus", "StreetViewStatus", "TransitMode", "TransitRoutePreference", "TravelMode", "UnitSystem"
    +  return {
    +    zoomControlOptions: {
    +      position: maps.ControlPosition.RIGHT_CENTER,
    +      style: maps.ZoomControlStyle.SMALL,
    +    },
    +    zoomControl: false,
    +    scaleControl: false,
    +    fullscreenControl: false,
    +    mapTypeControl: false,
    +  };
    +}
    +const Marker = ({ text, className }) => (
    +  <div className={className}>
    +    <i className="fas fa-map-marker-alt fa-lg"></i>
    +  </div>
    +);
    +Marker.propTypes = {
    +  text: PropTypes.string,
    +  className: PropTypes.string,
    +};
    +Marker.defaultProps = {
    +  className: "",
    +};
    +
    +const LatLongClockin = ({ clockin, children, isIn }) => {
    +  if (!clockin) return null;
    +  const lat = isIn ? clockin.latitude_in : clockin.latitude_out;
    +  const lng = isIn ? clockin.longitude_in : clockin.longitude_out;
    +  const distance = isIn
    +    ? clockin.distance_in_miles
    +    : clockin.distance_out_miles;
    +  const time = isIn
    +    ? clockin.started_at.format("LT")
    +    : clockin.ended_at
    +    ? clockin.ended_at.format("LT")
    +    : "";
    +
    +  return (
    +    <Tooltip
    +      placement="right"
    +      trigger={["hover"]}
    +      overlay={
    +        <div
    +          style={{ width: "200px", height: "200px" }}
    +          className="p-0 d-inline-block"
    +        >
    +          <GoogleMapReact
    +            bootstrapURLKeys={{ key: process.env.GOOGLE_MAPS_WEB_KEY }}
    +            defaultCenter={{ lat: 25.7617, lng: -80.1918 }}
    +            width="100%"
    +            height="100%"
    +            center={{ lat, lng }}
    +            options={createMapOptions}
    +            defaultZoom={14}
    +          >
    +            <Marker lat={lat} lng={lng} text={"Jobcore"} />
    +          </GoogleMapReact>
    +          <p
    +            className={`m-0 p-0 text-center ${
    +              distance > 0.2 ? "text-danger" : ""
    +            }`}
    +          >
    +            {distance} miles away @ {time}
    +            <br />
    +            <small>
    +              [ {lat}, {lng} ]
    +            </small>
    +          </p>
    +        </div>
    +      }
    +    >
    +      {children}
    +    </Tooltip>
    +  );
    +};
    +
    +LatLongClockin.propTypes = {
    +  clockin: PropTypes.object.isRequired,
    +  isIn: PropTypes.bool.isRequired,
    +  children: PropTypes.node.isRequired,
    +};
    +LatLongClockin.defaultProps = {
    +  clockin: null,
    +  isIn: true,
    +  children: null,
    +};
    +
    +const PaymentRow = ({
    +  payment,
    +  employee,
    +  onApprove,
    +  onReject,
    +  onUndo,
    +  readOnly,
    +  period,
    +  onChange,
    +  selection,
    +}) => {
    +  const { bar } = useContext(Theme.Context);
    +  if (!employee || employee.id === "new")
    +    return (
    +      <p className="px-3 py-1">⬆ Search an employee from the list above...</p>
    +    );
    +
    +  const [clockin, setClockin] = useState(
    +    Clockin(payment.clockin).defaults().unserialize()
    +  );
    +  const [shift, setShift] = useState(
    +    Shift(payment.shift).defaults().unserialize()
    +  );
    +  const [possibleShifts, setPossibleShifts] = useState(null);
    +
    +  const [breaktime, setBreaktime] = useState(payment.breaktime_minutes);
    +
    +  let shiftStartingTimeNoSeconds = moment(shift.starting_at).format(
    +    "YYYY-MM-DDTHH:mm"
    +  );
    +  let shiftEndingTimeNoSeconds = moment(shift.ending_at).format(
    +    "YYYY-MM-DDTHH:mm"
    +  );
    +  const approvedClockin = payment.approved_clockin_time
    +    ? moment(payment.approved_clockin_time).startOf("minute")
    +    : clockin.started_at
    +    ? clockin.started_at
    +    : shift.starting_at;
    +  const approvedClockout = payment.approved_clockout_time
    +    ? moment(payment.approved_clockout_time).startOf("minute")
    +    : clockin.ended_at
    +    ? clockin.ended_at
    +    : shift.ending_at;
    +  const [approvedTimes, setApprovedTimes] = useState({
    +    in: approvedClockin,
    +    out: approvedClockout,
    +  });
    +
    +  const clockInDuration = moment.duration(
    +    approvedTimes.out.diff(approvedTimes.in)
    +  );
    +
    +  // const clockinHours = !clockInDuration ? 0 : clockin.shift || !readOnly ? Math.round(clockInDuration.asHours() * 100) / 100 : "-";
    +  const clockinHours = Math.round(clockInDuration.asHours() * 100) / 100;
    +  const shiftStartTime = shift.starting_at.format("LT");
    +  const shiftEndTime = shift.ending_at.format("LT");
    +  const shiftNextDay = shift.ending_at.isBefore(shift.starting_at);
    +  const shiftDuration = moment.duration(
    +    moment(shiftEndingTimeNoSeconds).diff(moment(shiftStartingTimeNoSeconds))
    +  );
    +  const plannedHours = Math.round(shiftDuration.asHours() * 100) / 100;
    +
    +  const clockInDurationAfterBreak = clockInDuration.subtract(
    +    breaktime,
    +    "minute"
    +  );
    +  const clockInTotalHoursAfterBreak = !clockInDurationAfterBreak
    +    ? 0
    +    : clockInDurationAfterBreak.asHours().toFixed(2);
    +  const clockInTotalHoursAfterBreakCost = (
    +    Number(shift.price.amount) * Number(clockInTotalHoursAfterBreak)
    +  ).toFixed(2);
    +  const diff =
    +    Math.round(
    +      (Number(clockInTotalHoursAfterBreak) - Number(plannedHours)) * 100
    +    ) / 100;
    +  // const overtime = clockInTotalHoursAfterBreak > 40 ? clockInTotalHoursAfterBreak - 40 : 0;
    +
    +  const lateClockin =
    +    clockin &&
    +    shift &&
    +    clockin.started_at.diff(shift.starting_at, "minutes") >= 15
    +      ? true
    +      : false;
    +  const lateClockout =
    +    clockin && shift && clockin.ended_at.diff(shift.ending_at, "minutes") >= 30
    +      ? true
    +      : false;
    +  const earlyClockin =
    +    clockin &&
    +    shift &&
    +    clockin.started_at.diff(shift.starting_at, "minutes") <= -30
    +      ? true
    +      : false;
    +  const earlyClockout =
    +    clockin && shift && clockin.ended_at.diff(shift.ending_at, "minutes") <= -30
    +      ? true
    +      : false;
    +
    +  useEffect(() => {
    +    let subs = null;
    +    if (payment.status === "NEW") {
    +      fetchTemporal(
    +        `employers/me/shifts?start=${moment(period.starting_at).format(
    +          "YYYY-MM-DD"
    +        )}&end=${moment(period.ending_at).format("YYYY-MM-DD")}&employee=${
    +          employee.id
    +        }`,
    +        "employee-expired-shifts"
    +      ).then((_shifts) => {
    +        const _posibleShifts = _shifts.map((s) => ({
    +          label: "",
    +          value: Shift(s).defaults().unserialize(),
    +        }));
    +        setPossibleShifts(_posibleShifts);
    +      });
    +      subs = store.subscribe("employee-expired-shifts", (_shifts) => {
    +        const _posibleShifts = _shifts.map((s) => ({
    +          label: "",
    +          value: Shift(s).defaults().unserialize(),
    +        }));
    +        const possible = _posibleShifts.map((item) => {
    +          const obj = Object.assign({}, item);
    +          obj["value"]["starting_at"] = moment(
    +            item.value.starting_at,
    +            "YYYY-MM-DDTHH:mm"
    +          ).local();
    +          obj["value"]["ending_at"] = moment(
    +            item.value.ending_at,
    +            "YYYY-MM-DDTHH:mm"
    +          ).local();
    +          obj["value"]["position"] = item.value.position.label
    +            ? { title: item.value.position.label, id: item.value.position.id }
    +            : item.value.position;
    +          return obj;
    +        });
    +        setPossibleShifts(_posibleShifts);
    +      });
    +    }
    +    return () => {
    +      if (subs) subs.unsubscribe();
    +    };
    +  }, []);
    +
    +  return (
    +    <tr id={"paymemt" + payment.id}>
    +      {payment.status === "NEW" ? (
    +        <td>
    +          <Select
    +            className="select-shifts"
    +            value={
    +              !possibleShifts
    +                ? { label: "Loading talent shifts", value: "loading" }
    +                : { value: shift }
    +            }
    +            components={{
    +              Option: ShiftOption,
    +              SingleValue: ShiftOptionSelected({ multi: false }),
    +            }}
    +            onChange={(selectedOption) => {
    +              const _shift = selectedOption.value;
    +              if (_shift) {
    +                if (_shift == "new_shift")
    +                  bar.show({
    +                    slug: "create_expired_shift",
    +                    data: {
    +                      employeesToAdd: [
    +                        {
    +                          label:
    +                            employee.user.first_name +
    +                            " " +
    +                            employee.user.last_name,
    +                          value: employee.id,
    +                        },
    +                      ],
    +                      // Dates are in utc so I decided to change it to local time
    +                      starting_at: moment(period.starting_at),
    +                      ending_at: moment(period.starting_at).add(2, "hours"),
    +                      period_starting: moment(period.starting_at),
    +                      period_ending: moment(period.ending_at),
    +                      shift: _shift,
    +                      application_restriction: "SPECIFIC_PEOPLE",
    +                    },
    +                  });
    +                else {
    +                  setShift(_shift);
    +                  setBreaktime(0);
    +                }
    +              }
    +            }}
    +            options={
    +              possibleShifts
    +                ? [
    +                    {
    +                      label: "Add a shift",
    +                      value: "new_shift",
    +                      component: EditOrAddExpiredShift,
    +                    },
    +                  ].concat(possibleShifts)
    +                : [
    +                    {
    +                      label: "Add a shift",
    +                      value: "new_shift",
    +                      component: EditOrAddExpiredShift,
    +                    },
    +                  ]
    +            }
    +          ></Select>
    +        </td>
    +      ) : (
    +        <td>
    +          <div className="shift-details">
    +            <p className="p-o m-0">
    +              <strong className="shift-date">
    +                {shift.starting_at.format("ddd, ll")}
    +              </strong>
    +            </p>
    +            <small className="shift-position text-success">
    +              {shift.position.title || shift.position.label}
    +            </small>{" "}
    +            @
    +            <small className="shift-location text-primary">
    +              {" "}
    +              {shift.venue.title}
    +            </small>
    +          </div>
    +          {
    +            <div>
    +              {typeof shift.price == "string" ? (
    +                shift.price === "0.0" ? (
    +                  ""
    +                ) : (
    +                  <small className="shift-price"> ${shift.price}</small>
    +                )
    +              ) : (
    +                <small className="shift-price">
    +                  {" "}
    +                  {shift.price.currencySymbol || "$"}
    +                  {shift.price.amount || shift.minimum_hourly_rate}
    +                </small>
    +              )}{" "}
    +              {clockin && (
    +                <div className="d-inline-block">
    +                  {clockin.latitude_in > 0 && (
    +                    <LatLongClockin isIn={true} clockin={clockin}>
    +                      <small
    +                        className={`pointer mr-2 ${
    +                          clockin.distance_in_miles > 0.2 ? "text-danger" : ""
    +                        }`}
    +                      >
    +                        <i className="fas fa-map-marker-alt"></i> In
    +                      </small>
    +                    </LatLongClockin>
    +                  )}
    +                  {clockin.latitude_out > 0 && (
    +                    <LatLongClockin isIn={false} clockin={clockin}>
    +                      <small
    +                        className={`pointer ${
    +                          clockin.distance_out_miles > 0.2 ? "text-danger" : ""
    +                        }`}
    +                      >
    +                        <i className="fas fa-map-marker-alt"></i> Out
    +                      </small>
    +                    </LatLongClockin>
    +                  )}
    +                  {clockin.author != employee.user.profile.id ? (
    +                    <Tooltip
    +                      placement="bottom"
    +                      trigger={["hover"]}
    +                      overlay={<small>Clocked in by a supervisor</small>}
    +                    >
    +                      <i className="fas fa-user-cog text-danger ml-2"></i>
    +                    </Tooltip>
    +                  ) : !moment(payment.created_at).isSame(
    +                      moment(payment.updated_at)
    +                    ) && payment.status === "PENDING" ? (
    +                    <Tooltip
    +                      placement="bottom"
    +                      trigger={["hover"]}
    +                      overlay={<small>Previously updated by supervisor</small>}
    +                    >
    +                      <i className="fas fa-user-edit text-danger ml-2"></i>
    +                    </Tooltip>
    +                  ) : null}
    +                </div>
    +              )}
    +            </div>
    +          }
    +        </td>
    +      )}
    +      <td className="time">
    +        {readOnly ? (
    +          <p>
    +            {approvedTimes.in !== undefined && approvedTimes.in.format("LT")}
    +          </p>
    +        ) : (
    +          <TimePicker
    +            showSecond={false}
    +            defaultValue={approvedTimes.in}
    +            format={TIME_FORMAT}
    +            onChange={(value) => {
    +              if (value && value !== undefined) {
    +                let ended_at = approvedTimes.out;
    +                if (value.isAfter(ended_at))
    +                  ended_at = moment(ended_at).add(1, "days");
    +                if (value && value !== undefined)
    +                  setApprovedTimes({
    +                    ...approvedTimes,
    +                    in: value,
    +                    out: ended_at,
    +                  });
    +              }
    +            }}
    +            value={approvedTimes.in}
    +            use12Hours
    +          />
    +        )}
    +        <small>({shiftStartTime})</small>
    +      </td>
    +      <td className="time">
    +        {readOnly ? (
    +          <p>
    +            {approvedTimes.out !== undefined && approvedTimes.out.format("LT")}
    +          </p>
    +        ) : (
    +          <TimePicker
    +            className={`${
    +              clockin.automatically_closed ? "border border-danger" : ""
    +            }`}
    +            showSecond={false}
    +            defaultValue={approvedTimes.out}
    +            format={TIME_FORMAT}
    +            onChange={(d1) => {
    +              if (d1) {
    +                const starting = approvedTimes.in;
    +                let ended_at = moment(clockin.started_at).set({
    +                  hour: d1.get("hour"),
    +                  minute: d1.get("minute"),
    +                });
    +                if (starting.isAfter(ended_at))
    +                  ended_at = moment(ended_at).add(1, "days");
    +                if (ended_at && ended_at !== undefined)
    +                  setApprovedTimes({ ...approvedTimes, out: ended_at });
    +              }
    +            }}
    +            value={approvedTimes.out}
    +            use12Hours
    +          />
    +        )}
    +        <small>({shiftEndTime})</small>
    +        {shiftNextDay && (
    +          <Tooltip
    +            placement="bottom"
    +            trigger={["hover"]}
    +            overlay={<small>This shift ended on the next day</small>}
    +          >
    +            <i className="fas fa-exclamation-triangle fa-xs mr-2"></i>
    +          </Tooltip>
    +        )}
    +        {clockin.automatically_closed && (
    +          <Tooltip
    +            placement="bottom"
    +            trigger={["hover"]}
    +            overlay={<small>Automatically clocked out</small>}
    +          >
    +            <i className="fas fa-stopwatch text-danger fa-xs mr-2"></i>
    +          </Tooltip>
    +        )}
    +      </td>
    +      <td style={{ minWidth: "75px", maxWidth: "75px" }}>
    +        <p className="mt-1" style={{ marginBottom: "7px" }}>
    +          {clockinHours}
    +        </p>
    +        <small className="d-block my-0">(Plan: {plannedHours})</small>
    +      </td>
    +      {readOnly ? (
    +        <td>{payment.breaktime_minutes} min</td>
    +      ) : (
    +        <td
    +          style={{ minWidth: "75px", maxWidth: "75px" }}
    +          className="text-center"
    +        >
    +          {
    +            <input
    +              type="number"
    +              className="w-100 rounded"
    +              onChange={(e) =>
    +                e.target.value != ""
    +                  ? setBreaktime(Math.abs(parseInt(e.target.value)))
    +                  : setBreaktime(0)
    +              }
    +              value={breaktime}
    +            />
    +          }
    +          <small>minutes</small>
    +        </td>
    +      )}
    +      <td>
    +        <p className="mt-1" style={{ marginBottom: "7px" }}>
    +          {Number(clockInTotalHoursAfterBreak)}
    +        </p>
    +        <small className="d-block my-0">
    +          (${clockInTotalHoursAfterBreakCost})
    +        </small>
    +      </td>
    +      <td>{clockin.shift || !readOnly ? diff : "-"}</td>
    +      <td>
    +        {
    +          <div>
    +            {clockin && clockin.automatically_closed && (
    +              <span style={{ display: "block" }}>{"Forgot to clockout"}</span>
    +            )}
    +            {earlyClockin && (
    +              <span style={{ display: "block" }}>{"Early clockin"}</span>
    +            )}
    +            {earlyClockout && (
    +              <span style={{ display: "block" }}>{"Early clockout"}</span>
    +            )}
    +            {lateClockin && (
    +              <span style={{ display: "block" }}>{"Late clockin"}</span>
    +            )}
    +            {lateClockout && (
    +              <span style={{ display: "block" }}>{"Late clockout"}</span>
    +            )}
    +            {clockin && clockin.distance_in_miles > 0.2 && (
    +              <span style={{ display: "block" }}>
    +                {"Did not clockin on-site"}
    +              </span>
    +            )}
    +            {clockin && clockin.distance_out_miles > 0.2 && (
    +              <span style={{ display: "block" }}>
    +                {"Did not clockout on-site"}
    +              </span>
    +            )}
    +            {Number(diff) > 0.3 && (
    +              <span style={{ display: "block" }}>
    +                {"Worked more than the planned hours"}
    +              </span>
    +            )}
    +            {readOnly && payment.breaktime_minutes > 30 && (
    +              <span style={{ display: "block" }}>{"Long break time"}</span>
    +            )}
    +            {!earlyClockin &&
    +              !earlyClockout &&
    +              !lateClockout &&
    +              !lateClockin &&
    +              !clockin.automatically_closed &&
    +              clockin.distance_in_miles < 0.2 &&
    +              clockin.distance_out_miles < 0.2 &&
    +              Number(diff) < 0.3 &&
    +              readOnly &&
    +              payment.breaktime_minutes < 30 && (
    +                <span style={{ display: "block" }}>{"-"}</span>
    +              )}
    +          </div>
    +        }
    +      </td>
    +
    +      {readOnly ? (
    +        <td className="text-center">
    +          {payment.status === "APPROVED" ? (
    +            <span>
    +              <i className="fas fa-check-circle"></i>
    +            </span>
    +          ) : payment.status === "REJECTED" ? (
    +            <span>
    +              <i className="fas fa-times-circle"></i>
    +            </span>
    +          ) : payment.status === "PAID" ? (
    +            <p className="m-0 p-0">
    +              <span className="badge">paid</span>
    +            </p>
    +          ) : null}
    +          {period.status === "OPEN" &&
    +            (payment.status === "APPROVED" ||
    +              payment.status === "REJECTED") && (
    +              <i
    +                onClick={() => onUndo(payment)}
    +                className="fas fa-undo ml-2 pointer"
    +              ></i>
    +            )}
    +        </td>
    +      ) : (
    +        <td className="">
    +          <Button
    +            color="success"
    +            size="small"
    +            icon="check"
    +            onClick={(value) => {
    +              if (payment.status === "NEW") {
    +                if (shift.id === undefined)
    +                  Notify.error(
    +                    "You need to specify a shift for all the new clockins"
    +                  );
    +                else
    +                  onApprove({
    +                    shift: shift,
    +                    employee: employee,
    +                    clockin: null,
    +                    breaktime_minutes: breaktime,
    +                    regular_hours:
    +                      plannedHours > clockInTotalHoursAfterBreak ||
    +                      plannedHours === 0
    +                        ? clockInTotalHoursAfterBreak
    +                        : plannedHours,
    +                    over_time: diff < 0 ? 0 : diff,
    +                    //
    +                    approved_clockin_time: approvedTimes.in,
    +                    approved_clockout_time: approvedTimes.out,
    +                  });
    +              } else
    +                onApprove({
    +                  breaktime_minutes: breaktime,
    +                  regular_hours:
    +                    plannedHours > clockInTotalHoursAfterBreak ||
    +                    plannedHours === 0
    +                      ? clockInTotalHoursAfterBreak
    +                      : plannedHours,
    +                  over_time: diff < 0 ? 0 : diff,
    +                  shift: shift,
    +                  approved_clockin_time: approvedTimes.in,
    +                  approved_clockout_time: approvedTimes.out,
    +                });
    +            }}
    +          />
    +          <br />
    +          <Button
    +            className="mt-1"
    +            color="danger"
    +            size="small"
    +            icon={payment.status === "NEW" ? "trash" : "times"}
    +            onClick={(value) => onReject({ status: "REJECTED" })}
    +          />
    +        </td>
    +      )}
    +    </tr>
    +  );
    +};
    +PaymentRow.propTypes = {
    +  payment: PropTypes.object,
    +  period: PropTypes.object,
    +  employee: PropTypes.object,
    +  readOnly: PropTypes.bool,
    +  onApprove: PropTypes.func,
    +  onReject: PropTypes.func,
    +  onUndo: PropTypes.func,
    +  shifts: PropTypes.array,
    +  onChange: PropTypes.func,
    +  selection: PropTypes.object,
    +};
    +PaymentRow.defaultProps = {
    +  shifts: [],
    +  period: null,
    +};
    +
    +/**
    + * SelectTimesheet
    + */
    +
    +const filterClockins = (formChanges, formData, onChange) => {
    +  onChange(Object.assign(formChanges, { employees: [], loading: true }));
    +
    +  const query = queryString.stringify({
    +    starting_at: formChanges.starting_at
    +      ? formChanges.starting_at.format("YYYY-MM-DD")
    +      : null,
    +    ending_at: formChanges.ending_at
    +      ? formChanges.ending_at.format("YYYY-MM-DD")
    +      : null,
    +    shift: formData.shift ? formData.shift.id || formData.shift.id : "",
    +  });
    +  search(ENTITIY_NAME, "?" + query).then((data) =>
    +    onChange({ employees: data, loading: false })
    +  );
    +};
    +
    +export const SelectTimesheet = ({
    +  catalog,
    +  formData,
    +  onChange,
    +  onSave,
    +  onCancel,
    +  history,
    +}) => {
    +  const { bar } = useContext(Theme.Context);
    +  const employer = store.getState("current_employer");
    +  const [noMorePeriods, setNoMorePeriods] = useState(false);
    +  const [periods, setPeriods] = useState(formData.periods);
    +  if (
    +    !employer ||
    +    !employer.payroll_configured ||
    +    !moment.isMoment(employer.payroll_period_starting_time)
    +  ) {
    +    return (
    +      <div className="text-center">
    +        <p>Please setup your payroll settings first.</p>
    +        <Button
    +          color="success"
    +          onClick={() => history.push("/payroll/settings")}
    +        >
    +          Setup Payroll Settings
    +        </Button>
    +      </div>
    +    );
    +  }
    +  if (!periods) return "Loading...";
    +  let note = null;
    +  if (periods && periods.length > 0) {
    +    const end = moment(periods[0].ending_at);
    +    end.add(7, "days");
    +    if (end.isBefore(TODAY()))
    +      note = "Payroll was generated until " + end.format("L");
    +  }
    +  // eslint-disable-next-line no-console
    +
    +  const payments = periods.map((e) => e.payments);
    +  // .filter((value, index, self) => self.indexOf(value) === index);
    +  function totalEmployees(payments) {
    +    if (payments) {
    +      var employees = payments.map((e) => e.employee.id);
    +      var uniqueEmployees = employees.filter(function (v, i) {
    +        return i == employees.lastIndexOf(v);
    +      });
    +
    +      return "Employees: " + uniqueEmployees.length + " | ";
    +    } else return "";
    +  }
    +
    +  return (
    +    <div>
    +      <div className="top-bar">
    +        <Button
    +          icon="sync"
    +          color="primary"
    +          size="small"
    +          rounded={true}
    +          onClick={() =>
    +            processPendingPayrollPeriods().then((_periods) =>
    +              onChange(setPeriods(periods.concat(_periods)))
    +            )
    +          }
    +          note={note}
    +          notePosition="left"
    +        />
    +      </div>
    +      <div className="row mb-4">
    +        <div className="col-12">
    +          <h2 className="mt-1">Select a timesheet:</h2>
    +          <ul
    +            className="scroll"
    +            style={{
    +              maxHeight: "800px",
    +              overflowY: "auto",
    +              padding: "10px",
    +              margin: "-10px",
    +            }}
    +          >
    +            <div>
    +              {periods.length === 0 && (
    +                <p>
    +                  No previous payroll periods have been found. Please try
    +                  clicking the icon above.
    +                </p>
    +              )}
    +              {periods.map((p) => (
    +                <GenericCard
    +                  key={p.id}
    +                  hover={true}
    +                  className="pr-2"
    +                  onClick={() => history.push(`/payroll/period/${p.id}`)}
    +                >
    +                  <div className="avatar text-center pt-1 bg-transparent">
    +                    {p.status === "FINALIZED" || p.status === "PAID" ? (
    +                      <i className="fas fa-check-circle"></i>
    +                    ) : p.status === "OPEN" ? (
    +                      <i className="far fa-circle"></i>
    +                    ) : (
    +                      ""
    +                    )}
    +                  </div>
    +                  From {moment(p.starting_at).format("MMM DD, YYYY")} to{" "}
    +                  {moment(p.ending_at).format("MMM DD, YYYY")}
    +                  <p className="my-0">
    +                    <small
    +                      className={`badge ${
    +                        p.total_payments > 0 ? "badge-secondary" : "badge-info"
    +                      }`}
    +                    >
    +                      {"Employees: " +
    +                        p.employee_count +
    +                        " | Payments: " +
    +                        p.total_payments}{" "}
    +                    </small>
    +                  </p>
    +                </GenericCard>
    +              ))}
    +              {!noMorePeriods &&
    +              Array.isArray(periods) &&
    +              periods.length > 0 ? (
    +                <div className="row text-center w-100 mt-3">
    +                  <div className="col">
    +                    <Button
    +                      onClick={() => {
    +                        const PAGINATION_MONTHS_LENGTH = 1;
    +                        searchMe(
    +                          `payroll-periods`,
    +                          `?end=${moment(
    +                            periods[periods.length - 1]["ending_at"]
    +                          )
    +                            .subtract(1, "weeks")
    +                            .format("YYYY-MM-DD")}&start=${moment(
    +                            periods[periods.length - 1]["starting_at"]
    +                          )
    +                            .subtract(PAGINATION_MONTHS_LENGTH, "months")
    +                            .format("YYYY-MM-DD")}`,
    +                          formData.periods
    +                        ).then((newPeriods) => {
    +                          if (
    +                            Array.isArray(newPeriods) &&
    +                            newPeriods.length > 0 &&
    +                            newPeriods.length > periods.length
    +                          ) {
    +                            setPeriods(newPeriods);
    +                          } else setNoMorePeriods(true);
    +                        });
    +                      }}
    +                    >
    +                      Load More
    +                    </Button>
    +                  </div>
    +                </div>
    +              ) : null}
    +            </div>
    +          </ul>
    +        </div>
    +      </div>
    +    </div>
    +  );
    +};
    +SelectTimesheet.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  history: PropTypes.object.isRequired,
    +  onChange: PropTypes.func,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +export const SelectShiftPeriod = ({
    +  catalog,
    +  formData,
    +  onChange,
    +  onSave,
    +  onCancel,
    +  history,
    +}) => {
    +  const { bar } = useContext(Theme.Context);
    +
    +  let note = null;
    +  if (formData.periods.length > 0) {
    +    const end = moment(formData.periods[0].ending_at);
    +    end.add(7, "days");
    +    if (end.isBefore(TODAY()))
    +      note = "Payroll was generated until " + end.format("MM dd");
    +  }
    +  return (
    +    <div>
    +      <div className="top-bar">
    +        <Button
    +          icon="sync"
    +          color="primary"
    +          size="small"
    +          rounded={true}
    +          onClick={() => null}
    +          note={note}
    +          notePosition="left"
    +        />
    +      </div>
    +      <div className="row">
    +        <div className="col-12">
    +          <div>
    +            <h2 className="mt-1">Select a payment period:</h2>
    +            <Select
    +              className="select-shifts"
    +              isMulti={false}
    +              value={{
    +                value: null,
    +                label: `Select a payment period`,
    +              }}
    +              defaultValue={{
    +                value: null,
    +                label: `Select a payment period`,
    +              }}
    +              components={{ Option: ShiftOption, SingleValue: ShiftOption }}
    +              onChange={(selectedOption) =>
    +                searchMe("payment", `?period=${selectedOption.id}`).then(
    +                  (payments) => {
    +                    onChange({
    +                      selectedPayments: payments,
    +                      selectedPeriod: selectedOption,
    +                    });
    +                    history.push(`/payroll/period/${selectedOption.id}`);
    +                  }
    +                )
    +              }
    +              options={[
    +                {
    +                  value: null,
    +                  label: `Select a payment period`,
    +                },
    +              ].concat(formData.periods)}
    +            />
    +          </div>
    +        </div>
    +        {formData &&
    +        typeof formData.selectedPayments != "undefined" &&
    +        formData.selectedPayments.length > 0 ? (
    +          <div className="col-12 mt-3">
    +            <ul>
    +              {formData.selectedPayments.map((payment, i) => {
    +                return (
    +                  <EmployeeExtendedCard
    +                    key={i}
    +                    employee={payment.employee}
    +                    showFavlist={false}
    +                    showButtonsOnHover={false}
    +                    onClick={() => {
    +                      bar.show({
    +                        to:
    +                          `/payroll/period/${formData.selectedPeriod.id}?` +
    +                          queryString.stringify({
    +                            talent_id: payment.employee.id,
    +                          }),
    +                      });
    +                    }}
    +                  >
    +                    {payment.status === "PENDING" ? (
    +                      <span>
    +                        {" "}
    +                        pending{" "}
    +                        <i className="fas fa-exclamation-triangle mr-2"></i>
    +                      </span>
    +                    ) : payment.status === "PAID" ? (
    +                      <span>
    +                        {" "}
    +                        unpaid <i className="fas fa-dollar-sign mr-2"></i>
    +                      </span>
    +                    ) : (
    +                      <i className="fas fa-check-circle mr-2"></i>
    +                    )}
    +                  </EmployeeExtendedCard>
    +                );
    +              })}
    +            </ul>
    +          </div>
    +        ) : typeof formData.loading !== "undefined" && formData.loading ? (
    +          <div className="col-12 mt-3 text-center">Loading...</div>
    +        ) : (
    +          <div className="col-12 mt-3 text-center">
    +            No talents found for this period or shift
    +          </div>
    +        )}
    +      </div>
    +    </div>
    +  );
    +};
    +SelectShiftPeriod.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  history: PropTypes.object.isRequired,
    +  onChange: PropTypes.func,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +export class PayrollRating extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      ratings: [],
    +      employer: store.getState("current_employer"),
    +      payrollPeriods: [],
    +      payments: [],
    +      singlePayrollPeriod: null,
    +      reviews: [],
    +    };
    +  }
    +
    +  componentDidMount() {
    +    this.subscribe(store, "current_employer", (employer) => {
    +      this.setState({ employer });
    +    });
    +
    +    const payrollPeriods = store.getState("payroll-periods");
    +    this.subscribe(store, "payroll-periods", (_payrollPeriods) => {
    +      // this.updatePayrollPeriod(_payrollPeriods);
    +      //if(!this.state.singlePayrollPeriod) this.getSinglePeriod(this.props.match.params.period_id, payrollPeriods);
    +    });
    +    // if (!payrollPeriods) {
    +    //     this.getSinglePeriod(this.props.match.params.period_id, payrollPeriods);
    +    // }
    +    // else {
    +    //     this.updatePayrollPeriod(payrollPeriods);
    +    //     this.getSinglePeriod(this.props.match.params.period_id, payrollPeriods);
    +
    +    // }
    +    if (this.props.match.params.period_id !== undefined)
    +      fetchSingle("payroll-periods", this.props.match.params.period_id).then(
    +        (_period) => {
    +          this.defaultRatings(_period).then((res) =>
    +            this.setState({
    +              ratings: res,
    +              singlePayrollPeriod: _period,
    +              payments: _period.payments,
    +            })
    +          );
    +        }
    +      );
    +
    +    this.removeHistoryListener = this.props.history.listen((data) => {
    +      const period = /\/payroll\/period\/(\d+)/gm;
    +      const periodMatches = period.exec(data.pathname);
    +      // const search = /\?talent_id=(\d+)/gm;
    +      // const searchMatches = search.exec(data.search);
    +      if (periodMatches) this.getSinglePeriod(periodMatches[1]);
    +    });
    +    return () => {
    +      payrollPeriods.unsubscribe();
    +    };
    +  }
    +
    +  defaultRatings(singlePeriod) {
    +    return new Promise((resolve, reject) => {
    +      if (!singlePeriod) resolve(null);
    +      const shiftList = singlePeriod.payments
    +        .map((s) => s.shift.id)
    +        .filter((v, i, s) => s.indexOf(v) === i)
    +        .join(",");
    +      searchMe("ratings", "?shifts=" + shiftList)
    +        .then((previousRatings) => {
    +          let ratings = {};
    +          singlePeriod.payments.forEach((pay) => {
    +            if (typeof ratings[pay.employee.id] === "undefined")
    +              ratings[pay.employee.id] = {
    +                employee: pay.employee,
    +                shifts: [],
    +                rating: null,
    +                comments: "",
    +              };
    +            const hasPreviousShift = previousRatings.find(
    +              (r) =>
    +                r.shift &&
    +                pay.shift &&
    +                r.shift.id === pay.shift.id &&
    +                r.employee === pay.employee.id
    +            );
    +            if (!hasPreviousShift && pay.shift)
    +              ratings[pay.employee.id].shifts.push(pay.shift.id);
    +          });
    +
    +          resolve(Object.values(ratings));
    +        })
    +        .catch((error) =>
    +          Notify.error("There was an error fetching the ratings for the shift")
    +        );
    +    });
    +  }
    +
    +  getSinglePeriod(periodId, payrollPeriods) {
    +    if (typeof periodId !== "undefined") {
    +      if (!payrollPeriods) fetchSingle("payroll-periods", periodId);
    +      else {
    +        const singlePayrollPeriod = payrollPeriods.find(
    +          (pp) => pp.id == periodId
    +        );
    +        this.defaultRatings(singlePayrollPeriod).then((payments) =>
    +          this.setState({ singlePayrollPeriod, payments })
    +        );
    +      }
    +    }
    +  }
    +
    +  updatePayrollPeriod(payrollPeriods) {
    +    if (payrollPeriods == null) return;
    +
    +    let singlePayrollPeriod = null;
    +    if (typeof this.props.match.params.period_id !== "undefined") {
    +      singlePayrollPeriod = payrollPeriods.find(
    +        (pp) => pp.id == this.props.match.params.period_id
    +      );
    +    }
    +
    +    this.defaultRatings(singlePayrollPeriod).then((payments) =>
    +      this.setState({
    +        payrollPeriods,
    +        singlePayrollPeriod: singlePayrollPeriod || null,
    +        payments,
    +      })
    +    );
    +  }
    +
    +  render() {
    +    if (!this.state.employer) return "Loading...";
    +    else if (
    +      !this.state.employer.payroll_configured ||
    +      !moment.isMoment(this.state.employer.payroll_period_starting_time)
    +    ) {
    +      return (
    +        <div className="p-1 listcontents text-center">
    +          <h3>Please setup your payroll settings first.</h3>
    +          <Button
    +            color="success"
    +            onClick={() => this.props.history.push("/payroll-settings")}
    +          >
    +            Setup Payroll Settings
    +          </Button>
    +        </div>
    +      );
    +    }
    +
    +    return (
    +      <div className="p-1 listcontents">
    +        {/* {this.state.singlePayrollPeriod && this.state.singlePayrollPeriod.status == "FINALIZED" &&
    +                <Redirect from={'/payroll/rating/' + this.state.singlePayrollPeriod.id} to={'/payroll/report/' + this.state.singlePayrollPeriod.id} />
    +            } */}
    +        <Theme.Consumer>
    +          {({ bar }) => (
    +            <span>
    +              {!this.state.ratings ? (
    +                ""
    +              ) : this.state.singlePayrollPeriod ? (
    +                <div>
    +                  <p className="text-left">
    +                    <h2 className="mb-0">
    +                      Please rate the talents for this period (optional):
    +                    </h2>
    +                    <h4 className="mt-0">
    +                      {this.state.singlePayrollPeriod.label || ""}
    +                    </h4>
    +                  </p>
    +                </div>
    +              ) : (
    +                <p>No payments to review for this period</p>
    +              )}
    +
    +              {this.state.ratings.map((list, i) => {
    +                if (list.employee)
    +                  return (
    +                    <div className="row list-card" key={i}>
    +                      <div className="col-1 my-auto">
    +                        <Avatar url={list.employee.user.profile.picture} />
    +                      </div>
    +                      <div className="col-3 my-auto">
    +                        <span>
    +                          {list.employee.user.first_name +
    +                            " " +
    +                            list.employee.user.last_name}
    +                        </span>
    +                      </div>
    +                      <div className="col my-auto">
    +                        <StarRating
    +                          onClick={(e) => {
    +                            let newRating = Object.assign({}, this.state);
    +                            newRating.ratings[i].rating = e;
    +                            this.setState({
    +                              newRating,
    +                            });
    +                          }}
    +                          onHover={() => null}
    +                          direction="right"
    +                          fractions={2}
    +                          quiet={false}
    +                          readonly={false}
    +                          totalSymbols={5}
    +                          value={list.rating}
    +                          placeholderValue={0}
    +                          placeholderRating={Number(0)}
    +                          emptySymbol="far fa-star md"
    +                          fullSymbol="fas fa-star"
    +                          placeholderSymbol={"fas fa-star"}
    +                        />
    +                      </div>
    +                      <div className="col-6 my-auto">
    +                        <TextareaAutosize
    +                          style={{ width: "100%" }}
    +                          placeholder="Any additional comments?"
    +                          value={list.comments}
    +                          onChange={(e) => {
    +                            let newComment = Object.assign({}, this.state);
    +                            newComment.ratings[i].comments = e.target.value;
    +                            this.setState({
    +                              newComment,
    +                            });
    +                          }}
    +                        />
    +                      </div>
    +                    </div>
    +                  );
    +              })}
    +
    +              <div className="btn-bar mt-3 pt-3">
    +                <button
    +                  type="button"
    +                  className="btn btn-primary"
    +                  onClick={() => {
    +                    const unrated = this.state.ratings.find(
    +                      (p) => p.rating == null && p.shifts.length > 0
    +                    );
    +                    const rated = [].concat.apply(
    +                      [],
    +                      this.state.ratings
    +                        .filter((s) => s.shifts.length > 0 && s.rating)
    +                        .map((p) => {
    +                          if (p.shifts.length > 1) {
    +                            return p.shifts.map((s) => ({
    +                              employee: p.employee.id,
    +                              shift: s,
    +                              rating: p.rating,
    +                              comments: p.comments,
    +                            }));
    +                          } else {
    +                            return [
    +                              {
    +                                employee: p.employee.id,
    +                                shift: p.shifts[0],
    +                                rating: p.rating,
    +                                comments: p.comments,
    +                                // payment: p.id
    +                              },
    +                            ];
    +                          }
    +                        })
    +                    );
    +                    // if (unrated) Notify.error("There are still some employees that need to be rated");
    +                    // else {
    +                    create("ratings", rated).then((res) => {
    +                      this.props.history.push(
    +                        "/payroll/report/" + this.state.singlePayrollPeriod.id
    +                      );
    +                      // if (res)update('payroll-periods', Object.assign(this.state.singlePayrollPeriod, { status: 'FINALIZED' }));
    +                    });
    +                    // .then((resp) => { this.props.history.push('/payroll/report/' + this.state.singlePayrollPeriod.id); })
    +                    // .catch(e => Notify.error(e.message || e));
    +
    +                    // }
    +                  }}
    +                >
    +                  Take me to the Payroll Report
    +                </button>
    +              </div>
    +            </span>
    +          )}
    +        </Theme.Consumer>
    +      </div>
    +    );
    +  }
    +}
    +export class PayrollReport extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      employer: store.getState("current_employer"),
    +      payrollPeriods: [],
    +      payments: [],
    +      paymentInfo: [],
    +      singlePayrollPeriod: null,
    +    };
    +  }
    +
    +  componentDidMount() {
    +    this.subscribe(store, "current_employer", (employer) => {
    +      this.setState({ employer });
    +    });
    +    this.subscribe(store, "payroll-period-payments", (paymentInfo) => {
    +      const payrollPaymentsWithDeductible = paymentInfo.payments.map((e, i) => {
    +        var temp = Object.assign({}, e);
    +        if (e.employee.w4_year == 2019 || !e.employee.w4_year) {
    +          if (e.employee.filing_status == "SINGLE") {
    +            let federalWithholding = 0;
    +
    +            if (Number(temp.earnings) < 73) federalWithholding = 0;
    +            else if (Number(temp.earnings) > 73 && Number(temp.earnings) < 263)
    +              federalWithholding = Math.round(
    +                0 + (Number(temp.earnings) - 73) * 0.1
    +              );
    +            else if (Number(temp.earnings) > 263 && Number(temp.earnings) < 845)
    +              federalWithholding = Math.round(
    +                19.0 + (Number(temp.earnings) - 263) * 0.12
    +              );
    +            else if (
    +              Number(temp.earnings) > 845 &&
    +              Number(temp.earnings) < 1718
    +            )
    +              federalWithholding = Math.round(
    +                88.84 + (Number(temp.earnings) - 845) * 0.22
    +              );
    +            else if (
    +              Number(temp.earnings) > 1718 &&
    +              Number(temp.earnings) < 3213
    +            )
    +              federalWithholding = Math.round(
    +                280.9 + (Number(temp.earnings) - 1718) * 0.24
    +              );
    +            else if (
    +              Number(temp.earnings) > 3213 &&
    +              Number(temp.earnings) < 4061
    +            )
    +              federalWithholding = Math.round(
    +                639.7 + (Number(temp.earnings) - 3213) * 0.32
    +              );
    +            else if (
    +              Number(temp.earnings) > 4061 &&
    +              Number(temp.earnings) < 10042
    +            )
    +              federalWithholding = Math.round(
    +                911.06 + (Number(temp.earnings) - 4061) * 0.35
    +              );
    +            else if (Number(temp.earnings) > 10042)
    +              federalWithholding = Math.round(
    +                3004.41 + (Number(temp.earnings) - 10042) * 0.37
    +              );
    +            else federalWithholding = 0;
    +
    +            temp.deduction_list.push({
    +              name: "Federal Withholding",
    +              amount: federalWithholding,
    +            });
    +            temp["deductions"] =
    +              Math.round((temp["deductions"] + federalWithholding) * 100) / 100;
    +            temp["amount"] =
    +              Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +          } else if (e.employee.filing_status == "MARRIED_JOINTLY") {
    +            let federalWithholding = 0;
    +
    +            if (Number(temp.earnings) < 229) federalWithholding = 0;
    +            else if (Number(temp.earnings) > 229 && Number(temp.earnings) < 609)
    +              federalWithholding = Math.round(
    +                0 + (Number(temp.earnings) - 229) * 0.1
    +              );
    +            else if (
    +              Number(temp.earnings) > 609 &&
    +              Number(temp.earnings) < 1772
    +            )
    +              federalWithholding = Math.round(
    +                38.0 + (Number(temp.earnings) - 609) * 0.12
    +              );
    +            else if (
    +              Number(temp.earnings) > 1772 &&
    +              Number(temp.earnings) < 3518
    +            )
    +              federalWithholding = Math.round(
    +                177.56 + (Number(temp.earnings) - 1772) * 0.22
    +              );
    +            else if (
    +              Number(temp.earnings) > 3518 &&
    +              Number(temp.earnings) < 6510
    +            )
    +              federalWithholding = Math.round(
    +                561.68 + (Number(temp.earnings) - 3518) * 0.24
    +              );
    +            else if (
    +              Number(temp.earnings) > 6510 &&
    +              Number(temp.earnings) < 8204
    +            )
    +              federalWithholding = Math.round(
    +                1279.76 + (Number(temp.earnings) - 6510) * 0.32
    +              );
    +            else if (
    +              Number(temp.earnings) > 8204 &&
    +              Number(temp.earnings) < 12191
    +            )
    +              federalWithholding = Math.round(
    +                1,
    +                821.84 + (Number(temp.earnings) - 8204) * 0.35
    +              );
    +            else if (Number(temp.earnings) > 12191)
    +              federalWithholding = Math.round(
    +                3217.29 + (Number(temp.earnings) - 12191) * 0.37
    +              );
    +            else federalWithholding = 0;
    +
    +            temp.deduction_list.push({
    +              name: "Federal Withholding",
    +              amount: federalWithholding,
    +            });
    +            temp["deductions"] =
    +              Math.round((temp["deductions"] + federalWithholding) * 100) / 100;
    +            temp["amount"] =
    +              Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +          }
    +        } else if (e.employee.w4_year == 2020) {
    +          if (e.employee.filing_status == "MARRIED_JOINTLY") {
    +            if (!e.step2c_checked) {
    +              let federalWithholding = 0;
    +
    +              if (Number(temp.earnings) < 477) federalWithholding = 0;
    +              else if (
    +                Number(temp.earnings) > 477 &&
    +                Number(temp.earnings) < 857
    +              )
    +                federalWithholding = Math.round(
    +                  0 + (Number(temp.earnings) - 477) * 0.1
    +                );
    +              else if (
    +                Number(temp.earnings) > 857 &&
    +                Number(temp.earnings) < 2020
    +              )
    +                federalWithholding = Math.round(
    +                  38.0 + (Number(temp.earnings) - 857) * 0.12
    +                );
    +              else if (
    +                Number(temp.earnings) > 2020 &&
    +                Number(temp.earnings) < 3766
    +              )
    +                federalWithholding = Math.round(
    +                  177.56 + (Number(temp.earnings) - 2020) * 0.22
    +                );
    +              else if (
    +                Number(temp.earnings) > 3766 &&
    +                Number(temp.earnings) < 6758
    +              )
    +                federalWithholding = Math.round(
    +                  561.68 + (Number(temp.earnings) - 3766) * 0.24
    +                );
    +              else if (
    +                Number(temp.earnings) > 6758 &&
    +                Number(temp.earnings) < 8452
    +              )
    +                federalWithholding = Math.round(
    +                  1279.76 + (Number(temp.earnings) - 6758) * 0.32
    +                );
    +              else if (
    +                Number(temp.earnings) > 8452 &&
    +                Number(temp.earnings) < 12439
    +              )
    +                federalWithholding = Math.round(
    +                  1821.84 + (Number(temp.earnings) - 8452) * 0.35
    +                );
    +              else if (Number(temp.earnings) > 12439)
    +                federalWithholding = Math.round(
    +                  3004.41 + (Number(temp.earnings) - 12439) * 0.37
    +                );
    +              else federalWithholding = 0;
    +              temp.deduction_list.push({
    +                name: "Federal Withholding",
    +                amount: federalWithholding,
    +              });
    +              temp["deductions"] =
    +                Math.round((temp["deductions"] + federalWithholding) * 100) /
    +                100;
    +              temp["amount"] =
    +                Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +            } else {
    +              let federalWithholding = 0;
    +
    +              if (Number(temp.earnings) < 238) federalWithholding = 0;
    +              else if (
    +                Number(temp.earnings) > 238 &&
    +                Number(temp.earnings) < 428
    +              )
    +                federalWithholding = Math.round(
    +                  0 + (Number(temp.earnings) - 238) * 0.1
    +                );
    +              else if (
    +                Number(temp.earnings) > 428 &&
    +                Number(temp.earnings) < 1010
    +              )
    +                federalWithholding = Math.round(
    +                  19.0 + (Number(temp.earnings) - 428) * 0.12
    +                );
    +              else if (
    +                Number(temp.earnings) > 1010 &&
    +                Number(temp.earnings) < 1883
    +              )
    +                federalWithholding = Math.round(
    +                  88.84 + (Number(temp.earnings) - 1010) * 0.22
    +                );
    +              else if (
    +                Number(temp.earnings) > 1883 &&
    +                Number(temp.earnings) < 3379
    +              )
    +                federalWithholding = Math.round(
    +                  280.9 + (Number(temp.earnings) - 1883) * 0.24
    +                );
    +              else if (
    +                Number(temp.earnings) > 3379 &&
    +                Number(temp.earnings) < 4226
    +              )
    +                federalWithholding = Math.round(
    +                  639.94 + (Number(temp.earnings) - 3379) * 0.32
    +                );
    +              else if (
    +                Number(temp.earnings) > 4226 &&
    +                Number(temp.earnings) < 6220
    +              )
    +                federalWithholding = Math.round(
    +                  910.98 + (Number(temp.earnings) - 4226) * 0.35
    +                );
    +              else if (Number(temp.earnings) > 6220)
    +                federalWithholding = Math.round(
    +                  1608.88 + (Number(temp.earnings) - 6220) * 0.37
    +                );
    +              else federalWithholding = 0;
    +              temp.deduction_list.push({
    +                name: "Federal Withholding",
    +                amount: federalWithholding,
    +              });
    +              temp["deductions"] =
    +                Math.round((temp["deductions"] + federalWithholding) * 100) /
    +                100;
    +              temp["amount"] =
    +                Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +            }
    +          } else if (
    +            e.employee.filing_status == "SINGLE" ||
    +            e.employee.filing_status == "MARRIED_SEPARATELY"
    +          ) {
    +            if (e.step2c_checked) {
    +              let federalWithholding = 0;
    +
    +              if (Number(temp.earnings) < 119) federalWithholding = 0;
    +              else if (
    +                Number(temp.earnings) > 119 &&
    +                Number(temp.earnings) < 214
    +              )
    +                federalWithholding = Math.round(
    +                  0 + (Number(temp.earnings) - 119) * 0.1
    +                );
    +              else if (
    +                Number(temp.earnings) > 214 &&
    +                Number(temp.earnings) < 505
    +              )
    +                federalWithholding = Math.round(
    +                  9.5 + (Number(temp.earnings) - 214) * 0.12
    +                );
    +              else if (
    +                Number(temp.earnings) > 505 &&
    +                Number(temp.earnings) < 942
    +              )
    +                federalWithholding = Math.round(
    +                  44.42 + (Number(temp.earnings) - 505) * 0.22
    +                );
    +              else if (
    +                Number(temp.earnings) > 942 &&
    +                Number(temp.earnings) < 1689
    +              )
    +                federalWithholding = Math.round(
    +                  140.56 + (Number(temp.earnings) - 942) * 0.24
    +                );
    +              else if (
    +                Number(temp.earnings) > 1689 &&
    +                Number(temp.earnings) < 2113
    +              )
    +                federalWithholding = Math.round(
    +                  319.84 + (Number(temp.earnings) - 1689) * 0.32
    +                );
    +              else if (
    +                Number(temp.earnings) > 2113 &&
    +                Number(temp.earnings) < 5104
    +              )
    +                federalWithholding = Math.round(
    +                  455.52 + (Number(temp.earnings) - 2113) * 0.35
    +                );
    +              else if (Number(temp.earnings) > 5104)
    +                federalWithholding = Math.round(
    +                  1502.37 + (Number(temp.earnings) - 5104) * 0.37
    +                );
    +              else federalWithholding = 0;
    +              temp.deduction_list.push({
    +                name: "Federal Withholding",
    +                amount: federalWithholding,
    +              });
    +              temp["deductions"] =
    +                Math.round((temp["deductions"] + federalWithholding) * 100) /
    +                100;
    +              temp["amount"] =
    +                Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +            } else {
    +              let federalWithholding = 0;
    +
    +              if (Number(temp.earnings) < 238) federalWithholding = 0;
    +              else if (
    +                Number(temp.earnings) > 238 &&
    +                Number(temp.earnings) < 428
    +              )
    +                federalWithholding = Math.round(
    +                  0 + (Number(temp.earnings) - 238) * 0.1
    +                );
    +              else if (
    +                Number(temp.earnings) > 428 &&
    +                Number(temp.earnings) < 1010
    +              )
    +                federalWithholding = Math.round(
    +                  19.0 + (Number(temp.earnings) - 428) * 0.12
    +                );
    +              else if (
    +                Number(temp.earnings) > 1010 &&
    +                Number(temp.earnings) < 1883
    +              )
    +                federalWithholding = Math.round(
    +                  88.84 + (Number(temp.earnings) - 1010) * 0.22
    +                );
    +              else if (
    +                Number(temp.earnings) > 1883 &&
    +                Number(temp.earnings) < 3379
    +              )
    +                federalWithholding = Math.round(
    +                  280.9 + (Number(temp.earnings) - 1883) * 0.24
    +                );
    +              else if (
    +                Number(temp.earnings) > 3379 &&
    +                Number(temp.earnings) < 4226
    +              )
    +                federalWithholding = Math.round(
    +                  639.94 + (Number(temp.earnings) - 3379) * 0.32
    +                );
    +              else if (
    +                Number(temp.earnings) > 4226 &&
    +                Number(temp.earnings) < 10208
    +              )
    +                federalWithholding = Math.round(
    +                  910.98 + (Number(temp.earnings) - 4226) * 0.35
    +                );
    +              else if (Number(temp.earnings) > 10208)
    +                federalWithholding = Math.round(
    +                  3004.68 + (Number(temp.earnings) - 10208) * 0.37
    +                );
    +              else federalWithholding = 0;
    +              temp.deduction_list.push({
    +                name: "Federal Withholding",
    +                amount: federalWithholding,
    +              });
    +              temp["deductions"] =
    +                Math.round((temp["deductions"] + federalWithholding) * 100) /
    +                100;
    +              temp["amount"] =
    +                Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +            }
    +          } else if (e.employee.filing_status == "HEAD") {
    +            if (e.step2c_checked) {
    +              let federalWithholding = 0;
    +
    +              if (Number(temp.earnings) < 179) federalWithholding = 0;
    +              else if (
    +                Number(temp.earnings) > 179 &&
    +                Number(temp.earnings) < 315
    +              )
    +                federalWithholding = Math.round(
    +                  0 + (Number(temp.earnings) - 179) * 0.1
    +                );
    +              else if (
    +                Number(temp.earnings) > 315 &&
    +                Number(temp.earnings) < 696
    +              )
    +                federalWithholding = Math.round(
    +                  13.6 + (Number(temp.earnings) - 315) * 0.12
    +                );
    +              else if (
    +                Number(temp.earnings) > 696 &&
    +                Number(temp.earnings) < 1001
    +              )
    +                federalWithholding = Math.round(
    +                  59.32 + (Number(temp.earnings) - 696) * 0.22
    +                );
    +              else if (
    +                Number(temp.earnings) > 1001 &&
    +                Number(temp.earnings) < 1750
    +              )
    +                federalWithholding = Math.round(
    +                  126.42 + (Number(temp.earnings) - 1001) * 0.24
    +                );
    +              else if (
    +                Number(temp.earnings) > 1750 &&
    +                Number(temp.earnings) < 2173
    +              )
    +                federalWithholding = Math.round(
    +                  306.18 + (Number(temp.earnings) - 1750) * 0.32
    +                );
    +              else if (
    +                Number(temp.earnings) > 2173 &&
    +                Number(temp.earnings) < 5164
    +              )
    +                federalWithholding = Math.round(
    +                  441.54 + (Number(temp.earnings) - 2173) * 0.35
    +                );
    +              else if (Number(temp.earnings) > 5164)
    +                federalWithholding = Math.round(
    +                  1488.39 + (Number(temp.earnings) - 5164) * 0.37
    +                );
    +              else federalWithholding = 0;
    +              temp.deduction_list.push({
    +                name: "Federal Withholding",
    +                amount: federalWithholding,
    +              });
    +              temp["deductions"] =
    +                Math.round((temp["deductions"] + federalWithholding) * 100) /
    +                100;
    +              temp["amount"] =
    +                Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +            } else {
    +              let federalWithholding = 0;
    +
    +              if (Number(temp.earnings) < 359) federalWithholding = 0;
    +              else if (
    +                Number(temp.earnings) > 359 &&
    +                Number(temp.earnings) < 630
    +              )
    +                federalWithholding = Math.round(
    +                  0 + (Number(temp.earnings) - 359) * 0.1
    +                );
    +              else if (
    +                Number(temp.earnings) > 630 &&
    +                Number(temp.earnings) < 1391
    +              )
    +                federalWithholding = Math.round(
    +                  27.0 + (Number(temp.earnings) - 630) * 0.12
    +                );
    +              else if (
    +                Number(temp.earnings) > 1391 &&
    +                Number(temp.earnings) < 2003
    +              )
    +                federalWithholding = Math.round(
    +                  118.42 + (Number(temp.earnings) - 1391) * 0.22
    +                );
    +              else if (
    +                Number(temp.earnings) > 2003 &&
    +                Number(temp.earnings) < 3499
    +              )
    +                federalWithholding = Math.round(
    +                  253.06 + (Number(temp.earnings) - 2003) * 0.24
    +                );
    +              else if (
    +                Number(temp.earnings) > 3499 &&
    +                Number(temp.earnings) < 4346
    +              )
    +                federalWithholding = Math.round(
    +                  612.1 + (Number(temp.earnings) - 3499) * 0.32
    +                );
    +              else if (
    +                Number(temp.earnings) > 4346 &&
    +                Number(temp.earnings) < 10328
    +              )
    +                federalWithholding = Math.round(
    +                  883.14 + (Number(temp.earnings) - 4346) * 0.35
    +                );
    +              else if (Number(temp.earnings) > 10328)
    +                federalWithholding = Math.round(
    +                  2976.84 + (Number(temp.earnings) - 10328) * 0.37
    +                );
    +              else federalWithholding = 0;
    +              temp.deduction_list.push({
    +                name: "Federal Withholding",
    +                amount: federalWithholding,
    +              });
    +              temp["deductions"] =
    +                Math.round((temp["deductions"] + federalWithholding) * 100) /
    +                100;
    +              temp["amount"] =
    +                Math.round((temp["earnings"] - temp["deductions"]) * 100) / 100;
    +            }
    +          }
    +        }
    +        return temp;
    +      });
    +      let newPaymentInfo = paymentInfo;
    +      newPaymentInfo["payments"] = payrollPaymentsWithDeductible;
    +      this.setState({ paymentInfo });
    +    });
    +    this.subscribe(store, "employee-payment", () => {
    +      fetchPeyrollPeriodPayments(this.state.singlePayrollPeriod.id);
    +    });
    +    const payrollPeriods = store.getState("payroll-periods");
    +    this.subscribe(store, "payroll-periods", (_payrollPeriods) => {
    +      console.log(_payrollPeriods);
    +    });
    +
    +    this.updatePayrollPeriod(payrollPeriods);
    +    this.getSinglePeriod(this.props.match.params.period_id, payrollPeriods);
    +
    +    this.removeHistoryListener = this.props.history.listen((data) => {
    +      const period = /\/payroll\/period\/(\d+)/gm;
    +      const periodMatches = period.exec(data.pathname);
    +      // const search = /\?talent_id=(\d+)/gm;
    +      // const searchMatches = search.exec(data.search);
    +      if (periodMatches) this.getSinglePeriod(periodMatches[1]);
    +    });
    +  }
    +
    +  groupPayments(singlePeriod) {
    +    if (!singlePeriod) return null;
    +
    +    let groupedPayments = {};
    +
    +    if (singlePeriod.payments) {
    +      singlePeriod.payments.forEach((pay) => {
    +        if (typeof groupedPayments[pay.employee.id] === "undefined") {
    +          groupedPayments[pay.employee.id] = {
    +            employee: pay.employee,
    +            payments: [],
    +          };
    +        }
    +        groupedPayments[pay.employee.id].payments.push(pay);
    +      });
    +    }
    +
    +    return Object.values(groupedPayments);
    +  }
    +
    +  getSinglePeriod(periodId, payrollPeriods) {
    +    if (typeof periodId !== "undefined") {
    +      if (!payrollPeriods || !this.state.singlePayrollPeriod)
    +        fetchSingle("payroll-periods", periodId).then((period) => {
    +          this.setState(
    +            {
    +              singlePayrollPeriod: period,
    +              payments: this.groupPayments(period),
    +            },
    +            () => {
    +              fetchPeyrollPeriodPayments(this.state.singlePayrollPeriod.id);
    +            }
    +          );
    +        });
    +      else {
    +        const singlePayrollPeriod = payrollPeriods.find(
    +          (pp) => pp.id == periodId
    +        );
    +        this.setState(
    +          {
    +            singlePayrollPeriod,
    +            payments: this.groupPayments(singlePayrollPeriod),
    +          },
    +          () => {
    +            fetchPeyrollPeriodPayments(this.state.singlePayrollPeriod.id);
    +          }
    +        );
    +      }
    +    }
    +  }
    +
    +  updatePayrollPeriod(payrollPeriods) {
    +    if (payrollPeriods == null) return;
    +
    +    let singlePayrollPeriod = null;
    +    if (typeof this.props.match.params.period_id !== "undefined") {
    +      singlePayrollPeriod = payrollPeriods.find(
    +        (pp) => pp.id == this.props.match.params.period_id
    +      );
    +    }
    +
    +    this.setState({
    +      payrollPeriods,
    +      singlePayrollPeriod: singlePayrollPeriod || null,
    +      payments: this.groupPayments(singlePayrollPeriod),
    +    });
    +  }
    +  async getEmployeeDocumet(emp, type) {
    +    this.setState({
    +      formLoading: true,
    +      form: null,
    +    });
    +    const id = emp.employee.id;
    +
    +    const w4form = await GET("employers/me/" + "w4-form" + "/" + id);
    +    const i9form = await GET("employers/me/" + "i9-form" + "/" + id);
    +    const employeeDocument = await GET(
    +      "employers/me/" + "employee-documents" + "/" + id
    +    );
    +
    +    const data = {
    +      w4form: w4form[0],
    +      i9form: i9form[0],
    +      employeeDocument: employeeDocument[0] || "",
    +      employeeDocument2: employeeDocument[1] || "",
    +    };
    +
    +    if (type === "w4") this.fillForm(data);
    +    else if (type === "i9") this.fillFormI9(data);
    +  }
    +
    +  async fillForm(data) {
    +    if (data) {
    +      const signature = data.w4form.employee_signature;
    +      const png = `data:image/png;base64,${signature}`;
    +      const formUrl =
    +        "https://api.vercel.com/now/files/20f93230bb41a5571f15a12ca0db1d5b20dd9ce28ca9867d20ca45f6651cca0f/fw4.pdf";
    +
    +      const formPdfBytes = await fetch(formUrl).then((res) =>
    +        res.arrayBuffer()
    +      );
    +      const pngUrl = png;
    +
    +      var pngImageBytes;
    +      var pdfDoc = await PDFDocument.load(formPdfBytes);
    +      var pngImage;
    +      if (signature) {
    +        pngImageBytes = await fetch(pngUrl).then((res) => res.arrayBuffer());
    +
    +        pngImage = await pdfDoc.embedPng(pngImageBytes);
    +      }
    +      const pages = pdfDoc.getPages();
    +      const firstPage = pages[0];
    +
    +      const { width, height } = firstPage.getSize();
    +
    +      const form = pdfDoc.getForm();
    +
    +      var pngDims;
    +      if (pngImage) pngDims = pngImage.scale(0.18);
    +
    +      const nameField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_01[0]"
    +      );
    +      const lastNameField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_02[0]"
    +      );
    +      const socialSecurityField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_05[0]"
    +      );
    +      const addressField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_03[0]"
    +      );
    +      const cityField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step1a[0].f1_04[0]"
    +      );
    +      const fillingFieldSingle = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].c1_1[0]"
    +      );
    +      const fillingFieldMarried = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].c1_1[1]"
    +      );
    +      const fillingFieldHead = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].c1_1[2]"
    +      );
    +      const multipleJobsField = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].Step2c[0].c1_2[0]"
    +      );
    +      const step3aField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step3_ReadOrder[0].f1_06[0]"
    +      );
    +      const step3bField = form.getTextField(
    +        "topmostSubform[0].Page1[0].Step3_ReadOrder[0].f1_07[0]"
    +      );
    +      const step3cField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_08[0]"
    +      );
    +      const step4aField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_09[0]"
    +      );
    +      const step4bField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_10[0]"
    +      );
    +      const step4cField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_11[0]"
    +      );
    +      const employerField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_13[0]"
    +      );
    +      const employmentDateField = form.getTextField(
    +        "topmostSubform[0].Page1[0].f1_14[0]"
    +      );
    +      const einField = form.getTextField("topmostSubform[0].Page1[0].f1_15[0]");
    +
    +      nameField.setText(data.i9form.first_name);
    +      lastNameField.setText(data.i9form.last_name);
    +      socialSecurityField.setText(data.i9form.social_security);
    +      addressField.setText(data.i9form.address);
    +      cityField.setText(data.i9form.city);
    +
    +      if (data.w4form.filing_status == "SINGLE") fillingFieldSingle.check();
    +      else if (data.w4form.filing_status == "MARRIED")
    +        fillingFieldMarried.check();
    +      else if (data.w4form.filing_status == "HEAD") fillingFieldHead.check();
    +
    +      if (data.w4form.step2c) multipleJobsField.check();
    +
    +      step3aField.setText(data.w4form.dependant3b);
    +      step3bField.setText(data.w4form.dependant3c);
    +
    +      if (data.w4form.dependant3b && data.w4form.dependant3c) {
    +        var totalDependant =
    +          +data.w4form.dependant3b + +data.w4form.dependant3c;
    +        step3cField.setText(totalDependant.toString());
    +      }
    +
    +      step4aField.setText(data.w4form.step4a);
    +      step4bField.setText(data.w4form.step4b);
    +      step4cField.setText(data.w4form.step4c);
    +      employerField.setText(
    +        "JobCore Inc, 270 Catalonia AveCoral Gables, FL 33134"
    +      );
    +      employmentDateField.setText(
    +        moment(data.w4form.updated_at).format("MM/DD/YYYY")
    +      );
    +      einField.setText("83-1919066");
    +
    +      firstPage.drawText(moment(data.w4form.created_at).format("MM/DD/YYYY"), {
    +        x: 470,
    +        y: firstPage.getHeight() / 5.5,
    +        size: 14,
    +        color: rgb(0, 0, 0),
    +      });
    +
    +      if (pngImage) {
    +        firstPage.drawImage(pngImage, {
    +          x: firstPage.getWidth() / 7 - pngDims.width / 2 + 75,
    +          y: firstPage.getHeight() / 4.25 - pngDims.height,
    +          width: pngDims.width,
    +          height: pngDims.height,
    +        });
    +      }
    +
    +      const pdfBytes = await pdfDoc.save();
    +      var blob = new Blob([pdfBytes], { type: "application/pdf" });
    +
    +      const fileURL = URL.createObjectURL(blob);
    +      this.setState({
    +        form: fileURL,
    +        formLoading: false,
    +      });
    +
    +      //   window.open(blob);
    +      //   saveAs(blob, `${data.i9form.first_name + "_" + data.i9form.last_name+"_W4"}.pdf`);
    +    }
    +  }
    +  async fillFormI9(data) {
    +    if (data) {
    +      const signature = data.i9form.employee_signature;
    +      const png = `data:image/png;base64,${signature}`;
    +      const formUrl =
    +        "https://api.vercel.com/now/files/5032a373d2e112174680444f0aac149210e4ac4c3c3b55144913e319cfa72bd4/i92.pdf";
    +      const formPdfBytes = await fetch(formUrl).then((res) =>
    +        res.arrayBuffer()
    +      );
    +      
    +      const pngUrl = png;
    +      var pngImageBytes;
    +      var pngImage;
    +      const pdfDoc = await PDFDocument.load(formPdfBytes);
    +      if (signature) {
    +        pngImageBytes = await fetch(pngUrl).then((res) => res.arrayBuffer());
    +        pngImage = await pdfDoc.embedPng(pngImageBytes);
    +      }
    +
    +      const document = data.employeeDocument.document;
    +      var documentBytes;
    +      var documentImage;
    +      if (document) {
    +        documentBytes = await fetch(document).then((res) => res.arrayBuffer());
    +        documentImage = await pdfDoc.embedJpg(documentBytes);
    +      }
    +      var document2Image = null;
    +      if (data.employeeDocument2) {
    +        const document2 = data.employeeDocument2.document;
    +        const document2Bytes = await fetch(document2).then((res) =>
    +          res.arrayBuffer()
    +        );
    +        document2Image = await pdfDoc.embedJpg(document2Bytes);
    +      }
    +      const newPage = pdfDoc.addPage();
    +
    +      const pages = pdfDoc.getPages();
    +      const firstPage = pages[0];
    +
    +      const { width, height } = firstPage.getSize();
    +
    +      const form = pdfDoc.getForm();
    +
    +      var pngDims;
    +
    +      if (pngImage) pngDims = pngImage.scale(0.1);
    +
    +      const lastname = form.getTextField(
    +        "topmostSubform[0].Page1[0].Last_Name_Family_Name[0]"
    +      );
    +      const name = form.getTextField(
    +        "topmostSubform[0].Page1[0].First_Name_Given_Name[0]"
    +      );
    +      const middle = form.getTextField(
    +        "topmostSubform[0].Page1[0].Middle_Initial[0]"
    +      );
    +      const otherlastname = form.getTextField(
    +        "topmostSubform[0].Page1[0].Other_Last_Names_Used_if_any[0]"
    +      );
    +      const address = form.getTextField(
    +        "topmostSubform[0].Page1[0].Address_Street_Number_and_Name[0]"
    +      );
    +      const apt = form.getTextField("topmostSubform[0].Page1[0].Apt_Number[0]");
    +      const city = form.getTextField(
    +        "topmostSubform[0].Page1[0].City_or_Town[0]"
    +      );
    +      const zip = form.getTextField("topmostSubform[0].Page1[0].ZIP_Code[0]");
    +      const birthday = form.getTextField(
    +        "topmostSubform[0].Page1[0].Date_of_Birth_mmddyyyy[0]"
    +      );
    +      const ssn3 = form.getTextField(
    +        "topmostSubform[0].Page1[0].U\\.S\\._Social_Security_Number__First_3_Numbers_[0]"
    +      );
    +      const ssn2 = form.getTextField(
    +        "topmostSubform[0].Page1[0].U\\.S\\._Social_Security_Number__Next_2_numbers_[0]"
    +      );
    +      const ssn4 = form.getTextField(
    +        "topmostSubform[0].Page1[0].U\\.S\\._Social_Security_Number__Last_4_numbers_[0]"
    +      );
    +      const email = form.getTextField(
    +        "topmostSubform[0].Page1[0].Employees_Email_Address[0]"
    +      );
    +      const tel = form.getTextField(
    +        "topmostSubform[0].Page1[0].Employees_Telephone_Number[0]"
    +      );
    +      const citizen = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._1\\._A_citizen_of_the_United_States[0]"
    +      );
    +      const noncitizen = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._2\\._A_noncitizen_national_of_the_United_States__See_instructions_[0]"
    +      );
    +      const resident = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._3\\._A_lawful_permanent_resident__Alien_Registration_Number_USCIS_Number__[0]"
    +      );
    +      const uscis = form.getTextField(
    +        "topmostSubform[0].Page1[0].Alien_Registration_NumberUSCIS_Number_1[0]"
    +      );
    +      const alien = form.getCheckBox(
    +        "topmostSubform[0].Page1[0]._4\\._An_alien_authorized_to_work_until__expiration_date__if_applicable__mmd_dd_yyyy__[0]"
    +      );
    +      const exp = form.getTextField(
    +        "topmostSubform[0].Page1[0].expiration_date__if_applicable__mm_dd_yyyy[0]"
    +      );
    +      const alienuscis = form.getTextField(
    +        "topmostSubform[0].Page1[0]._1_Alien_Registration_NumberUSCIS_Number[0]"
    +      );
    +      const admision = form.getTextField(
    +        "topmostSubform[0].Page1[0]._2_Form_I94_Admission_Number[0]"
    +      );
    +      const foreign = form.getTextField(
    +        "topmostSubform[0].Page1[0]._3_Foreign_Passport_Number[0]"
    +      );
    +      const issuance = form.getTextField(
    +        "topmostSubform[0].Page1[0].Country_of_Issuance[0]"
    +      );
    +      const nottranslator = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].I_did_not_use_a_preparer_or_translator[0]"
    +      );
    +      const translator = form.getCheckBox(
    +        "topmostSubform[0].Page1[0].A_preparer_s__and_or_translator_s__assisted_the_employee_in_completing_Section_1[0]"
    +      );
    +      const lastnamet = form.getTextField(
    +        "topmostSubform[0].Page1[0].Last_Name_Family_Name_2[0]"
    +      );
    +      const firstnamet = form.getTextField(
    +        "topmostSubform[0].Page1[0].First_Name_Given_Name_2[0]"
    +      );
    +      const addresst = form.getTextField(
    +        "topmostSubform[0].Page1[0].Address_Street_Number_and_Name_2[0]"
    +      );
    +      const cityt = form.getTextField(
    +        "topmostSubform[0].Page1[0].City_or_Town_2[0]"
    +      );
    +      const zipcodet = form.getTextField(
    +        "topmostSubform[0].Page1[0].Zip_Code[0]"
    +      );
    +      const statet = form.getDropdown("topmostSubform[0].Page1[0].State[0]");
    +      const state2t = form.getDropdown("topmostSubform[0].Page1[0].State[1]");
    +      const lastname2 = form.getTextField(
    +        "topmostSubform[0].Page2[0].Last_Name_Family_Name_3[0]"
    +      );
    +      const firstname2 = form.getTextField(
    +        "topmostSubform[0].Page2[0].First_Name_Given_Name_3[0]"
    +      );
    +      const middle2 = form.getTextField("topmostSubform[0].Page2[0].MI[0]");
    +
    +      lastname.setText(data.i9form.last_name);
    +      name.setText(data.i9form.first_name);
    +      middle.setText(data.i9form.middle_initial);
    +      otherlastname.setText(data.i9form.other_last_name);
    +      address.setText(data.i9form.address);
    +      apt.setText(data.i9form.apt_number);
    +      city.setText(data.i9form.city);
    +      zip.setText(data.i9form.zipcode);
    +      birthday.setText(data.i9form.date_of_birth);
    +
    +      if (data.i9form.social_security) {
    +        var ssn = data.i9form.social_security.split("-");
    +        ssn3.setText(ssn[0]);
    +        ssn2.setText(ssn[1]);
    +        ssn4.setText(ssn[2]);
    +      }
    +
    +      email.setText(data.i9form.email);
    +      tel.setText(data.i9form.phone);
    +      if (data.i9form.employee_attestation === "CITIZEN") citizen.check();
    +      else if (data.i9form.employee_attestation === "NON_CITIZEN")
    +        noncitizen.check();
    +      else if (data.i9form.employee_attestation === "ALIEN") alien.check();
    +      else if (data.i9form.employee_attestation === "RESIDENT")
    +        resident.check();
    +
    +      uscis.setText(data.i9form.USCIS);
    +      exp.setText("");
    +      alienuscis.setText(data.i9form.USCIS);
    +      admision.setText(data.i9form.I_94);
    +      foreign.setText(data.i9form.passport_number);
    +      issuance.setText(data.i9form.country_issuance);
    +
    +      if (!data.i9form.translator) nottranslator.check();
    +      else if (!data.i9form.translator) translator.check();
    +
    +      lastnamet.setText(data.i9form.translator_last_name);
    +      firstnamet.setText(data.i9form.translator_first_name);
    +      addresst.setText(data.i9form.translator_address);
    +      cityt.setText(data.i9form.translator_city);
    +      zipcodet.setText(data.i9form.translator_zipcode);
    +      if (data.i9form.translator_state) {
    +        statet.select(data.i9form.translator_state);
    +        state2t.select(data.i9form.translator_state);
    +      }
    +      lastname2.setText(data.i9form.last_name);
    +      firstname2.setText(data.i9form.first_name);
    +      middle2.setText(data.i9form.middle_initial);
    +
    +      if (documentImage) {
    +        pages[3].drawImage(documentImage, {
    +          height: 325,
    +          width: 275,
    +          x: 50,
    +          y: 790 - 325,
    +        });
    +      }
    +
    +      if (document2Image) {
    +        pages[3].drawImage(document2Image, {
    +          height: 325,
    +          width: 275,
    +          x: 50,
    +          y: 790 - 325 - 350,
    +        });
    +      }
    +      firstPage.drawText(moment(data.w4form.created_at).format("MM/DD/YYYY"), {
    +        x: 375,
    +        y: 257,
    +        size: 10,
    +        color: rgb(0, 0, 0),
    +      });
    +
    +      if (pngImage) {
    +        firstPage.drawImage(pngImage, {
    +          x: 115,
    +          y: 240,
    +          width: pngDims.width,
    +          height: pngDims.height,
    +        });
    +      }
    +
    +      const pdfBytes = await pdfDoc.save();
    +      var blob = new Blob([pdfBytes], { type: "application/pdf" });
    +      const fileURL = URL.createObjectURL(blob);
    +
    +      this.setState({
    +        form: fileURL,
    +        formLoading: false,
    +      });
    +      //   saveAs(blob, `${data.i9form.first_name + "_" + data.i9form.last_name+"_I9"+moment().format("MMDDYYYY")}.pdf`);
    +    }
    +  }
    +  getDifferentHours(id) {
    +    if (
    +      id &&
    +      this.state.payments &&
    +      Array.isArray(this.state.payments) &&
    +      this.state.payments.length > 0
    +    ) {
    +      const s = this.state.payments
    +        .filter((e) => {
    +          if (e.employee && e.employee.id === id) {
    +            return e;
    +          } else null;
    +        })
    +        .map((p) => p.payments)[0]
    +        .map((z) => ({
    +          position: z.shift.position.title,
    +          hourly_rate: parseFloat(z.hourly_rate),
    +          hours: parseFloat(z.over_time) + parseFloat(z.regular_hours),
    +          status: z.status,
    +        }));
    +
    +      const output = s
    +        .filter((e) => e.status == "APPROVED")
    +        .reduce((accumulator, cur) => {
    +          let hr = cur.hourly_rate;
    +          let found = accumulator.find((elem) => elem.hourly_rate === hr);
    +          if (found) found.hours += cur.hours;
    +          else accumulator.push(cur);
    +          return accumulator;
    +        }, []);
    +
    +      return (
    +        <table
    +          className="table table-sm table-bordered"
    +          style={{ fontSize: "12px", border: "1px solid black" }}
    +        >
    +          <thead style={{ background: "transparent" }}>
    +            <tr style={{ background: "transparent", fontSize: "14px" }}>
    +              <th scope="col">Position</th>
    +              <th scope="col">Hours</th>
    +              <th scope="col">$/hr</th>
    +            </tr>
    +          </thead>
    +          <tbody>
    +            {output.map((e, i) => (
    +              <tr key={i} style={{ background: "transparent" }}>
    +                <td>{e.position}</td>
    +                <td>{e.hours.toFixed(2)}</td>
    +                <td>{"$" + e.hourly_rate.toFixed(2)}</td>
    +              </tr>
    +            ))}
    +          </tbody>
    +        </table>
    +      );
    +    }
    +  }
    +
    +  render() {
    +    if (!this.state.employer) return "Loading...";
    +    else if (
    +      !this.state.employer.payroll_configured ||
    +      !moment.isMoment(this.state.employer.payroll_period_starting_time)
    +    ) {
    +      return (
    +        <div className="p-1 listcontents text-center">
    +          <h3>Please setup your payroll settings first.</h3>
    +          <Button
    +            color="success"
    +            onClick={() => this.props.history.push("/payroll/settings")}
    +          >
    +            Setup Payroll Settings
    +          </Button>
    +        </div>
    +      );
    +    }
    +    const payrollPeriodLabel = this.state.singlePayrollPeriod
    +      ? `Payments From ${moment(
    +          this.state.singlePayrollPeriod.starting_at
    +        ).format("MM-D-YY h:mm A")} to ${moment(
    +          this.state.singlePayrollPeriod.ending_at
    +        ).format("MM-D-YY h:mm A")}`
    +      : "";
    +    //const allowLevels = (window.location.search != '');
    +    const formLoading = this.state.formLoading;
    +    const form = this.state.form;
    +    return (
    +      <div className="p-1" style={{ maxWidth: "1200px" }}>
    +        <div
    +          className="modal fade"
    +          id="exampleModalCenter"
    +          tabIndex="-1"
    +          role="dialog"
    +          aria-labelledby="exampleModalCenterTitle"
    +          aria-hidden="true"
    +        >
    +          <div className="modal-dialog modal-dialog-centered" role="document">
    +            <div className="modal-content">
    +              {form ? (
    +                <iframe
    +                  src={form}
    +                  style={{ width: "800px", height: "900px" }}
    +                  frameBorder="0"
    +                ></iframe>
    +              ) : (
    +                <div
    +                  className="spinner-border text-center mx-auto"
    +                  role="status"
    +                >
    +                  <span className="sr-only">Loading...</span>
    +                </div>
    +              )}
    +            </div>
    +          </div>
    +        </div>
    +        <Theme.Consumer>
    +          {({ bar }) => (
    +            <span>
    +              {!this.state.paymentInfo ? (
    +                ""
    +              ) : this.state.paymentInfo.payments &&
    +                this.state.paymentInfo.payments.length > 0 ? (
    +                <div>
    +                  <p className="text-right">
    +                    <h2>{payrollPeriodLabel}</h2>
    +                  </p>
    +                  <div className="row mb-4 text-right">
    +                    <div className="col text-left">
    +                      <Button
    +                        size="small"
    +                        onClick={() => {
    +                          // res => this.props.history.push('/payroll/period/' + period.id
    +                          const period = this.state.singlePayrollPeriod;
    +                          update(
    +                            "payroll-periods",
    +                            Object.assign(period, { status: "OPEN" })
    +                          )
    +                            .then((res) =>
    +                              this.props.history.push(
    +                                "/payroll/period/" + period.id
    +                              )
    +                            )
    +                            .catch((e) => Notify.error(e.message || e));
    +                        }}
    +                      >
    +                        Undo Period
    +                      </Button>
    +                    </div>
    +
    +                    <div className="col">
    +                      <Button
    +                        size="small"
    +                        onClick={() =>
    +                          this.props.history.push(
    +                            "/payroll/period/" +
    +                              this.state.singlePayrollPeriod.id
    +                          )
    +                        }
    +                      >
    +                        Review Timesheet
    +                      </Button>
    +                    </div>
    +                    <PDFDownloadLink
    +                      document={
    +                        <PayrollPeriodReport
    +                          employer={this.state.employer}
    +                          payments={this.state.paymentInfo.payments}
    +                          period={this.state.singlePayrollPeriod}
    +                        />
    +                      }
    +                      fileName={
    +                        "JobCore payments" + payrollPeriodLabel + ".pdf"
    +                      }
    +                    >
    +                      {({ blob, url, loading, error }) =>
    +                        loading ? (
    +                          "Loading..."
    +                        ) : (
    +                          <div className="col">
    +                            <Button color="success" size="small">
    +                              Export to PDF
    +                            </Button>
    +                          </div>
    +                        )
    +                      }
    +                    </PDFDownloadLink>
    +                  </div>
    +
    +                  {/* {this.state.singlePayrollPeriod.status == "OPEN" &&
    +                                    <Redirect from={'/payroll/report/' + this.state.singlePayrollPeriod.id} to={'/payroll/rating/' + this.state.singlePayrollPeriod.id} />
    +                                } */}
    +                  <table className="table table-striped payroll-summary text-center">
    +                    <thead>
    +                      <tr>
    +                        <th scope="col" className="text-left">
    +                          Staff
    +                        </th>
    +                        {/* <th scope="col">Regular Hrs</th> */}
    +                        <th scope="col"></th>
    +                        <th scope="col">Over Time</th>
    +                        <th scope="col">Total Hrs</th>
    +                        <th scope="col">Pay Rate</th>
    +                        <th scope="col">Earnings</th>
    +                        <th scope="col">Federal Withholding</th>
    +                        <th scope="col">Social Security</th>
    +                        <th scope="col">Medicare</th>
    +                        {/* <th scope="col">Taxes</th> */}
    +                        <th scope="col">Amount</th>
    +                        <th scope="col"></th>
    +                      </tr>
    +                    </thead>
    +                    <tbody>
    +                      {this.state.paymentInfo.payments
    +                        .sort((a, b) =>
    +                          a.employee.last_name.toLowerCase() >
    +                          b.employee.last_name.toLowerCase()
    +                            ? 1
    +                            : -1
    +                        )
    +                        .map((pay) => {
    +                          const totalHour =
    +                            Math.round(
    +                              (Number(pay.regular_hours) +
    +                                Number(pay.over_time)) *
    +                                100
    +                            ) / 100;
    +                          var payRate;
    +
    +                          if (totalHour > 40) {
    +                            payRate = (
    +                              pay.earnings /
    +                              (40 + (totalHour - 40) * 1.5)
    +                            ).toFixed(2);
    +                          } else
    +                            payRate = (pay.earnings / totalHour).toFixed(2);
    +                          return (
    +                            <tr key={pay.employee.id}>
    +                              <td className="text-left">
    +                                {pay.employee.last_name},{" "}
    +                                {pay.employee.first_name}
    +                                <div className="row pt-1 pb-1">
    +                                  <div className="col pr-0">
    +                                    {pay.employee
    +                                      .employment_verification_status ===
    +                                    "APPROVED" ? (
    +                                      <span
    +                                        style={{ cursor: "pointer" }}
    +                                        data-toggle="modal"
    +                                        data-target="#exampleModalCenter"
    +                                        onClick={() => {
    +                                          if (!formLoading)
    +                                            this.getEmployeeDocumet(pay, "w4");
    +                                        }}
    +                                      >
    +                                        <i
    +                                          style={{
    +                                            fontSize: "16px",
    +                                            color: "#43A047",
    +                                          }}
    +                                          className="fas fa-file-alt mr-1"
    +                                        ></i>
    +                                        {!formLoading ? "W-4" : "Loading"}
    +                                      </span>
    +                                    ) : (
    +                                      <span className="text-muted">
    +                                        <i className="fas fa-exclamation-circle text-danger mr-1"></i>
    +                                        W-4
    +                                      </span>
    +                                    )}
    +                                  </div>
    +                                  <div className="col">
    +                                    {pay.employee
    +                                      .employment_verification_status ===
    +                                    "APPROVED" ? (
    +                                      <span
    +                                        style={{ cursor: "pointer" }}
    +                                        data-toggle="modal"
    +                                        data-target="#exampleModalCenter"
    +                                        onClick={() => {
    +                                          if (!formLoading)
    +                                            this.getEmployeeDocumet(pay, "i9");
    +                                        }}
    +                                      >
    +                                        <i
    +                                          style={{
    +                                            fontSize: "16px",
    +                                            color: "#43A047",
    +                                          }}
    +                                          className="fas fa-file-alt mr-1"
    +                                        ></i>
    +                                        {!formLoading ? "I-9" : "Loading"}
    +                                      </span>
    +                                    ) : (
    +                                      <span className="text-muted">
    +                                        <i className="fas fa-exclamation-circle text-danger mr-1"></i>
    +                                        I-9
    +                                      </span>
    +                                    )}
    +                                  </div>
    +                                </div>
    +                                <p className="m-0 p-0">
    +                                  <span className="badge">
    +                                    {pay.paid ? "paid" : "unpaid"}
    +                                  </span>
    +                                </p>
    +                              </td>
    +                              <td className="p-0">
    +                                <div className="row">
    +                                  <div className="col-12 pr-0 pl-0">
    +                                    {this.getDifferentHours(pay.employee.id)}
    +
    +                                    {/* {Math.round((Number(pay.regular_hours) + Number(pay.over_time)) * 100) / 100 > 40 ? 40 : Math.round((Number(pay.regular_hours) + Number(pay.over_time)) * 100)/100} */}
    +                                  </div>
    +                                  {/* {this.getDifferentHours(pay.employee.id)} */}
    +                                </div>
    +                              </td>
    +                              <td>
    +                                {Math.round(
    +                                  (Number(pay.regular_hours) +
    +                                    Number(pay.over_time)) *
    +                                    100
    +                                ) /
    +                                  100 >
    +                                40
    +                                  ? Math.round(
    +                                      (Number(pay.regular_hours) +
    +                                        Number(pay.over_time) -
    +                                        40) *
    +                                        100
    +                                    ) / 100
    +                                  : "-"}
    +                              </td>
    +                              <td>
    +                                {Math.round(
    +                                  (Number(pay.regular_hours) +
    +                                    Number(pay.over_time)) *
    +                                    100
    +                                ) / 100}
    +                              </td>
    +                              <td>{"$" + Math.floor(payRate * 100) / 100}</td>
    +                              <td>{pay.earnings}</td>
    +                              <td>
    +                                {pay.deduction_list.find(
    +                                  (e) => e.name == "Federal Withholding"
    +                                ).amount > 0
    +                                  ? "-" +
    +                                    pay.deduction_list.find(
    +                                      (e) => e.name == "Federal Withholding"
    +                                    ).amount
    +                                  : 0}
    +                              </td>
    +                              <td>
    +                                {"-" +
    +                                  pay.deduction_list.find(
    +                                    (e) => e.name == "Social Security"
    +                                  ).amount}
    +                              </td>
    +                              <td>
    +                                {"-" +
    +                                  pay.deduction_list.find(
    +                                    (e) => e.name == "Medicare"
    +                                  ).amount}
    +                              </td>
    +                              {/* <td>{"-" + pay.deductions}</td> */}
    +                              <td>{pay.amount}</td>
    +                              <td>
    +                                <Button
    +                                  color="success"
    +                                  size="small"
    +                                  onClick={() =>
    +                                    bar.show({
    +                                      slug: "make_payment",
    +                                      data: {
    +                                        pay: pay,
    +                                        paymentInfo: this.state.paymentInfo,
    +                                        periodId:
    +                                          this.state.singlePayrollPeriod.id,
    +                                        bar: bar,
    +                                      },
    +                                    })
    +                                  }
    +                                >
    +                                  {pay.paid
    +                                    ? "Payment details"
    +                                    : "Make payment"}
    +                                </Button>
    +                              </td>
    +                              {/* <td>{Math.round((total.regular_hours + total.over_time) * 100) / 100}</td>
    +                                                <td>${Math.round(total.total_amount * 100) / 100}</td> */}
    +                            </tr>
    +                          );
    +                        })}
    +                    </tbody>
    +                  </table>
    +                </div>
    +              ) : (
    +                <p>No payments to review for this period</p>
    +              )}
    +            </span>
    +          )}
    +        </Theme.Consumer>
    +      </div>
    +    );
    +  }
    +}
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_profile.js.html b/docs/views_profile.js.html new file mode 100644 index 0000000..2c2894f --- /dev/null +++ b/docs/views_profile.js.html @@ -0,0 +1,1043 @@ + + + + + JSDoc: Source: views/profile.js + + + + + + + + + + +
    + +

    Source: views/profile.js

    + + + + + + +
    +
    +
    import React, { useState, useContext, useEffect } from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import {
    +  store,
    +  fetchTemporal,
    +  update,
    +  updateProfileImage,
    +  searchMe,
    +  remove,
    +  updateUser,
    +  removeUser,
    +  updateProfileMe,
    +  sendCompanyInvitation,
    +} from "../actions.js";
    +import {
    +  TIME_FORMAT,
    +  DATETIME_FORMAT,
    +  DATE_FORMAT,
    +  TODAY,
    +} from "../components/utils.js";
    +import {
    +  Button,
    +  Theme,
    +  GenericCard,
    +  Avatar,
    +  SearchCatalogSelect,
    +  Wizard,
    +} from "../components/index";
    +import { Notify } from "bc-react-notifier";
    +import { Session } from "bc-react-session";
    +import { validator, ValidationError } from "../utils/validation";
    +import Dropzone from "react-dropzone";
    +import DateTime from "react-datetime";
    +import moment from "moment";
    +import PropTypes from "prop-types";
    +import Select from "react-select";
    +import { GET } from "../utils/api_wrapper";
    +import { hasTutorial } from "../utils/tutorial";
    +import Cropper from "react-cropper";
    +import "cropperjs/dist/cropper.css";
    +
    +import Tooltip from "rc-tooltip";
    +import "rc-tooltip/assets/bootstrap_white.css";
    +import { select } from "underscore";
    +
    +export const Employer = (data = {}) => {
    +  const _defaults = {
    +    title: undefined,
    +    website: undefined,
    +    payroll_period_starting_time: TODAY(),
    +    maximum_clockout_delay_minutes: 0,
    +    bio: undefined,
    +    uploadCompanyLogo: null,
    +    editingImage: false,
    +    response_time: undefined,
    +    rating: undefined,
    +    retroactive: undefined,
    +    serialize: function () {
    +      const newShift = {
    +        //                status: (this.status == 'UNDEFINED') ? 'DRAFT' : this.status,
    +      };
    +
    +      return Object.assign(this, newShift);
    +    },
    +  };
    +
    +  let _employer = Object.assign(_defaults, data);
    +  return {
    +    validate: () => {
    +      if (_employer.bio && validator.isEmpty(_employer.bio))
    +        throw new ValidationError("The company bio cannot be empty");
    +      if (_employer.title && validator.isEmpty(_employer.title))
    +        throw new ValidationError("The company name cannot be empty");
    +      if (_employer.website && validator.isEmpty(_employer.website))
    +        throw new ValidationError("The company website cannot be empty");
    +      return _employer;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +  };
    +};
    +
    +export class Profile extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      employer: Employer().defaults(),
    +      currentUser: Session.getPayload().user.profile,
    +      runTutorial: hasTutorial(),
    +      stepIndex: 0,
    +      steps: [
    +        {
    +          content: (
    +            <div className="text-left">
    +              <h1>Your Profile</h1>
    +              <p>
    +                Here you will edit your company information. You can also manage
    +                subscription and view your company rating
    +              </p>
    +            </div>
    +          ),
    +          placement: "center",
    +
    +          styles: {
    +            options: {
    +              zIndex: 10000,
    +            },
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          locale: { skip: "Skip tutorial" },
    +          target: "body",
    +        },
    +        {
    +          target: "#company-logo-circle",
    +          content: "Click here to upload your company logo",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +            buttonNext: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#company-logo-dropzone",
    +          content: "Click the on the rectangle to upload a company logo",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +            buttonNext: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#company-logo-save",
    +          content: "Click save to update your logo",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +            buttonNext: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#company_title",
    +          content: "Edit company name",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#company_website",
    +          content: "Edit company website",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#company_bio",
    +          content: "Tell future employees about your business",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +        {
    +          target: "#button_save",
    +          content: "Save all your progress",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +            buttonNext: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +
    +        {
    +          target: "#manage_locations",
    +          content:
    +            "Edit your company locations. This is how your employee will know where to report. You will need at least one company address in order to send shifts to future employees",
    +          placement: "right",
    +          styles: {
    +            buttonClose: {
    +              display: "none",
    +            },
    +            buttonNext: {
    +              display: "none",
    +            },
    +          },
    +          spotlightClicks: true,
    +        },
    +      ],
    +    };
    +  }
    +
    +  setEmployer(newEmployer) {
    +    const employer = Object.assign(this.state.employer, newEmployer);
    +    this.setState({ employer });
    +  }
    +
    +  componentDidMount() {
    +    const users = store.getState("users");
    +    this.subscribe(store, "users", (_users) => {
    +      const user = _users.filter(
    +        (e) => e.profile.id == Session.getPayload().user.profile.id
    +      )[0];
    +      this.setState({ user: user });
    +    });
    +    if (users) {
    +      const user = users.filter(
    +        (e) => e.profile.id == Session.getPayload().user.profile.id
    +      )[0];
    +      this.setState({ user: user });
    +    } else searchMe("users");
    +
    +    let employer = store.getState("current_employer");
    +    if (employer) this.setState({ employer });
    +    this.subscribe(store, "current_employer", (employer) => {
    +      this.setState({ employer });
    +    });
    +  }
    +
    +  _crop() {
    +    // image in dataUrl
    +    console.log(this.cropper.getCroppedCanvas().toDataURL());
    +  }
    +
    +  onCropperInit(cropper) {
    +    this.cropper = cropper;
    +  }
    +  callback = (data) => {
    +
    +    // if(data.action == 'next' && data.index == 0){
    +    //     this.props.history.push("/payroll");
    +
    +    // }
    +    if (data.action == "next" && data.index == 0) {
    +      this.setState({ stepIndex: 1 });
    +    } else if (
    +      data.action == "next" &&
    +      data.index == 4 &&
    +      data.lifecycle == "complete" &&
    +      data.step.target == "#company_title"
    +    ) {
    +      this.setState({ stepIndex: 5 });
    +    } else if (
    +      data.action == "next" &&
    +      data.index == 5 &&
    +      data.lifecycle == "complete" &&
    +      data.step.target == "#company_website"
    +    ) {
    +      this.setState({ stepIndex: 6 });
    +    } else if (
    +      data.action == "next" &&
    +      data.index == 6 &&
    +      data.lifecycle == "complete" &&
    +      data.step.target == "#company_bio"
    +    ) {
    +      this.setState({ stepIndex: 7 });
    +    }
    +
    +    if (data.action == "skip") {
    +      const session = Session.get();
    +      updateProfileMe({ show_tutorial: false });
    +
    +      const profile = Object.assign(session.payload.user.profile, {
    +        show_tutorial: false,
    +      });
    +      const user = Object.assign(session.payload.user, { profile });
    +      Session.setPayload({ user });
    +      this.setState({ runTutorial: false });
    +      document.getElementById("profilelink").style.backgroundColor = "";
    +    }
    +  };
    +
    +  render() {
    +    return (
    +      <div className="p-1 listcontents company-profile">
    +        <Wizard
    +          continuous
    +          steps={this.state.steps}
    +          run={this.state.runTutorial}
    +          callback={(data) => this.callback(data)}
    +          stepIndex={this.state.stepIndex}
    +          allowClicksThruHole={true}
    +          disableOverlay={true}
    +          spotlightClicks={true}
    +          styles={{
    +            options: {
    +              width: 600,
    +              primaryColor: "#000",
    +              zIndex: 1000,
    +            },
    +          }}
    +        />
    +        <h1>
    +          <span id="company_details">User Details</span>
    +        </h1>
    +        <form>
    +          <div className="row mt-2">
    +            <div className="col-6">
    +              <label>Name</label>
    +              <p>
    +                {this.state.user &&
    +                  this.state.user.first_name + " " + this.state.user.last_name}
    +              </p>
    +            </div>
    +            <div className="col-6">
    +              <label>Email</label>
    +              <p>{this.state.user && this.state.user.email}</p>
    +            </div>
    +          </div>
    +        </form>
    +        <h1>
    +          <span id="company_details">Company Details</span>
    +        </h1>
    +        <form>
    +          <div className="row mt-2">
    +            <div className="col-6">
    +              <label>Response Time</label>
    +              <p>
    +                You answer applications within{" "}
    +                <span className="text-success">
    +                  {this.state.employer.response_time} min.
    +                </span>
    +              </p>
    +            </div>
    +            <div className="col-6">
    +              <label>Rating</label>
    +              <p>
    +                Talents rated you with{" "}
    +                <span className="text-success">
    +                  {this.state.employer.rating} points.
    +                </span>
    +              </p>
    +            </div>
    +          </div>
    +          <div className="row">
    +            <div className="col-12">
    +              <label>Subscription</label>
    +              <p>
    +                {this.state.employer.active_subscription
    +                  ? this.state.employer.active_subscription.title
    +                  : "No active subscription"}
    +                <Button
    +                  className="ml-2"
    +                  onClick={() =>
    +                    this.props.history.push("/profile/subscription")
    +                  }
    +                  size="small"
    +                >
    +                  update
    +                </Button>
    +              </p>
    +            </div>
    +          </div>
    +          <div className="row" id="company-logo-dropzone">
    +            <div className="col-12">
    +              <label>Company Logo</label>
    +
    +              {!this.state.editingImage ? (
    +                <div
    +                  id="company-logo-circle"
    +                  className="company-logo"
    +                  style={{
    +                    backgroundImage: `url(${this.state.employer.picture})`,
    +                  }}
    +                >
    +                  <Button
    +                    color="primary"
    +                    size="small"
    +                    onClick={() =>
    +                      this.setState({ editingImage: true, stepIndex: 2 })
    +                    }
    +                    icon="pencil"
    +                  />
    +                </div>
    +              ) : (
    +                <div>
    +                  {this.state.uploadCompanyLogo ? (
    +                    <div
    +                      className="company-logo"
    +                      style={{
    +                        backgroundImage: `url(${URL.createObjectURL(
    +                          this.state.uploadCompanyLogo
    +                        )})`,
    +                      }}
    +                    >
    +                      {" "}
    +                      <Button
    +                        color="primary"
    +                        size="small"
    +                        onClick={() =>
    +                          this.setState({
    +                            editingImage: false,
    +                            uploadCompanyLogo: null,
    +                          })
    +                        }
    +                        icon="times"
    +                      />
    +                    </div>
    +                  ) : (
    +                    <div>
    +                      <Dropzone
    +                        onDrop={(acceptedFiles) =>
    +                          this.setState({
    +                            uploadCompanyLogo: acceptedFiles[0],
    +                            stepIndex: 3,
    +                          })
    +                        }
    +                      >
    +                        {({ getRootProps, getInputProps }) => {
    +                          return (
    +                            <section className="upload-zone">
    +                              <div {...getRootProps()}>
    +                                <input {...getInputProps()} />
    +                                <strong
    +                                  style={{
    +                                    textDecoration: "underline",
    +                                    cursor: "pointer",
    +                                    fontSize: "20px",
    +                                  }}
    +                                >
    +                                  Drop your company logo here, or click here to
    +                                  open the file browser
    +                                </strong>
    +                              </div>
    +                            </section>
    +                          );
    +                        }}
    +                      </Dropzone>
    +                    </div>
    +                  )}
    +
    +                  <br />
    +
    +                  <Button
    +                    className="mr-2"
    +                    onClick={() =>
    +                      this.setState({
    +                        editingImage: false,
    +                        uploadCompanyLogo: null,
    +                      })
    +                    }
    +                    color="secondary"
    +                  >
    +                    Cancel
    +                  </Button>
    +                  <Button
    +                    id="company-logo-save"
    +                    onClick={() => {
    +                      updateProfileImage(this.state.uploadCompanyLogo).then(
    +                        (picture) => {
    +                          this.setState((prevState) => {
    +                            let employer = Object.assign(
    +                              {},
    +                              prevState.employer
    +                            );
    +                            employer.picture = picture;
    +                            return {
    +                              employer,
    +                              editingImage: false,
    +                              uploadCompanyLogo: null,
    +                              stepIndex: 4,
    +                            };
    +                          });
    +                        }
    +                      );
    +                    }}
    +                    color="success"
    +                  >
    +                    Save
    +                  </Button>
    +                </div>
    +              )}
    +            </div>
    +          </div>
    +          <div className="row" id="company_title">
    +            <div className="col-12">
    +              <label>Company Name</label>
    +              <input
    +                type="text"
    +                className="form-control"
    +                value={this.state.employer.title}
    +                onChange={(e) => this.setEmployer({ title: e.target.value })}
    +              />
    +            </div>
    +          </div>
    +          <div className="row mt-2" id="company_website">
    +            <div className="col-12">
    +              <label>Website</label>
    +              <input
    +                type="text"
    +                className="form-control"
    +                value={this.state.employer.website}
    +                onChange={(e) => this.setEmployer({ website: e.target.value })}
    +              />
    +            </div>
    +          </div>
    +          <div className="row mt-2" id="company_bio">
    +            <div className="col-12">
    +              <label>Bio</label>
    +              <input
    +                type="text"
    +                className="form-control"
    +                value={this.state.employer.bio}
    +                onChange={(e) => this.setEmployer({ bio: e.target.value })}
    +              />
    +            </div>
    +          </div>
    +          <div className="mt-4 text-right">
    +            <button
    +              type="button"
    +              id="button_save"
    +              className="btn btn-primary"
    +              onClick={() => {
    +                update(
    +                  { path: "employers/me", event_name: "current_employer" },
    +                  Employer(this.state.employer).validate().serialize()
    +                ).catch((e) => Notify.error(e.message || e));
    +                this.setState({ stepIndex: 8 });
    +              }}
    +            >
    +              Save
    +            </button>
    +          </div>
    +        </form>
    +      </div>
    +    );
    +  }
    +}
    +Profile.propTypes = {
    +  history: PropTypes.object,
    +};
    +
    +export class ManageUsers extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      companyUsers: [],
    +      currentUser: Session.getPayload().user.profile,
    +    };
    +  }
    +
    +  componentDidMount() {
    +    const users = store.getState("users");
    +    this.subscribe(store, "users", (_users) => {
    +      this.setState({
    +        companyUsers: _users,
    +        currentUser: Session.getPayload().user.profile,
    +      });
    +    });
    +    if (users)
    +      this.setState({
    +        companyUsers: users,
    +        currentUser: Session.getPayload().user.profile,
    +      });
    +    else searchMe("users");
    +
    +    this.props.history.listen(() => {
    +      this.filter();
    +      this.setState({ firstSearch: false });
    +    });
    +  }
    +
    +  filter(users = null) {
    +    searchMe("users", window.location.search);
    +  }
    +
    +  showRole(profile) {
    +    if (profile.employer.id === this.state.currentUser.employer.id) {
    +      return profile.employer_role;
    +    } else if (profile.employer.id != this.state.currentUser.employer.id) {
    +      const role = profile.other_employers.find(
    +        (emp) => emp.employer == this.state.currentUser.employer
    +      );
    +      if (role) return role.employer_role;
    +      else return "";
    +    } else return "";
    +  }
    +  render() {
    +    const allowLevels = window.location.search != "";
    +    return (
    +      <div className="p-1 listcontents">
    +        <Theme.Consumer>
    +          {({ bar }) => (
    +            <span>
    +              <p className="text-right">
    +                <h1 className="float-left">Company Users</h1>
    +                <Button
    +                  onClick={() =>
    +                    bar.show({
    +                      slug: "invite_user_to_employer",
    +                      allowLevels: true,
    +                    })
    +                  }
    +                >
    +                  Invite new user
    +                </Button>
    +              </p>
    +
    +              {this.state.companyUsers.map((u, i) => (
    +                <GenericCard key={i} hover={true}>
    +                  <Avatar url={u.profile.picture} />
    +                  <div className="btn-group">
    +                    <Tooltip
    +                      placement="bottom"
    +                      trigger={["hover"]}
    +                      overlay={
    +                        <small>
    +                          Admin can create shifts, make payroll payments and
    +                          change employers role.
    +                        </small>
    +                      }
    +                    >
    +                      <Button
    +                        color="primary"
    +                        style={{ background: "white", color: "black" }}
    +                        onClick={() => {
    +                          if (this.state.currentUser.id === u.profile.id)
    +                            Notify.error("You cannot make yourself an admin");
    +                          else if (
    +                            this.state.currentUser.employer_role != "ADMIN"
    +                          ) {
    +                            Notify.error(
    +                              "You cannot change role if you are not ADMIN"
    +                            );
    +                          } else {
    +                            const noti = Notify.info(
    +                              "Are you sure you want to make this person Admin?",
    +                              (answer) => {
    +                                if (answer)
    +                                  updateUser({
    +                                    id: u.profile.id,
    +                                    employer_id:
    +                                      this.state.currentUser.employer.id,
    +                                    employer_role: "ADMIN",
    +                                  });
    +                                noti.remove();
    +                              }
    +                            );
    +                          }
    +                        }}
    +                      >
    +                        Make Admin
    +                      </Button>
    +                    </Tooltip>
    +
    +                    <Tooltip
    +                      placement="bottom"
    +                      trigger={["hover"]}
    +                      overlay={
    +                        <small>
    +                          Manager can create shifts and make payroll payments.
    +                        </small>
    +                      }
    +                    >
    +                      <Button
    +                        style={{ background: "white", color: "black" }}
    +                        onClick={() => {
    +                          if (this.state.currentUser.id === u.profile.id)
    +                            Notify.error("You cannot make yourself an manager");
    +                          else if (
    +                            this.state.currentUser.employer_role != "ADMIN"
    +                          ) {
    +                            Notify.error(
    +                              "You cannot change role if you are not ADMIN"
    +                            );
    +                          } else {
    +                            const noti = Notify.info(
    +                              "Are you sure you want to make this person Manager?",
    +                              (answer) => {
    +                                if (answer)
    +                                  updateUser({
    +                                    id: u.profile.id,
    +                                    employer_id:
    +                                      this.state.currentUser.employer.id,
    +                                    employer_role: "MANAGER",
    +                                  });
    +                                noti.remove();
    +                              }
    +                            );
    +                          }
    +                        }}
    +                      >
    +                        Make manager
    +                      </Button>
    +                    </Tooltip>
    +
    +                    <Tooltip
    +                      placement="bottom"
    +                      trigger={["hover"]}
    +                      overlay={
    +                        <small>
    +                          Supervisor can create shifts and invite employees to
    +                          work.
    +                        </small>
    +                      }
    +                    >
    +                      <Button
    +                        style={{ background: "white", color: "black" }}
    +                        onClick={() => {
    +                          if (this.state.currentUser.id === u.profile.id)
    +                            Notify.error(
    +                              "You cannot make yourself an supervisor"
    +                            );
    +                          else if (
    +                            this.state.currentUser.employer_role != "ADMIN"
    +                          ) {
    +                            Notify.error(
    +                              "You cannot change role if you are not ADMIN"
    +                            );
    +                          } else {
    +                            const noti = Notify.info(
    +                              "Are you sure you want to make this person Supervisor?",
    +                              (answer) => {
    +                                if (answer)
    +                                  updateUser({
    +                                    id: u.profile.id,
    +                                    employer_id:
    +                                      this.state.currentUser.employer.id,
    +                                    employer_role: "SUPERVISOR",
    +                                  });
    +                                noti.remove();
    +                              }
    +                            );
    +                          }
    +                        }}
    +                      >
    +                        Make Supervisor
    +                      </Button>
    +                    </Tooltip>
    +                    <Button
    +                      icon="trash"
    +                      style={{ background: "white", color: "red" }}
    +                      onClick={() => {
    +                        if (this.state.currentUser.id === u.profile.id)
    +                          Notify.error("You cannot delete yourself");
    +                        else if (
    +                          this.state.currentUser.employer_role != "ADMIN"
    +                        ) {
    +                          Notify.error(
    +                            "You cannot delete if you are not ADMIN"
    +                          );
    +                        } else {
    +                          const noti = Notify.info(
    +                            "Are you sure you want to delete this user?",
    +                            (answer) => {
    +                              if (answer) removeUser(u);
    +                              noti.remove();
    +                            }
    +                          );
    +                        }
    +                      }}
    +                    ></Button>
    +                  </div>
    +                  <p className="mt-2">
    +                    {u.first_name} {u.last_name} ({this.showRole(u.profile)})
    +                  </p>
    +                </GenericCard>
    +              ))}
    +            </span>
    +          )}
    +        </Theme.Consumer>
    +      </div>
    +    );
    +  }
    +}
    +
    +/**
    + * Invite a new user to the company
    + */
    +export const InviteUserToCompanyJobcore = ({
    +  onSave,
    +  onCancel,
    +  onChange,
    +  catalog,
    +  formData,
    +}) => {
    +  const { bar } = useContext(Theme.Context);
    +  const [isNew, setIsNew] = useState(true);
    +  const [selectedUser, setSelectedUser] = useState(null);
    +  const [employer, setEmployer] = useState(Session.getPayload().user.profile);
    +  if (selectedUser) formData.user = selectedUser.value;
    +
    +  return (
    +    <form>
    +      <div className="row">
    +        <div className="col-12">
    +          <p>
    +            <span>Invite someone into your company </span>
    +            <span
    +              className="anchor"
    +              onClick={() =>
    +                bar.show({
    +                  slug: "show_pending_jobcore_invites",
    +                  allowLevels: true,
    +                })
    +              }
    +            >
    +              review previous invites
    +            </span>
    +            :
    +          </p>
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-12 align-content-center justify-content-center text-center mb-4">
    +          <div className="btn-group btn-group-toggle" data-toggle="buttons">
    +            <label className={"btn btn-secondary " + (isNew ? "active" : "")}>
    +              <input
    +                type="radio"
    +                name="options"
    +                id="option1"
    +                autoComplete="off"
    +                onClick={() => setIsNew(true)}
    +                checked={isNew}
    +              />{" "}
    +              New User
    +            </label>
    +            <label className={"btn btn-secondary " + (!isNew ? "active" : "")}>
    +              <input
    +                type="radio"
    +                name="options"
    +                id="option2"
    +                autoComplete="off"
    +                onClick={() => setIsNew(false)}
    +                checked={!isNew}
    +              />{" "}
    +              Existing User
    +            </label>
    +          </div>
    +        </div>
    +      </div>
    +      {isNew ? (
    +        <div>
    +          <div className="row">
    +            <div className="col-12">
    +              <label>First Name</label>
    +              <input
    +                type="text"
    +                className="form-control"
    +                onChange={(e) => onChange({ first_name: e.target.value })}
    +              />
    +            </div>
    +            <div className="col-12">
    +              <label>Last Name</label>
    +              <input
    +                type="text"
    +                className="form-control"
    +                onChange={(e) => onChange({ last_name: e.target.value })}
    +              />
    +            </div>
    +            <div className="col-12">
    +              <label>Email</label>
    +              <input
    +                type="email"
    +                className="form-control"
    +                onChange={(e) => onChange({ email: e.target.value })}
    +              />
    +            </div>
    +            <div className="col-12">
    +              <label>Company Role</label>
    +              <Select
    +                value={catalog.employer_role.find(
    +                  (a) => a.value == formData.employer_role
    +                )}
    +                onChange={(selection) =>
    +                  onChange({ employer_role: selection.value.toString() })
    +                }
    +                options={catalog.employer_role}
    +              />
    +            </div>
    +          </div>
    +          <div className="btn-bar">
    +            <Button color="success" onClick={() => onSave()}>
    +              Send Invite
    +            </Button>
    +            <Button color="secondary" onClick={() => onCancel()}>
    +              Cancel
    +            </Button>
    +          </div>
    +        </div>
    +      ) : (
    +        <div>
    +          <div className="row">
    +            <div className="col-12">
    +              <label>Search people in JobCore:</label>
    +              <SearchCatalogSelect
    +                isMulti={false}
    +                value={selectedUser}
    +                onChange={(selection) => {
    +                  setSelectedUser({
    +                    label: selection.label,
    +                    value: selection.value,
    +                  });
    +                }}
    +                searchFunction={(search) =>
    +                  new Promise((resolve, reject) =>
    +                    GET("catalog/profiles?full_name=" + search)
    +                      .then((talents) =>
    +                        resolve(
    +                          [
    +                            {
    +                              label: `${
    +                                talents.length == 0 ? "No one found: " : ""
    +                              }Invite "${search}" to Company?`,
    +                              value: "invite_talent_to_jobcore",
    +                            },
    +                          ].concat(talents)
    +                        )
    +                      )
    +                      .catch((error) => reject(error))
    +                  )
    +                }
    +              />
    +            </div>
    +
    +            <div className="col-12">
    +              <label>Company Role</label>
    +              <Select
    +                value={catalog.employer_role.find(
    +                  (a) => a.value == formData.employer_role
    +                )}
    +                onChange={(selection) =>
    +                  onChange({ employer_role: selection.value.toString() })
    +                }
    +                options={catalog.employer_role}
    +              />
    +            </div>
    +          </div>
    +          <div className="btn-bar">
    +            <Button
    +              color="success"
    +              onClick={() => {
    +                GET(`employers/me/users/${formData.user}`).then((user) =>
    +                  sendCompanyInvitation(
    +                    user.email,
    +                    employer.employer,
    +                    formData.employer_role,
    +                    employer.id
    +                  )
    +                );
    +              }}
    +            >
    +              Send Invite
    +            </Button>
    +            <Button color="secondary" onClick={() => onCancel()}>
    +              Cancel
    +            </Button>
    +          </div>
    +        </div>
    +      )}
    +    </form>
    +  );
    +};
    +InviteUserToCompanyJobcore.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_ratings.js.html b/docs/views_ratings.js.html new file mode 100644 index 0000000..c900e1c --- /dev/null +++ b/docs/views_ratings.js.html @@ -0,0 +1,560 @@ + + + + + JSDoc: Source: views/ratings.js + + + + + + + + + + +
    + +

    Source: views/ratings.js

    + + + + + + +
    +
    +
    import React, { useContext, useState, useEffect } from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from 'prop-types';
    +import { store, search, fetchTemporal, create, GET, searchMe } from '../actions.js';
    +import { callback, hasTutorial } from '../utils/tutorial';
    +import { GenericCard, Avatar, Stars, Theme, Button, Wizard, StarRating, SearchCatalogSelect, ShiftOption, ShiftOptionSelected, EmployeeExtendedCard } from '../components/index';
    +import Select from 'react-select';
    +import queryString from 'query-string';
    +import { Session } from 'bc-react-session';
    +import moment from 'moment';
    +import { Notify } from 'bc-react-notifier';
    +import { NOW } from "../components/utils.js";
    +import { Talent } from '../views/talents.js';
    +
    +//gets the querystring and creats a formData object to be used when opening the rightbar
    +export const getRatingInitialFilters = (catalog) => {
    +    let query = queryString.parse(window.location.search);
    +    if (typeof query == 'undefined') return {};
    +    if (!Array.isArray(query.positions)) query.positions = (typeof query.positions == 'undefined') ? [] : [query.positions];
    +    if (!Array.isArray(query.badges)) query.badges = (typeof query.badges == 'undefined') ? [] : [query.badges];
    +    return {
    +        positions: query.positions.map(pId => catalog.positions.find(pos => pos.value == pId)),
    +        badges: query.badges.map(bId => catalog.badges.find(b => b.value == bId)),
    +        rating: catalog.stars.find(rate => rate.value == query.rating)
    +    };
    +};
    +
    +export const Rating = (data) => {
    +
    +    const session = Session.getPayload();
    +    const _defaults = {
    +        //foo: 'bar',
    +        serialize: function () {
    +
    +            const newRating = {
    +                comments: '',
    +                rating: 5,
    +                sender: null,
    +                shift: null,
    +                created_at: NOW()
    +            };
    +
    +            return Object.assign(this, newRating);
    +        },
    +        unserialize: function () {
    +            //this.fullName = function() { return (this.user.first_name.length>0) ? this.user.first_name + ' ' + this.user.last_name : 'No name specified'; };
    +            let shift = null;
    +            if (this.shift) {
    +                shift = {
    +                    ...this.shift,
    +                    starting_at: moment(this.shift.starting_at),
    +                    ending_at: moment(this.shift.ending_at)
    +                };
    +            }
    +
    +            return { ...this, shift };
    +        }
    +
    +    };
    +
    +    let _entity = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +
    +            return _entity;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        },
    +        getFormData: () => {
    +            const _formRating = {
    +                id: _entity.id,
    +                comments: _entity.comments,
    +                rating: _entity.rating,
    +                employee: _entity.employee,
    +
    +                // if more than one employee will be rated for the same shift
    +                employees_to_rate: _entity ? _entity.employees : [],
    +
    +                sender: _entity.sender,
    +                shift: _entity.shift ?
    +                    {
    +                        ..._entity.shift,
    +                        starting_at: moment(_entity.shift.starting_at),
    +                        ending_at: moment(_entity.shift.ending_at)
    +                    }
    +                    : null,
    +                created_at: (moment.isMoment(_entity.created_at)) ? _entity.created_at : moment(_entity.created_at)
    +            };
    +            return _formRating;
    +        },
    +        filters: () => {
    +            const _filters = {
    +                //positions: _entity.positions.map( item => item.value ),
    +            };
    +            for (let key in _entity) if (typeof _entity[key] == 'function') delete _entity[key];
    +            return Object.assign(_entity, _filters);
    +        }
    +    };
    +};
    +
    +export class ManageRating extends Flux.DashView {
    +
    +    constructor() {
    +        super();
    +        this.state = {
    +            ratings: [],
    +            runTutorial: hasTutorial(),
    +            steps: [],
    +            employer: null
    +        };
    +    }
    +
    +    componentDidMount() {
    +
    +
    +        // this.filter();
    +   
    +        this.props.history.listen(() => {
    +            this.filter();
    +            this.setState({ firstSearch: false });
    +        });
    +        this.setState({ runTutorial: true });
    +
    +        fetchTemporal('employers/me', 'current_employer');
    +        this.subscribe(store, 'current_employer', (employer) => {
    +            searchMe(`ratings`, `?employer=${employer.id}`);
    +            this.setState({ employer });
    +        });
    +
    +        const ratings = store.getState('ratings');
    +        this.subscribe(store, 'ratings', (_ratings) => {
    +            this.setState({ ratings:_ratings });
    +        });
    +    }
    +
    +    filter(ratings = null) {
    +        search('ratings', `?employer=${this.state.employer.id}`);
    +    }
    +
    +    render() {
    +        console.log('state',this.state);
    +        return (<div className="p-1 listcontents">
    +            <Theme.Consumer>
    +                {({ bar }) => (<span>
    +                    <Wizard continuous
    +                        steps={this.state.steps}
    +                        run={this.state.runTutorial}
    +                        callback={callback}
    +                    />
    +                    <h2>Company Ratings</h2>
    +                    <div className="row mt-2">
    +                        <div className="col-6">
    +                            <label>Total Ratings</label>
    +                            <p>You have been rated <span className="text-success">{this.state.ratings.length} times.</span></p>
    +                        </div>
    +                        <div className="col-6">
    +                            <label>Rating</label>
    +                            <p>Talents rated you with <span className="text-success">{this.state.employer ? this.state.employer.rating : 0} points avg.</span></p>
    +                        </div>
    +                    </div>
    +                    <div className="row">
    +                        <div className="col-12">
    +                            <h3>Recent Ratings</h3>
    +                            {this.state.ratings.filter(emp => !emp.employee && emp.shift).map((rate, i) => (
    +                                <GenericCard key={i} hover={true} onClick={() => bar.show({ slug: "show_single_rating", data: rate, allowLevels: false })}>
    +                                    <Avatar url={rate.sender.picture} />
    +                                    <Stars className="float-left" rating={Number(rate.rating)} />
    +                                    <span className="pl-1">{`on ${rate.created_at.substring(0, 10)}`}</span>
    +                                    <p className="mt-0">{rate.comments !== '' ? `"${rate.comments}"` : `The talent didn't provide any comments for this rating.`}</p>
    +                                </GenericCard>
    +                            ))}
    +                        </div>
    +                    </div>
    +                </span>)}
    +            </Theme.Consumer>
    +        </div>);
    +    }
    +}
    +
    +
    +/**
    + * Talent Details
    + */
    +export const RatingDetails = (props) => {
    +    const { formData } = props;
    +    const { shift } = formData;
    +   
    +    return (<Theme.Consumer>
    +        {({ bar }) =>
    +            (<li className="aplication-details">
    +                <Avatar url={formData.sender.picture} />
    +                <p>{formData.sender.user.first_name + ' ' + formData.sender.user.last_name}</p>
    +                <div>
    +                    <Stars rating={Number(formData.rating)} />
    +                </div>
    +                <h5 className="mt-3">
    +                    {formData.comments !== '' ? `"${formData.comments}"` : `${formData.sender.user.first_name} didn't provide any comments for this rating.`}
    +                </h5>
    +                {!shift || typeof shift.position === 'undefined' ?
    +                    'Loading shift information...' :
    +                    <div>
    +                        <a href="#" className="shift-position">{shift.position.title}</a> @
    +                        <a href="#" className="shift-location"> {shift.venue.title}</a>
    +                        <span className="shift-date"> {shift.starting_at.format('ll')} from {shift.starting_at.format('LT')} to {shift.ending_at.format('LT')} </span>
    +                    </div>
    +                }
    +                <Button color="primary" onClick={() => bar.show({ slug: "review_single_talent", data: formData, allowLevels: false })}>Rate talent back</Button>
    +            </li>)}
    +    </Theme.Consumer>);
    +};
    +RatingDetails.propTypes = {
    +    catalog: PropTypes.object.isRequired,
    +    formData: PropTypes.object
    +};
    +
    +
    +/**
    + * Talent Details
    + */
    +export const PendingRatings = (props) => {
    +    return (<Theme.Consumer>
    +        {({ bar }) =>
    +            (<li className="aplication-details">
    +            </li>)}
    +    </Theme.Consumer>);
    +};
    +PendingRatings.propTypes = {
    +    catalog: PropTypes.object.isRequired,
    +    formData: PropTypes.object
    +};
    +
    +
    +/**
    + * Revire Talent for a specific shift
    + */
    +export const ReviewTalentAndShift = (props) => {
    +    const shift = props.formData.shift;
    +    const employee = props.formData.employee;
    +    const startDate = shift.starting_at.format('ll');
    +    const startTime = shift.starting_at.format('LT');
    +    const endTime = shift.ending_at.format('LT');
    +    return (<Theme.Consumer>
    +        {({ bar }) =>
    +            (<li className="aplication-details">
    +                <h4>How satisfied are you with {employee.user.first_name}{"'"}s performance during this shift?</h4>
    +                <p className="mb-3">
    +                    <Avatar url={employee.user.profile.picture} />
    +                </p>
    +                <a href="#" className="shift-position">{shift.position.title}</a> @
    +                <a href="#" className="shift-location"> {shift.venue.title}</a>
    +                <span className="shift-date"> {startDate} from {startTime} to {endTime} </span>
    +                {
    +                    (typeof shift.price == 'string') ?
    +                        <span className="shift-price"> ${shift.price}</span>
    +                        :
    +                        <span className="shift-price"> {shift.price.currencySymbol}{shift.price.amount}</span>
    +                }
    +                <StarRating
    +                    placeholderRating={Number(employee.rating ? employee.rating : 1)}
    +                    emptySymbol="fa fa-star-o fa-2x"
    +                    fullSymbol="fa fa-star fa-2x"
    +                    placeholderSymbol={"fa fa-star fa-2x"}
    +                />
    +                <textarea className="form-control mt-3" placeholder={`Please describe further your experiences with ${employee.user.first_name}`}>
    +                </textarea>
    +                <div className="btn-bar">
    +                    <Button color="secondary" onClick={() => bar.close()}>Cancel</Button>
    +                    <Button color="primary" onClick={() => null}>Send</Button>
    +                </div>
    +            </li>)}
    +    </Theme.Consumer>);
    +};
    +ReviewTalentAndShift.propTypes = {
    +    catalog: PropTypes.object.isRequired,
    +    formData: PropTypes.object
    +};
    +
    +
    +/**
    + * Review Talent in general
    + */
    +export const RatingEmployees = (props) => {
    +
    +    const { onCancel, onSave, catalog, formData } = props;
    +
    +    const shiftEmployees = formData.shift.employees.map((e) => {
    +        if (formData.ratings.find(rated => rated.employee == e.id)) {
    +            var ratedEmployee = Object.assign({}, e);
    +            ratedEmployee.rating = formData.ratings.find(rated => rated.employee == e.id).rating;
    +            ratedEmployee.created_at = formData.ratings.find(rated => rated.employee == e.id).created_at;
    +            return ratedEmployee;
    +        } else {
    +            return e;
    +        }
    +    });
    +
    +    return (<Theme.Consumer>
    +        {({ bar }) => (<div className="sidebar-applicants">
    +            {shiftEmployees.find(e => !e.rating) ? (
    +                <div className="top-bar">
    +                    <button type="button" className="btn btn-primary btn-sm"
    +                        onClick={() => bar.show({ slug: "review_talent", data: { shift: formData.shift, employees: shiftEmployees.filter(e => !e.rating) }, allowLevels: true })}
    +
    +                    >
    +                        Rate employee
    +                    </button>
    +                </div>
    +            ) : (
    +                    null
    +                )}
    +
    +
    +            <h3>Shift Ratings:</h3>
    +            <ul style={{ overflowY: "auto", maxHeight: "75vh" }}>
    +                {
    +                    shiftEmployees.length > 0 ?
    +                        shiftEmployees.map((tal, i) => (
    +                            <GenericCard key={i} hover={true}>
    +                                <Avatar url={tal.user.profile.picture} />
    +                                <a href="#"><b>{tal.user.first_name + ' ' + tal.user.last_name}</b></a>
    +
    +                                <Stars rating={Number(tal.rating)} noRatingLabel="Not yet rated for this shift" />
    +                                {
    +                                    tal.rating ? null : (
    +                                        <div className="btn-group" role="group" aria-label="Basic example">
    +                                            <Button
    +                                                className="mt-0 text-white" label="Rate"
    +                                                notePosition="left" note="Rate Employee"
    +                                                onClick={() => bar.show({ slug: "review_talent", data: { shift: formData.shift, employees: [tal] }, allowLevels: true })}
    +                                            >
    +                                                Rate
    +                                            </Button>
    +
    +                                        </div>
    +
    +                                    )
    +                                }
    +                            </GenericCard>
    +
    +                        ))
    +                        :
    +                        <li>No ratings were found for this shift</li>
    +                }
    +            </ul>
    +        </div>)}
    +    </Theme.Consumer>);
    +};
    +RatingEmployees.propTypes = {
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    catalog: PropTypes.object, //contains the data needed for the form to load
    +    formData: PropTypes.object, //contains the data needed for the form to load
    +    context: PropTypes.object //contact any additional data for context purposes
    +};
    +export const UnratingEmployees = (props) => {
    +
    +    const { onCancel, onSave, catalog, formData } = props;
    +    const unrated_employees = formData.employees.filter(e => !e.rating);
    +    return (<Theme.Consumer>
    +        {({ bar }) => (<div className="sidebar-applicants">
    +
    +            <div className="top-bar">
    +                <button type="button" className="btn btn-primary btn-sm"
    +                    onClick={() => bar.show({ slug: "review_talent", data: catalog.shift, allowLevels: true })}
    +
    +                >
    +                    Rate employee
    +                </button>
    +            </div>
    +
    +            <h3>Shift Ratings:</h3>
    +            <ul style={{ overflowY: "auto", maxHeight: "75vh" }}>
    +                {
    +                    unrated_employees.length > 0 ?
    +                        unrated_employees.map((tal, i) => (
    +                            <EmployeeExtendedCard
    +                                key={i}
    +                                employee={tal}
    +                                hover={false}
    +                                showFavlist={false}
    +                            >
    +
    +                            </EmployeeExtendedCard>)
    +                        )
    +                        :
    +                        <li>No ratings were found for this shift</li>
    +                }
    +            </ul>
    +        </div>)}
    +    </Theme.Consumer>);
    +};
    +UnratingEmployees.propTypes = {
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    catalog: PropTypes.object, //contains the data needed for the form to load
    +    formData: PropTypes.object, //contains the data needed for the form to load
    +    context: PropTypes.object //contact any additional data for context purposes
    +};
    +
    +
    +export const ReviewTalent = ({ onSave, onCancel, onChange, catalog, formData, error }) => {
    +
    +    console.log(formData);
    +
    +    const [shifts, setShifts] = useState([]);
    +    const [rating, setRating] = useState(0);
    +    const [comments, setComments] = useState('');
    +    const [employeesToRate, setEmployeesToRate] = useState(formData.employees_to_rate.map(e => (
    +        {
    +            label: e.user.first_name + " " + e.user.last_name,
    +            value: e.id
    +        }
    +    )));
    + 
    +    return (<Theme.Consumer>
    +        {({ bar }) => (
    +            < form >
    +                <div className="row">
    +                    <div className="col-12">
    +                        <label>Who worked on this shift?</label>
    +
    +                        <Select
    +                            isMulti
    +                            value={employeesToRate}
    +                            onChange={(employees) => setEmployeesToRate(employees)}
    +                            options={formData.employees_to_rate.map(e => ({
    +                                label: e.user.first_name + " " + e.user.last_name,
    +                                value: e.id
    +                            }))}
    +
    +                        />
    +
    +                    </div>
    +                </div>
    +                {/* <div className="row">
    +                    <div className="col-12">
    +
    +                        <label>What shift was it working?</label>
    +                        <Select
    +                            value={{ value: formData.shift }}
    +                            components={{ Option: ShiftOption, SingleValue: ShiftOptionSelected({ multi: false }) }}
    +                            onChange={(selection) => onChange({ shift: selection.value.toString() })}
    +                            options={[]}
    +                        />
    +                    </div>
    +                </div> */}
    +                <div className="row">
    +                    <div className="col-12">
    +                        <label>How was his performance during the shift</label>
    +                        <StarRating
    +                            onClick={(e) =>
    +                                setRating(e)
    +                            }
    +                            onHover={() => null}
    +                            direction="right"
    +                            fractions={2}
    +                            quiet={false}
    +                            readonly={false}
    +                            totalSymbols={5}
    +                            value={rating}
    +                            placeholderValue={0}
    +                            placeholderRating={Number(0)}
    +                            emptySymbol="far fa-star md"
    +                            fullSymbol="fas fa-star"
    +                            placeholderSymbol={"fas fa-star"}
    +                        />
    +                    </div>
    +                </div>
    +                <div className="row">
    +                    <div className="col-12">
    +                        <label>Any comments?</label>
    +                        <textarea className="form-control" onChange={e => setComments(e.target.value)}></textarea>
    +                    </div>
    +                </div>
    +                <div className="btn-bar">
    +                    <Button color="success"
    +                        onClick={() => create('ratings',
    +                            // {
    +                            //     employee: formData.employees_to_rate.map(e => e.id),
    +                            //     shifts: formData.shift.id,
    +                            //     rating: rating,
    +                            //     comments: comments
    +                            // }
    +                            formData.employees_to_rate.map(e => ({
    +                                employee: e.id,
    +                                shift: formData.shift.id,
    +                                rating: rating,
    +                                comments: comments
    +                            }))
    +                        ).then((res) => bar.close("last"))
    +                            .catch(e => Notify.error(e.message || e))}>Send Review</Button>
    +                </div>
    +            </form>
    +        )}
    +    </Theme.Consumer>
    +    );
    +};
    +ReviewTalent.propTypes = {
    +    error: PropTypes.string,
    +    bar: PropTypes.object,
    +    onSave: PropTypes.func.isRequired,
    +    onCancel: PropTypes.func.isRequired,
    +    onChange: PropTypes.func.isRequired,
    +    formData: PropTypes.object,
    +    catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +ReviewTalent.defaultProps = {
    +    oldShift: null
    +};
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_shifts.js.html b/docs/views_shifts.js.html new file mode 100644 index 0000000..b95598c --- /dev/null +++ b/docs/views_shifts.js.html @@ -0,0 +1,3261 @@ + + + + + JSDoc: Source: views/shifts.js + + + + + + + + + + +
    + +

    Source: views/shifts.js

    + + + + + + +
    +
    +
    import React, { useContext, useState, useEffect } from "react";
    +import { Link } from "react-router-dom";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import {
    +  store,
    +  create,
    +  searchMe,
    +  fetchAllMe,
    +  deleteShiftEmployee,
    +} from "../actions.js";
    +import PropTypes from "prop-types";
    +import _ from "underscore";
    +
    +import Select from "react-select";
    +
    +import DateTime from "react-datetime";
    +import TimePicker from "rc-time-picker";
    +
    +import { Notify } from "bc-react-notifier";
    +import queryString from "query-string";
    +import {
    +  ShiftCard,
    +  Wizard,
    +  Theme,
    +  SearchCatalogSelect,
    +  Button,
    +  ApplicantCard,
    +  GenericCard,
    +  EmployeeExtendedCard,
    +  Avatar,
    +} from "../components/index";
    +import {
    +  DATETIME_FORMAT,
    +  TIME_FORMAT,
    +  NOW,
    +  TODAY,
    +  YESTERDAY,
    +} from "../components/utils.js";
    +import { validator, ValidationError } from "../utils/validation";
    +import { callback, hasTutorial } from "../utils/tutorial";
    +import { AddOrEditLocation } from "../views/locations.js";
    +import { ShiftInvite, Talent } from "../views/talents.js";
    +import TextareaAutosize from "react-textarea-autosize";
    +
    +import moment from "moment";
    +import { GET } from "../utils/api_wrapper";
    +const SHIFT_POSSIBLE_STATUS = ["UNDEFINED", "DRAFT", "OPEN", "CANCELLED"];
    +
    +//gets the querystring and creats a formData object to be used when opening the rightbar
    +export const getShiftInitialFilters = () => {
    +  return {};
    +};
    +
    +export const Shift = (data) => {
    +  const _defaults = {
    +    position: "",
    +    maximum_allowed_employees: "1",
    +    application_restriction: "ANYONE",
    +    minimum_hourly_rate: "8",
    +    starting_at: moment().add(15 - (moment().minute() % 15), "minutes"),
    +    ending_at: moment().add(15 - (moment().minute() % 15) + 120, "minutes"),
    +    employees: [],
    +    pending_invites: [],
    +    pending_jobcore_invites: [],
    +    candidates: [],
    +    allowed_from_list: [],
    +    allowedFavlists: [],
    +    allowedTalents: [],
    +    minimum_allowed_rating: "0",
    +    venue: "",
    +    status: "UNDEFINED",
    +    withStatus: function (newStatus) {
    +      if (typeof newStatus === "string") this.status = newStatus;
    +      else throw new Error("Invalid status " + newStatus);
    +
    +      return this;
    +    },
    +    serialize: function () {
    +      let newShift = {
    +        status: this.status == "UNDEFINED" ? "DRAFT" : this.status,
    +        // starting_at: (moment.isMoment(this.starting_at)) ? this.starting_at.format(DATETIME_FORMAT) : this.starting_at,
    +        // ending_at: (moment.isMoment(this.ending_at)) ? this.ending_at.format(DATETIME_FORMAT) : this.ending_at,
    +        starting_at: moment(this.starting_at),
    +        ending_at: moment(this.ending_at),
    +        allowed_from_list: this.allowedFavlists.map((f) => f.value),
    +        multiple_dates:
    +          Array.isArray(this.multiple_dates) && this.multiple_dates.length > 0
    +            ? this.multiple_dates
    +            : undefined,
    +      };
    +
    +      // this is not ready yet
    +      delete newShift.required_badges;
    +      //this is a special property used on the form for creating an expried (past) shift and adding the employess right away
    +      if (Array.isArray(this.employeesToAdd))
    +        newShift.employees = this.employeesToAdd.map((e) => e.value || e.id);
    +      return Object.assign(this, newShift);
    +    },
    +    unserialize: function () {
    +      const dataType = typeof this.starting_at;
    +      //if its already serialized
    +      if (
    +        typeof this.position == "object" &&
    +        ["number", "string"].indexOf(dataType) == -1
    +      )
    +        return this;
    +      const newShift = {
    +        position:
    +          typeof this.position != "object"
    +            ? store.get("positions", this.position)
    +            : this.position,
    +        venue:
    +          typeof this.venue != "object"
    +            ? store.get("venues", this.venue)
    +            : this.venue,
    +        starting_at: !moment.isMoment(this.starting_at)
    +          ? moment(this.starting_at)
    +          : this.starting_at,
    +        ending_at: !moment.isMoment(this.ending_at)
    +          ? moment(this.ending_at)
    +          : this.ending_at,
    +        allowedFavlists: this.allowed_from_list.map((fav) => {
    +          const list = store.get("favlists", fav.id || fav);
    +          return list
    +            ? { value: list.id, label: list.title, title: list.title }
    +            : null;
    +        }),
    +        expired: moment(this.ending_at).isBefore(NOW()),
    +        price: {
    +          currency: "usd",
    +          currencySymbol: "$",
    +          amount: this.minimum_hourly_rate,
    +          timeframe: "hr",
    +        },
    +      };
    +      return Object.assign(this, newShift);
    +    },
    +  };
    +
    +  let _shift = Object.assign(_defaults, data);
    +  return {
    +    get: () => {
    +      return _shift;
    +    },
    +    validate: () => {
    +      const start = _shift.starting_at;
    +      const finish = _shift.ending_at;
    +
    +      if (_shift.status == "CANCELLED") return _shift;
    +
    +      if (!validator.isInt(_shift.position.toString(), { min: 1 }))
    +        throw new ValidationError("The shift is missing a position");
    +      if (
    +        !validator.isInt(_shift.maximum_allowed_employees.toString(), {
    +          min: 1,
    +          max: 25,
    +        })
    +      )
    +        throw new ValidationError(
    +          "The shift needs to employ at least 1 talent and no more than 25"
    +        );
    +      if (!validator.isFloat(_shift.minimum_hourly_rate.toString(), { min: 7 }))
    +        throw new ValidationError("The minimum allowed hourly rate is $7");
    +      // if (!start.isValid() || start.isBefore(NOW())) throw new ValidationError('The shift date has to be greater than today');
    +      if (!finish.isValid() || finish.isBefore(start))
    +        throw new ValidationError(
    +          "The shift ending time has to be grater than the starting time"
    +        );
    +      if (!validator.isInt(_shift.venue.toString(), { min: 1 }))
    +        throw new ValidationError("The shift is missing a venue");
    +      if (SHIFT_POSSIBLE_STATUS.indexOf(_shift.status) == -1)
    +        throw new Error('Invalid status "' + _shift.status + '" for shift');
    +
    +      return _shift;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      const _formShift = {
    +        id: _shift.id.toString(),
    +        pending_jobcore_invites: _shift.pending_jobcore_invites,
    +        application_restriction: _shift.application_restriction,
    +        pending_invites:
    +          typeof _shift.pending_invites == "undefined"
    +            ? []
    +            : _shift.pending_invites,
    +        position: _shift.position.id.toString() || _shift.position.toString(),
    +        maximum_allowed_employees: _shift.maximum_allowed_employees.toString(),
    +        minimum_hourly_rate: _shift.minimum_hourly_rate.toString(),
    +        starting_at: _shift.starting_at,
    +        ending_at: _shift.ending_at,
    +        status: _shift.status,
    +        allowedFavlists: _shift.allowedFavlists,
    +        start_time: moment.isMoment(_shift.starting_at)
    +          ? _shift.starting_at
    +          : moment(_shift.starting_at + " " + _shift.starting_at).format(
    +              "MM/DD/YYYY"
    +            ),
    +        finish_time: moment.isMoment(_shift.starting_at)
    +          ? _shift.ending_at
    +          : moment(_shift.ending_at + " " + _shift.ending_at).format(
    +              "MM/DD/YYYY"
    +            ),
    +        minimum_allowed_rating: _shift.minimum_allowed_rating.toString(),
    +        venue: _shift.venue.id.toString() || _shift.venue.toString(),
    +        employees: _shift.employees,
    +      };
    +      return _formShift;
    +    },
    +  };
    +};
    +
    +export class ManageShifts extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      shifts: [],
    +      showNextButton: true,
    +      offset: 10,
    +      runTutorial: hasTutorial(),
    +      steps: [
    +        {
    +          target: "#shift-details-header",
    +          content: "Here you can see your entire list of shifts",
    +          placement: "right",
    +        },
    +        {
    +          target: "#create_shift",
    +          content: "You can also create new shifts",
    +          placement: "left",
    +        },
    +        {
    +          target: "#filter_shift",
    +          content: "Or filter them for better browsing",
    +          placement: "left",
    +        },
    +      ],
    +    };
    +  }
    +
    +  componentDidMount() {
    +    let status = queryString.parse(window.location.search, {
    +      arrayFormat: "index",
    +    });
    +
    +    // fetch if not loaded already
    +    let shifts = store.getState("shifts");
    +
    +    if (status.status) {
    +      searchMe(
    +        `shifts`,
    +        `?envelope=true&limit=10&${
    +          status.status == "FILLED"
    +            ? "filled=true&upcoming=true&not_status=DRAFT"
    +            : "status=" + status.status
    +        }`
    +      ).then((data) => {
    +        const showNextButton = data.next !== null;
    +        this.setState({ showNextButton });
    +      });
    +    } else {
    +      searchMe(`shifts`, `?envelope=true&limit=10`).then((data) => {
    +        const showNextButton = data.next !== null;
    +        this.setState({ showNextButton });
    +      });
    +    }
    +    this.subscribe(store, "shifts", (shifts) => {
    +      this.filterShifts(shifts);
    +    });
    +
    +    this.props.history.listen(() => {
    +      this.filterShifts();
    +    });
    +    this.setState({ runTutorial: true });
    +  }
    +
    +  filterShifts(shifts = null) {
    +    let filters = this.getFilters();
    +    if (!shifts) shifts = store.getState("shifts");
    +    if (Array.isArray(shifts) && shifts.length > 0)
    +      this.setState({ shifts: shifts });
    +    else this.setState({ shifts: [] });
    +  }
    +
    +  getFilters() {
    +    let filters = queryString.parse(window.location.search, {
    +      arrayFormat: "index",
    +    });
    +    for (let f in filters) {
    +      switch (f) {
    +        case "status":
    +          filters[f] = {
    +            value: filters[f],
    +            matches: (shift) => {
    +              let values = filters.status.value.split(",");
    +              if (values.length == 1) {
    +                if (values.includes("OPEN")) {
    +                  if (
    +                    moment(shift.ending_at).isBefore(NOW()) || //has passed
    +                    shift.maximum_allowed_employees <= shift.employees.length //or its filled
    +                  ) {
    +                    return false;
    +                  }
    +                } else if (values.includes("FILLED")) {
    +                  if (shift.maximum_allowed_employees > shift.employees.length)
    +                    return false;
    +                } else if (values.includes("EXPIRED")) {
    +                  if (moment(shift.ending_at).isAfter(NOW())) return false;
    +                  else values.push("OPEN");
    +                }
    +              }
    +              return values.includes(shift.status);
    +            },
    +          };
    +          break;
    +        case "position":
    +          filters[f] = {
    +            value: filters[f],
    +            matches: (shift) => {
    +              if (!filters.position.value) return true;
    +              if (isNaN(filters.position.value)) return true;
    +              return shift.position.id == filters.position.value;
    +            },
    +          };
    +          break;
    +        case "talent":
    +          filters[f] = {
    +            value: filters[f],
    +            matches: (shift) => {
    +              const emp = shift.employees.find(
    +                (e) => e.id == filters.talent.value
    +              );
    +              return emp;
    +            },
    +          };
    +          break;
    +        case "venue":
    +          filters[f] = {
    +            value: filters[f],
    +            matches: (shift) => {
    +              if (!filters.venue.value) return true;
    +              if (isNaN(filters.venue.value)) return true;
    +              return shift.venue.id == filters.venue.value;
    +            },
    +          };
    +          break;
    +        case "minimum_hourly_rate":
    +          filters[f] = {
    +            value: filters[f],
    +            matches: (shift) => {
    +              if (!filters.minimum_hourly_rate.value) return true;
    +              if (isNaN(filters.minimum_hourly_rate.value)) return true;
    +              return (
    +                parseInt(shift.minimum_hourly_rate, 10) >=
    +                filters.minimum_hourly_rate.value
    +              );
    +            },
    +          };
    +          break;
    +        case "date":
    +          filters[f] = {
    +            value: filters[f],
    +            matches: (shift) => {
    +              const fdate = moment(filters.date.value);
    +              return shift.starting_at.diff(fdate, "days") == 0;
    +            },
    +          };
    +          break;
    +        default:
    +          throw new Error("Invalid filter");
    +      }
    +    }
    +    return filters;
    +  }
    +
    +  render() {
    +    let status = queryString.parse(window.location.search, {
    +      arrayFormat: "index",
    +    });
    +    const groupedShifts = _.groupBy(this.state.shifts, (s) =>
    +      moment(s.starting_at).format("MMMM YYYY")
    +    );
    +    const shiftsHTML = [];
    +
    +    for (let date in groupedShifts) {
    +      shiftsHTML.push(
    +        <div key={date} className="date-group">
    +          <p className="date-group-label">{date}</p>
    +          <div>
    +            {groupedShifts[date].map((s, i) => (
    +              <ShiftCard key={i} shift={s} showStatus={true} />
    +            ))}
    +          </div>
    +        </div>
    +      );
    +    }
    +    return (
    +      <div className="p-1 listcontents">
    +        {/* <Wizard continuous
    +                steps={this.state.steps}
    +                run={this.state.runTutorial}
    +                callback={callback}
    +                showProgress
    +            /> */}
    +        <h1 className="float-left">
    +          <span id="shift-details-header">Shift Details</span>
    +        </h1>
    +        {shiftsHTML.length == 0 && (
    +          <div className="mt-5">No shifts have been found</div>
    +        )}
    +        {shiftsHTML}
    +        {this.state.showNextButton && shiftsHTML.length != 0 ? (
    +          <div className="row text-center w-100 mt-3">
    +            <div className="col">
    +              <Button
    +                onClick={() => {
    +                  const PAGINATION_LENGTH = 10;
    +                  const NOT_FILLED_SHIFT = `&status=${status.status}`;
    +                  const FILLED_SHIFT =
    +                    "&filled=true&upcoming=true&not_status=DRAFT&envelope=true";
    +                  if (status.status) {
    +                    searchMe(
    +                      `shifts`,
    +                      `?envelope=true&limit=10&offset=${
    +                        this.state.offset + PAGINATION_LENGTH
    +                      }${
    +                        status.status == "FILLED"
    +                          ? FILLED_SHIFT
    +                          : NOT_FILLED_SHIFT
    +                      }`,
    +                      this.state.shifts
    +                    ).then((newShifts) => {
    +                      const showNextButton = newShifts.next !== null;
    +                      this.setState({
    +                        shifts: newShifts,
    +                        offset: this.state.offset + PAGINATION_LENGTH,
    +                        showNextButton,
    +                      });
    +                    });
    +                  } else {
    +                    searchMe(
    +                      `shifts`,
    +                      `?envelope=true&limit=10&offset=${
    +                        this.state.offset + PAGINATION_LENGTH
    +                      }`,
    +                      this.state.shifts
    +                    ).then((newShifts) => {
    +                      const showNextButton = newShifts.next !== null;
    +                      this.setState({
    +                        shifts: newShifts,
    +                        offset: this.state.offset + PAGINATION_LENGTH,
    +                        showNextButton,
    +                      });
    +                    });
    +                  }
    +                }}
    +              >
    +                Load More
    +              </Button>
    +            </div>
    +          </div>
    +        ) : null}
    +      </div>
    +    );
    +  }
    +}
    +
    +/**
    + * FilterShifts
    + */
    +export const FilterShifts = ({ onSave, onCancel, onChange, catalog }) => {
    +  const [position, setPosition] = useState("");
    +  const [date, setDate] = useState("");
    +  const [price, setPrice] = useState("");
    +  const [employees, setEmployees] = useState("");
    +  const [location, setLocation] = useState("");
    +  const [status, setStatus] = useState("");
    +  useEffect(() => {
    +    const venues = store.getState("venues");
    +    if (!venues) fetchAllMe(["venues"]);
    +  }, []);
    +
    +  return (
    +    <form>
    +      <div className="row">
    +        <div className="col">
    +          <label>Looking for</label>
    +          <Select
    +            onChange={(selection) => setPosition(selection.value)}
    +            options={catalog.positions}
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-8">
    +          <label>Date</label>
    +          <DateTime
    +            timeFormat={false}
    +            className="w-100"
    +            closeOnSelect={true}
    +            renderInput={(properties) => {
    +              const { value, ...rest } = properties;
    +              return (
    +                <input value={value.match(/\d{2}\/\d{2}\/\d{4}/gm)} {...rest} />
    +              );
    +            }}
    +            onChange={(value) => {
    +              if (typeof value == "string") {
    +                value = moment(value);
    +                setDate(value.format("YYYY-MM-DD"));
    +              }
    +            }}
    +          />
    +        </div>
    +        <div className="col-4">
    +          <label>Price / hour</label>
    +          <input
    +            type="number"
    +            className="form-control"
    +            onChange={(e) => setPrice(e.target.value)}
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col">
    +          <label>Location</label>
    +          <Select
    +            onChange={(selection) => setLocation(selection.value.toString())}
    +            options={catalog.venues}
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col">
    +          <label>Worked by a talent:</label>
    +          <SearchCatalogSelect
    +            isMulti={true}
    +            value={employees}
    +            onChange={(selections) => {
    +              setEmployees(selections);
    +            }}
    +            searchFunction={(search) =>
    +              new Promise((resolve, reject) =>
    +                GET("catalog/employees?full_name=" + search)
    +                  .then((talents) =>
    +                    resolve(
    +                      [
    +                        {
    +                          label: `${
    +                            talents.length == 0 ? "No one found: " : ""
    +                          }`,
    +                          value: "invite_talent_to_jobcore",
    +                        },
    +                      ].concat(talents)
    +                    )
    +                  )
    +                  .catch((error) => reject(error))
    +              )
    +            }
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col">
    +          <label>Status</label>
    +          <Select
    +            onChange={(selection) => setStatus(selection.value.toString())}
    +            options={catalog.shiftStatus}
    +          />
    +        </div>
    +      </div>
    +      <div className="btn-bar">
    +        <button
    +          type="button"
    +          className="btn btn-primary"
    +          onClick={() => {
    +            const employeesList =
    +              employees != ""
    +                ? `&employee=${employees.map((e) => e.value)}`
    +                : "";
    +            searchMe(
    +              `shifts`,
    +              `?${
    +                status == "FILLED"
    +                  ? "filled=true&upcoming=true&not_status=DRAFT"
    +                  : "status=" + status
    +              }&envelope=true&limit=10&position=${position}&venue=${location}&start=${date}${employeesList}`
    +            );
    +          }}
    +        >
    +          Apply Filters
    +        </button>
    +        <button
    +          type="button"
    +          className="btn btn-secondary"
    +          onClick={() => onSave(false)}
    +        >
    +          Clear Filters
    +        </button>
    +      </div>
    +    </form>
    +  );
    +};
    +FilterShifts.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  formData: PropTypes.object, //contains the data needed for the form to load
    +  onChange: PropTypes.func.isRequired,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +/**
    + * ShiftApplicants
    + */
    +export const ShiftApplicants = (props) => {
    +  const { onCancel, onSave, catalog } = props;
    +  return (
    +    <Theme.Consumer>
    +      {({ bar }) => (
    +        <div className="sidebar-applicants">
    +          {catalog.shift.expired ? (
    +            <div className="alert alert-warning">
    +              This shift has already expired
    +            </div>
    +          ) : (
    +            <div className="top-bar">
    +              <button
    +                type="button"
    +                className="btn btn-primary btn-sm"
    +                onClick={() =>
    +                  bar.show({
    +                    slug: "search_talent_and_invite_to_shift",
    +                    data: { shifts: [catalog.shift], employees: [] },
    +                    allowLevels: true,
    +                  })
    +                }
    +              >
    +                invite
    +              </button>
    +            </div>
    +          )}
    +          <h3>Shift applicants:</h3>
    +          <ul style={{ overflowY: "auto", maxHeight: "75vh" }}>
    +            {catalog.applicants.length > 0 ? (
    +              catalog.applicants.map((tal, i) => (
    +                <EmployeeExtendedCard
    +                  key={i}
    +                  employee={tal}
    +                  hover={false}
    +                  showFavlist={false}
    +                  onClick={() =>
    +                    bar.show({
    +                      slug: "show_single_talent",
    +                      data: Talent(tal, i).defaults().unserialize(),
    +                      allowLevels: true,
    +                    })
    +                  }
    +                >
    +                  {!catalog.shift.expired && (
    +                    <Button
    +                      className="mt-0"
    +                      icon="check"
    +                      label="Delete"
    +                      onClick={() =>
    +                        onSave({
    +                          executed_action: "accept_applicant",
    +                          applicant: tal,
    +                          shift: catalog.shift,
    +                        })
    +                      }
    +                    />
    +                  )}
    +                  {!catalog.shift.expired && (
    +                    <Button
    +                      className="mt-0"
    +                      icon="times"
    +                      label="Delete"
    +                      onClick={() =>
    +                        onSave({
    +                          executed_action: "reject_applicant",
    +                          applicant: tal,
    +                          shift: catalog.shift,
    +                        })
    +                      }
    +                    />
    +                  )}
    +                </EmployeeExtendedCard>
    +              ))
    +            ) : (
    +              <li>
    +                No applicants were found for this shift,{" "}
    +                <span
    +                  className="anchor"
    +                  onClick={() =>
    +                    bar.show({
    +                      slug: "search_talent_and_invite_to_shift",
    +                      data: { shifts: [catalog.shift], employees: [] },
    +                      allowLevels: true,
    +                    })
    +                  }
    +                >
    +                  invite more talents
    +                </span>{" "}
    +                or{" "}
    +                <span
    +                  className="anchor"
    +                  onClick={() =>
    +                    bar.show({
    +                      slug: "review_shift_invites",
    +                      allowLevels: true,
    +                      data: catalog.shift,
    +                    })
    +                  }
    +                >
    +                  review previous invites
    +                </span>
    +              </li>
    +            )}
    +          </ul>
    +        </div>
    +      )}
    +    </Theme.Consumer>
    +  );
    +};
    +ShiftApplicants.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +  context: PropTypes.object, //contact any additional data for context purposes
    +};
    +
    +/**
    + * ShiftApplicants
    + */
    +export const ShiftEmployees = (props) => {
    +  const { onCancel, onSave, catalog } = props;
    +  return (
    +    <Theme.Consumer>
    +      {({ bar }) => (
    +        <div className="sidebar-applicants">
    +          {catalog.shift.expired ? (
    +            <div className="alert alert-warning">
    +              This shift has already expired
    +            </div>
    +          ) : (
    +            <div className="top-bar">
    +              <button
    +                type="button"
    +                className="btn btn-primary btn-sm"
    +                onClick={() =>
    +                  bar.show({
    +                    slug: "search_talent_and_invite_to_shift",
    +                    data: { shifts: [catalog.shift] },
    +                    allowLevels: true,
    +                  })
    +                }
    +              >
    +                invite
    +              </button>
    +            </div>
    +          )}
    +          <h3>Scheduled Employees:</h3>
    +          {catalog.shift.employees.length > 0 ? (
    +            catalog.shift.employees.map((emp, i) => (
    +              <EmployeeExtendedCard
    +                key={i}
    +                employee={emp}
    +                hover={false}
    +                showFavlist={false}
    +                onClick={() =>
    +                  bar.show({
    +                    slug: "show_single_talent",
    +                    data: Talent(emp).defaults().unserialize(),
    +                    allowLevels: true,
    +                  })
    +                }
    +              >
    +                <Button
    +                  className="mt-0 text-black"
    +                  icon="clock"
    +                  label="Clockin log"
    +                  onClick={() =>
    +                    bar.show({
    +                      slug: "talent_shift_clockins",
    +                      data: { employee: emp, shift: catalog.shift },
    +                      allowLevels: true,
    +                    })
    +                  }
    +                />
    +
    +                {!catalog.shift.expired && (
    +                  <Button
    +                    className="mt-0 text-black"
    +                    icon="trash"
    +                    label="Delete"
    +                    onClick={() => {
    +                      const noti = Notify.info(
    +                        "Are you sure? The Talent will be kicked out of this shift",
    +                        (answer) => {
    +                          if (catalog.showShift) {
    +                            if (answer) {
    +                              deleteShiftEmployee(catalog.shift.id, emp);
    +                              catalog.shift.employees =
    +                                catalog.shift.employees.filter(
    +                                  (e) => e.id == emp.id
    +                                );
    +                            }
    +                          } else {
    +                            if (answer) {
    +                              onSave({
    +                                executed_action: "delete_shift_employee",
    +                                employee: emp,
    +                                shift: catalog.shift,
    +                              });
    +                            }
    +                          }
    +                          noti.remove();
    +                        }
    +                      );
    +                    }}
    +                  />
    +                )}
    +              </EmployeeExtendedCard>
    +            ))
    +          ) : catalog.shift.expired ? (
    +            <p>No talents every worked on this shift</p>
    +          ) : (
    +            <p>
    +              No talents have been accepted for this shift yet,{" "}
    +              <span
    +                className="anchor"
    +                onClick={() =>
    +                  bar.show({
    +                    slug: "search_talent_and_invite_to_shift",
    +                    data: { shifts: [catalog.shift] },
    +                    allowLevels: true,
    +                  })
    +                }
    +              >
    +                invite more talents
    +              </span>{" "}
    +              or{" "}
    +              <span
    +                className="anchor"
    +                onClick={() =>
    +                  bar.show({
    +                    slug: "review_shift_invites",
    +                    allowLevels: true,
    +                    data: catalog.shift,
    +                  })
    +                }
    +              >
    +                review previous invites
    +              </span>
    +            </p>
    +          )}
    +        </div>
    +      )}
    +    </Theme.Consumer>
    +  );
    +};
    +ShiftEmployees.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +  context: PropTypes.object, //contact any additional data for context purposes
    +};
    +
    +/**
    + * ShiftApplicants
    + */
    +export const ShiftInvites = ({ onCancel, onSave, formData }) => {
    +  const { bar } = useContext(Theme.Context);
    +  const status = {
    +    PENDING: "Waiting for reponse",
    +    APPLIED: "The talent applied",
    +    REJECTED: "The talent reject it",
    +  };
    +  //formData.shift.maximum_allowed_employees
    +  //formData.shift.status != "OPEN"
    +  const htmlInvites = formData.invites.map((invite, i) => (
    +    <GenericCard
    +      key={i}
    +      className="pr-2"
    +      onClick={() =>
    +        bar.show({
    +          slug: "show_single_talent",
    +          data: invite.employee,
    +          allowLevels: true,
    +        })
    +      }
    +    >
    +      <Avatar
    +        url={
    +          invite.employee.user.profile
    +            ? invite.employee.user.profile.picture
    +            : "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png"
    +        }
    +      />
    +      <p>
    +        <b>
    +          {invite.employee.user.first_name +
    +            " " +
    +            invite.employee.user.last_name}
    +        </b>
    +      </p>
    +      <p className="mr-2 my-0">
    +        Sent {moment(invite.created_at).fromNow()} and{" "}
    +        <span className="badge">{status[invite.status]}</span>
    +      </p>
    +    </GenericCard>
    +  ));
    +  return (
    +    <div className="sidebar-applicants">
    +      <h3>Already invited to this shift:</h3>
    +      {formData.shift.status != "OPEN" && (
    +        <div className="alert alert-warning">
    +          This shift is not accepting any more candidates, this invites will be
    +          erased soon.
    +        </div>
    +      )}
    +      {formData.shift.maximum_allowed_employees <=
    +        formData.shift.employees.length && (
    +        <div className="alert alert-warning">
    +          This shift is full (filled), this invites will be erased soon.
    +        </div>
    +      )}
    +      {htmlInvites.length > 0 ? htmlInvites : <p>No invites have been sent</p>}
    +    </div>
    +  );
    +};
    +ShiftInvites.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  formData: PropTypes.object, //contains the data needed for the form to load
    +  context: PropTypes.object, //contact any additional data for context purposes
    +};
    +
    +/**
    + * EditOrAddShift
    + */
    +const EditOrAddShift = ({
    +  onSave,
    +  onCancel,
    +  onChange,
    +  catalog,
    +  formData,
    +  error,
    +  bar,
    +  oldShift,
    +}) => {
    +  const [expired, setExpired] = useState()
    +  const [runTutorial, setRunTutorial] = useState(hasTutorial());
    +  const [steps, setSteps] = useState([
    +    {
    +      target: "#looking-for",
    +      content: "Here you can select what position you are looking for",
    +      placement: "left",
    +      disableOverlay: true,
    +      disableBeacon: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#how-many",
    +      content: "Edit the total numbers of talents needed for this shift",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#price",
    +      content: "Edit how much you are willing to pay per hour",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#date-shift",
    +      content: "Enter the data. Click more to add additional dates",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      spotlightClicks: true,
    +      disableOverlay: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#from-to-date",
    +      content: "Edit the starting time and ending time of the shift",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#location",
    +      content:
    +        "Select the location for which the shift it's taking. You can add a new location by selecting add new location option",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#instruction",
    +      content:
    +        "Add instruction or any information about the shift if necessary that could help the employees",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#who-can-apply",
    +      content:
    +        "Select who can apply for this shift or broadcast this shift for all qualified employees in JobCore.",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +    {
    +      target: "#publish",
    +      content: "Publish your shift notify invited talents",
    +      placement: "left",
    +      allowClicksThruHole: true,
    +      disableOverlay: true,
    +      spotlightClicks: true,
    +      styles: {
    +        options: {
    +          zIndex: 10000,
    +        },
    +      },
    +      locale: { skip: "Skip Tutorial" },
    +    },
    +  ]);
    +  const [description, setDescription] = useState("");
    +  const [tutorial, setTutorial] = useState(false);
    +  const [recurrent, setRecurrent] = useState();
    +  const [recurrentDates, setRecurrentDates] = useState({
    +    starting_at: moment(),
    +    ending_at: moment().add(1, "M"),
    +  });
    +  const [totalShift, setTotalShift] = useState(0);
    +
    +  const [recurrentTimes, setRecurrentTimes] = useState({
    +    sunday: {
    +      active: false,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +    monday: {
    +      active: true,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +    tuesday: {
    +      active: true,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +    wednesday: {
    +      active: true,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +    thursday: {
    +      active: true,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +    friday: {
    +      active: true,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +    saturday: {
    +      active: false,
    +      starting_at: null,
    +      ending_at: null,
    +    },
    +  });
    +  const [multipleRecurrentShift, setMultipleRecurrentShift] = useState([]);
    +  const [previousShifts, setPreviousShifts] = useState([]);
    +  const [totalHoursEmployeeWeek, settotalHoursEmployeeWeek] = useState(null);
    +
    +  const setDescriptionContent = (description) => {
    +    description.length > 300
    +      ? setDescription(description.slice(0, 300))
    +      : setDescription(description);
    +  };
    +
    +  const getRecurrentShifts = async function getRecurrentDates() {
    +    var startDate = recurrentDates.starting_at;
    +    var endDate = recurrentDates.ending_at;
    +
    +    const start = startDate.startOf("days");
    +    const end = endDate.startOf("days");
    +
    +    let weekDays = Object.values([recurrentTimes][0]);
    +
    +    const dailyInfo = weekDays;
    +    let totalDays = 0;
    +    var multipleShifts = [];
    +
    +    dailyInfo.forEach((info, index) => {
    +      if (info.active === true && info.starting_at && info.ending_at) {
    +        let current = start.clone();
    +        if (current.isoWeekday() <= index) {
    +          current = current.isoWeekday(index);
    +        } else {
    +          current.add(1, "weeks").isoWeekday(index);
    +        }
    +
    +        while (current.isSameOrBefore(end)) {
    +          const starting = moment(
    +            current.format("MM-DD-YYYY") +
    +              " " +
    +              info.starting_at.format("hh:mm a"),
    +            "MM-DD-YYYY hh:mm a"
    +          );
    +          const ending = moment(
    +            current.format("MM-DD-YYYY") +
    +              " " +
    +              info.ending_at.format("hh:mm a"),
    +            "MM-DD-YYYY hh:mm a"
    +          );
    +
    +          multipleShifts.push({ starting_at: starting, ending_at: ending });
    +          current.day(7 + index);
    +          totalDays += 1;
    +        }
    +      }
    +    });
    +
    +    setTotalShift(totalDays);
    +    setMultipleRecurrentShift(multipleShifts);
    +    return multipleShifts;
    +  };
    +  const saveRecurrentDates = async function saveRecurrentDates() {
    +    await getRecurrentShifts().then((res) => {
    +      if (res && Array.isArray(res) && res.length > 0) {
    +        const noti = Notify.info(
    +          `Are you sure to publish a total of ${res.length}? (
    +                    ${
    +                      "From " +
    +                      res[0].starting_at.format("MM-DD-YYYY") +
    +                      " - " +
    +                      res[res.length - 1].ending_at.format("MM-DD-YYYY") +
    +                      " "
    +                      // res.map(s => {
    +                      //     return s.starting_at.format("MM-DD-YYYY hh:mm a") + " - " + s.ending_at.format("MM-DD-YYYY hh:mm a") + " ";
    +                      // })
    +                    }
    +                )`,
    +          (answer) => {
    +            if (answer) {
    +              formData.multiple_dates = res;
    +              onSave({
    +                executed_action: isNaN(formData.id)
    +                  ? "create_shift"
    +                  : "update_shift",
    +                status: "OPEN",
    +              });
    +              // window.location.reload();
    +            }
    +            noti.remove();
    +          }
    +        );
    +        return noti;
    +      } else alert("Error: Please select recurrent dates.  ");
    +    });
    +  };
    +  async function getPreviousShift(emp) {
    +    let response = await GET(
    +      "employers/me/shifts?employee=" + emp + "&limit=15"
    +    ).then((res) => {
    +      const previous_shifts = res.filter(
    +        (v, i, a) =>
    +          a.findIndex((t) => t.position.id === v.position.id) === i &&
    +          v.position.id == parseInt(formData.position)
    +      );
    +          
    +      var start_payroll = moment().clone().weekday(1);
    +      var end_payroll = moment(start_payroll).add(6, "days");
    +      const payrollWeekShifts =
    +        res.filter((e) =>
    +          moment(e.starting_at).isBetween(
    +            start_payroll,
    +            end_payroll,
    +            "day",
    +            "[]"
    +          )
    +        ) || [];
    +
    +      const scheduleHours = payrollWeekShifts.reduce(
    +        (total, { starting_at, ending_at }) =>
    +          total +
    +          moment
    +            .duration(moment(ending_at).diff(moment(starting_at)))
    +            .asHours(),
    +        0
    +      );
    +      console.log("scheduleHours###", scheduleHours)
    +      setPreviousShifts(previous_shifts);
    +      settotalHoursEmployeeWeek(scheduleHours);
    +    });
    +
    +    return response;
    +  }
    +
    +  useEffect(() => {
    +    setRecurrent(true)
    +    const venues = store.getState("venues");
    +    const favlists = store.getState("favlists");
    +    if (!venues || !favlists) fetchAllMe(["venues", "favlists"]);
    +  }, []);
    +  useEffect(() => {
    +    setExpired(moment(formData.starting_at).isBefore(NOW()) || moment(formData.ending_at).isBefore(NOW()))
    +  }, [formData.starting_at]);
    +  
    +  if (
    +    catalog.positions.find(
    +      (pos) =>
    +        pos.value == formData.position.id || pos.value == formData.position
    +    )
    +  )
    +    formData["position"] = catalog.positions
    +      .find(
    +        (pos) =>
    +          pos.value == formData.position.id || pos.value == formData.position
    +      )
    +      .value.toString();
    +  if (
    +    catalog.venues.find(
    +      (pos) => pos.value == formData.venue.id || pos.value == formData.venue
    +    )
    +  )
    +    formData["venue"] = catalog.venues
    +      .find(
    +        (pos) => pos.value == formData.venue.id || pos.value == formData.venue
    +      )
    +      .value.toString();
    +  if (formData.employer && isNaN(formData.employer))
    +    formData.employer = formData.employer.id;
    +
    +  if (!formData.shift && !isNaN(formData.id)) formData.shift = formData.id;
    +  if (formData.required_badges) delete formData.required_badges;
    +  if (description) formData.description = description;
    +  
    +  const handleChange = e => {if (e.target.value==="true") {
    +    setRecurrent(false) 
    +  } else if (e.target.value==="false") {
    +    setRecurrent(true)
    +  }}
    +    
    +  return (
    +    <div>
    +      {/* <Wizard continuous 
    +            steps={steps}
    +            run={tutorial}
    +            callback={callback}
    +            disableBeacon={true}
    +            styles={{
    +                options: {
    +                primaryColor: '#000',
    +                }
    +            }}
    +            /> */}
    +
    +      <div
    +        style={{
    +          overflowY: "auto",
    +          overflowX: "hidden",
    +          height: "calc(100vh - 75px)",
    +        }}
    +      >
    +        <form>
    +          {/* <div className="row">
    +                        <div className="col-12 text-right">
    +                            <button type="button" className="btn btn-primary p-1 text-right" onClick={()=>setTutorial(true)}><strong>HELP ?</strong></button>
    +                        </div>
    +                    </div> */}
    +          <div className="row">
    +            <div className="col-12">
    +              {formData.hide_warnings === true ? null : formData.status ==
    +                  "DRAFT" && !error ? (
    +                <div className="alert alert-warning d-inline">
    +                  <i className="fas fa-exclamation-triangle"></i> This shift is
    +                  a draft
    +                </div>
    +              ) : formData.status != "UNDEFINED" && !error ? (
    +                <div className="alert alert-success">
    +                  This shift is published, therefore{" "}
    +                  <strong>it needs to be unpublished</strong> before it can be
    +                  updated
    +                </div>
    +              ) : (
    +                ""
    +              )}
    +            </div>
    +          </div>
    +
    +          <div className="row" id="looking-for">
    +            <div className="col-12">
    +              <label>Looking for</label>
    +
    +              <Select
    +                placeholder="Select a position"
    +                value={catalog.positions.find(
    +                  (pos) =>
    +                    pos.value == formData.position.id ||
    +                    pos.value == formData.position
    +                )}
    +                onChange={(selection) => {
    +                  onChange({
    +                    position: selection.value.toString(),
    +                    has_sensitive_updates: true,
    +                  });
    +                  if (
    +                    Array.isArray(formData.pending_invites) &&
    +                    formData.pending_invites.length == 1 &&
    +                    formData.position
    +                  ) {
    +                    getPreviousShift(formData.pending_invites[0].value);
    +                  } else setPreviousShifts([]);
    +                }}
    +                options={catalog.positions}
    +              />
    +            </div>
    +          </div>
    +          <div className="row">
    +            <div className="col-6" id="how-many">
    +              <label>How many?</label>
    +              <input
    +                type="number"
    +                className="form-control"
    +                value={formData.maximum_allowed_employees}
    +                onChange={(e) => {
    +                  if (parseInt(e.target.value, 10) > 0) {
    +                    if (
    +                      oldShift &&
    +                      oldShift.employees.length > parseInt(e.target.value, 10)
    +                    )
    +                      Notify.error(
    +                        `${oldShift.employees.length} talents are scheduled to work on this shift already, delete scheduled employees first.`
    +                      );
    +                    else
    +                      onChange({ maximum_allowed_employees: e.target.value });
    +                  }
    +                }}
    +              />
    +            </div>
    +            <div className="col-6" id="price">
    +              <label>Price / hour</label>
    +              <input
    +                type="number"
    +                className="form-control"
    +                value={formData.minimum_hourly_rate}
    +                onChange={(e) =>
    +                  onChange({
    +                    minimum_hourly_rate: e.target.value,
    +                    has_sensitive_updates: true,
    +                  })
    +                }
    +              />
    +              {Array.isArray(previousShifts) && previousShifts.length == 1 ? (
    +                <span
    +                  className="badge badge-primary"
    +                  style={{ cursor: "pointer" }}
    +                  onClick={() =>
    +                    onChange({
    +                      minimum_hourly_rate: parseFloat(
    +                        previousShifts[0]["minimum_hourly_rate"]
    +                      ),
    +                    })
    +                  }
    +                >
    +                  Previous $/hr: $
    +                  {parseFloat(previousShifts[0]["minimum_hourly_rate"]).toFixed(
    +                    2
    +                  )}
    +                </span>
    +              ) : null}
    +            </div>
    +          </div>
    +          <div className="row mt-3 mb-1" id="date-shift">
    +            <div className="col-12">
    +              <label className="mb-1">Create recurrent shifts?</label>
    +              <div className="form-check form-check-inline ml-2">
    +                <input
    +                  className="form-check-input"
    +                  type="radio"
    +                  name="recurrentShifts"
    +                  id="recurrentYes"
    +                  value={true}
    +                  style={{ verticalAlign: "middle" }}
    +                  onChange={handleChange}
    +                />
    +                <span className="form-check-label" htmlFor="recurrentYes">
    +                  Yes
    +                </span>
    +              </div>
    +              <div className="form-check form-check-inline">
    +                <input
    +                  className="form-check-input"
    +                  type="radio"
    +                  name="recurrentShifts"
    +                  id="recurrentNo"
    +                  value={false}
    +                  defaultChecked
    +                  onChange={handleChange}
    +                />
    +                <span className="form-check-label" htmlFor="recurrentNo">
    +                  No
    +                </span>
    +              </div>
    +            </div>
    +            {!recurrent && (
    +              <div className="col-12 mt-2">
    +                <div className="row text-center">
    +                  <div className="col" />
    +                  <div className="col">
    +                    <span>From</span>
    +                  </div>
    +                  <div className="col">
    +                    <span>To</span>
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.monday.active}
    +                        checked={recurrentTimes.monday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            monday: {
    +                              active: !recurrentTimes.monday.active,
    +                              starting_at: recurrentTimes.monday.starting_at,
    +                              ending_at: recurrentTimes.monday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Monday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.monday.starting_at}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.monday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          monday: {
    +                            active: recurrentTimes.monday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.monday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.monday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.monday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          monday: {
    +                            active: recurrentTimes.monday.active,
    +                            starting_at: recurrentTimes.monday.starting_at,
    +                            ending_at: value,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.tuesday.active}
    +                        checked={recurrentTimes.tuesday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            tuesday: {
    +                              active: !recurrentTimes.tuesday.active,
    +                              starting_at: recurrentTimes.tuesday.starting_at,
    +                              ending_at: recurrentTimes.tuesday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Tuesday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.tuesday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.tuesday.starting_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          tuesday: {
    +                            active: recurrentTimes.tuesday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.tuesday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.tuesday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.tuesday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          tuesday: {
    +                            active: recurrentTimes.tuesday.active,
    +                            starting_at: recurrentTimes.tuesday.starting_at,
    +                            ending_at: value,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.wednesday.active}
    +                        checked={recurrentTimes.wednesday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            wednesday: {
    +                              active: !recurrentTimes.wednesday.active,
    +                              starting_at: recurrentTimes.wednesday.starting_at,
    +                              ending_at: recurrentTimes.wednesday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Wednesday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.wednesday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.wednesday.starting_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          wednesday: {
    +                            active: recurrentTimes.wednesday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.wednesday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.wednesday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.wednesday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          wednesday: {
    +                            active: recurrentTimes.wednesday.active,
    +                            starting_at: recurrentTimes.wednesday.starting_at,
    +                            ending_at: value,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.thursday.active}
    +                        checked={recurrentTimes.thursday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            thursday: {
    +                              active: !recurrentTimes.thursday.active,
    +                              starting_at: recurrentTimes.thursday.starting_at,
    +                              ending_at: recurrentTimes.thursday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Thursday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.thursday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.thursday.starting_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          thursday: {
    +                            active: recurrentTimes.thursday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.thursday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.thursday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeFormat={DATETIME_FORMAT}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.thursday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          thursday: {
    +                            active: recurrentTimes.thursday.active,
    +                            ending_at: value,
    +                            starting_at: recurrentTimes.thursday.starting_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.friday.active}
    +                        checked={recurrentTimes.friday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            friday: {
    +                              active: !recurrentTimes.friday.active,
    +                              starting_at: recurrentTimes.friday.starting_at,
    +                              ending_at: recurrentTimes.friday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Friday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.friday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.friday.starting_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          friday: {
    +                            active: recurrentTimes.friday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.friday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.friday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.friday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          friday: {
    +                            active: recurrentTimes.friday.active,
    +                            ending_at: value,
    +                            starting_at: recurrentTimes.friday.starting_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.saturday.active}
    +                        checked={recurrentTimes.saturday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            saturday: {
    +                              active: !recurrentTimes.saturday.active,
    +                              starting_at: recurrentTimes.saturday.starting_at,
    +                              ending_at: recurrentTimes.saturday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Saturday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.saturday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.saturday.starting_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          saturday: {
    +                            active: recurrentTimes.saturday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.saturday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.saturday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.saturday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          saturday: {
    +                            active: recurrentTimes.saturday.active,
    +                            ending_at: value,
    +                            starting_at: recurrentTimes.saturday.starting_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row mb-1" id="from-to-date">
    +                  <div className="col my-auto">
    +                    <div className="form-check">
    +                      <input
    +                        className="form-check-input"
    +                        type="checkbox"
    +                        value={recurrentTimes.sunday.active}
    +                        checked={recurrentTimes.sunday.active}
    +                        onChange={() =>
    +                          setRecurrentTimes({
    +                            ...recurrentTimes,
    +                            sunday: {
    +                              active: !recurrentTimes.sunday.active,
    +                              starting_at: recurrentTimes.sunday.starting_at,
    +                              ending_at: recurrentTimes.sunday.ending_at,
    +                            },
    +                          })
    +                        }
    +                        id="defaultCheck1"
    +                      />
    +                      <span
    +                        className="form-check-label"
    +                        htmlFor="defaultCheck1"
    +                      >
    +                        Sunday
    +                      </span>
    +                    </div>
    +                  </div>
    +                  <div className="col">
    +                    <DateTime
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.sunday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      closeOnTab={true}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.sunday.starting_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          sunday: {
    +                            active: recurrentTimes.sunday.active,
    +                            starting_at: value,
    +                            ending_at: recurrentTimes.sunday.ending_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                  <div className="col-4">
    +                    {/* <label>To {(formData.ending_at.isBefore(formData.starting_at)) && "(next day)"}</label> */}
    +                    <DateTime
    +                      className="picker-left"
    +                      dateFormat={false}
    +                      timeFormat={DATETIME_FORMAT}
    +                      inputProps={{
    +                        disabled: !recurrentTimes.sunday.active,
    +                        placeholder: "0:00 am",
    +                      }}
    +                      timeConstraints={{ minutes: { step: 15 } }}
    +                      value={recurrentTimes.sunday.ending_at}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        if (typeof value == "string") value = moment(value);
    +                        setRecurrentTimes({
    +                          ...recurrentTimes,
    +                          sunday: {
    +                            active: recurrentTimes.sunday.active,
    +                            ending_at: value,
    +                            starting_at: recurrentTimes.sunday.starting_at,
    +                          },
    +                        });
    +                      }}
    +                    />
    +                  </div>
    +                </div>
    +                <div className="row" id="date-shift">
    +                  <div className="col-6">
    +                    <label className="mb-1">Starting Date</label>
    +
    +                    <div className="input-group">
    +                      <DateTime
    +                        timeFormat={false}
    +                        className="shiftdate-picker"
    +                        closeOnSelect={true}
    +                        value={recurrentDates.starting_at}
    +                        isValidDate={(current) => {
    +                          return current.isAfter(YESTERDAY) ? true : false;
    +                        }}
    +                        renderInput={(properties) => {
    +                          const { value, ...rest } = properties;
    +                          return (
    +                            <input
    +                              value={value.match(/\d{2}\/\d{2}\/\d{4}/gm)}
    +                              {...rest}
    +                            />
    +                          );
    +                        }}
    +                        onChange={(value) => {
    +                          if (typeof value == "string") value = moment(value);
    +                          const getRealDate = (start, end) => {
    +                            const starting = moment(
    +                              value.format("MM-DD-YYYY"),
    +                              "MM-DD-YYYY"
    +                            );
    +                            var ending = moment(end);
    +                            if (
    +                              typeof starting !== "undefined" &&
    +                              starting.isValid()
    +                            ) {
    +                              if (ending.isBefore(starting)) {
    +                                ending = ending.add(1, "days");
    +                              }
    +
    +                              return setRecurrentDates({
    +                                starting_at: starting,
    +                                ending_at: ending,
    +                              });
    +                            }
    +                            return null;
    +                          };
    +
    +                          getRealDate(
    +                            recurrentDates.starting_at,
    +                            recurrentDates.ending_at
    +                          );
    +                        }}
    +                      />
    +                    </div>
    +                  </div>
    +                  {/* <div className="col-12"/> */}
    +                  <div className="col-6">
    +                    <label className="mb-1">Ending Date</label>
    +                    <div className="input-group">
    +                      <DateTime
    +                        timeFormat={false}
    +                        className="picker-left"
    +                        closeOnSelect={true}
    +                        value={recurrentDates.ending_at}
    +                        isValidDate={(current) => {
    +                          return current.isAfter(YESTERDAY) &&
    +                            current.isBefore(moment().add(36, "M"))
    +                            ? true
    +                            : false;
    +                        }}
    +                        renderInput={(properties) => {
    +                          const { value, ...rest } = properties;
    +                          return (
    +                            <input
    +                              value={value.match(/\d{2}\/\d{2}\/\d{4}/gm)}
    +                              {...rest}
    +                            />
    +                          );
    +                        }}
    +                        onChange={(value) => {
    +                          if (typeof value == "string") value = moment(value);
    +
    +                          const getRealDate = (start, end) => {
    +                            const starting = start;
    +                            var ending = moment(
    +                              value.format("MM-DD-YYYY"),
    +                              "MM-DD-YYYY"
    +                            );
    +
    +                            if (
    +                              typeof starting !== "undefined" &&
    +                              starting.isValid()
    +                            ) {
    +                              if (ending.isBefore(starting)) {
    +                                ending = ending.add(1, "days");
    +                              }
    +
    +                              return setRecurrentDates({
    +                                starting_at: starting,
    +                                ending_at: ending,
    +                              });
    +                            }
    +                            return null;
    +                          };
    +
    +                          getRealDate(
    +                            recurrentDates.starting_at,
    +                            recurrentDates.ending_at
    +                          );
    +                        }}
    +                      />
    +                    </div>
    +                  </div>
    +                </div>
    +              </div>
    +            )}
    +          </div>
    +          {recurrent && (
    +            <div>
    +              <div className="row" id="date-shift">
    +                <div className="col-12">
    +                  <label className="mb-1">Dates</label>
    +                  {formData.multiple_dates && (
    +                    <p className="mb-1 mt-0">
    +                      {formData.multiple_dates.map((d, i) => (
    +                        <span key={i} className="badge">
    +                          {d.starting_at.format("MM-DD-YYYY")}
    +                          <i
    +                            className="fas fa-trash-alt ml-1 pointer"
    +                            onClick={() =>
    +                              onChange({
    +                                multiple_dates: !formData.multiple_dates
    +                                  ? []
    +                                  : formData.multiple_dates.filter(
    +                                      (dt) =>
    +                                        !dt.starting_at.isSame(d.starting_at)
    +                                    ),
    +                                has_sensitive_updates: true,
    +                              })
    +                            }
    +                          />
    +                        </span>
    +                      ))}
    +                    </p>
    +                  )}
    +                  <div className="input-group">
    +                    <DateTime
    +                      timeFormat={false}
    +                      className="shiftdate-picker"
    +                      closeOnSelect={true}
    +                      value={formData.starting_at}
    +                      isValidDate={(current) => {
    +                        return formData.multiple_dates !== undefined &&
    +                          formData.multiple_dates.length > 0
    +                          ? current.isAfter(YESTERDAY)
    +                          : true;
    +                      }}
    +                      renderInput={(properties) => {
    +                        const { value, ...rest } = properties;
    +                        return (
    +                          <input
    +                            value={value.match(/\d{2}\/\d{2}\/\d{4}/gm)}
    +                            {...rest}
    +                          />
    +                        );
    +                      }}
    +                      onChange={(value) => {
    +                        const getRealDate = (start, end) => {
    +                          if (typeof start == "string") value = moment(start);
    +
    +                          const starting = moment(
    +                            start.format("MM-DD-YYYY") +
    +                              " " +
    +                              start.format("hh:mm a"),
    +                            "MM-DD-YYYY hh:mm a"
    +                          );
    +
    +                          var ending = moment(
    +                            start.format("MM-DD-YYYY") +
    +                              " " +
    +                              end.format("hh:mm a"),
    +                            "MM-DD-YYYY hh:mm a"
    +                          );
    +                          if (
    +                            typeof starting !== "undefined" &&
    +                            starting.isValid()
    +                          ) {
    +                            if (ending.isBefore(starting)) {
    +                              ending = ending.add(1, "days");
    +                            }
    +
    +                            return { starting_at: starting, ending_at: ending };
    +                          }
    +                          return null;
    +                        };
    +
    +                        const mainDate = getRealDate(value, formData.ending_at);
    +
    +                        const multipleDates = !Array.isArray(
    +                          formData.multiple_dates
    +                        )
    +                          ? []
    +                          : formData.multiple_dates.map((d) =>
    +                              getRealDate(d.starting_at, d.ending_at)
    +                            );
    +                        onChange({
    +                          ...mainDate,
    +                          multiple_dates: multipleDates,
    +                          has_sensitive_updates: true,
    +                        });
    +                      }}
    +                    />
    +                    <div
    +                      className="input-group-append"
    +                      onClick={() => {
    +                        if (expired)
    +                          Notify.error(
    +                            "Shifts with and expired starting or ending times cannot have multiple dates or be recurrent"
    +                          );
    +                        else
    +                          onChange({
    +                            multiple_dates: !formData.multiple_dates
    +                              ? [
    +                                  {
    +                                    starting_at: formData.starting_at,
    +                                    ending_at: formData.ending_at,
    +                                  },
    +                                ]
    +                              : formData.multiple_dates
    +                                  .filter(
    +                                    (dt) =>
    +                                      !dt.starting_at.isSame(
    +                                        formData.starting_at
    +                                      )
    +                                  )
    +                                  .concat({
    +                                    starting_at: formData.starting_at,
    +                                    ending_at: formData.ending_at,
    +                                  }),
    +                            has_sensitive_updates: true,
    +                          });
    +                      }}
    +                    >
    +                      <span className="input-group-text pointer">
    +                        More <i className="fas fa-plus ml-1"></i>
    +                      </span>
    +                    </div>
    +                  </div>
    +                </div>
    +              </div>
    +
    +              <div className="row" id="from-to-date">
    +                <div className="col-6">
    +                  <label>From</label>
    +                  <DateTime
    +                    dateFormat={false}
    +                    timeFormat={DATETIME_FORMAT}
    +                    closeOnTab={true}
    +                    timeConstraints={{ minutes: { step: 15 } }}
    +                    value={formData.starting_at}
    +                    renderInput={(properties) => {
    +                      const { value, ...rest } = properties;
    +                      return (
    +                        <input
    +                          value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                          {...rest}
    +                        />
    +                      );
    +                    }}
    +                    onChange={(value) => {
    +                      if (typeof value == "string") value = moment(value);
    +
    +                      const getRealDate = (start, end) => {
    +                        const starting = moment(
    +                          start.format("MM-DD-YYYY") +
    +                            " " +
    +                            value.format("hh:mm a"),
    +                          "MM-DD-YYYY hh:mm a"
    +                        );
    +
    +                        var ending = moment(end);
    +                        if (
    +                          typeof starting !== "undefined" &&
    +                          starting.isValid()
    +                        ) {
    +                          if (ending.isBefore(starting)) {
    +                            ending = ending.add(1, "days");
    +                          }
    +
    +                          return { starting_at: starting, ending_at: ending };
    +                        }
    +                        return null;
    +                      };
    +
    +                      const mainDate = getRealDate(
    +                        formData.starting_at,
    +                        formData.ending_at
    +                      );
    +                      const multipleDates = !Array.isArray(
    +                        formData.multiple_dates
    +                      )
    +                        ? []
    +                        : formData.multiple_dates.map((d) =>
    +                            getRealDate(d.starting_at, d.ending_at)
    +                          );
    +                      onChange({
    +                        ...mainDate,
    +                        multiple_dates: multipleDates,
    +                        has_sensitive_updates: true,
    +                      });
    +                    }}
    +                  />
    +                </div>
    +                <div className="col-6">
    +                  <label>
    +                    To{" "}
    +                    {formData.ending_at.isBefore(formData.starting_at) &&
    +                      "(next day)"}
    +                  </label>
    +                  <DateTime
    +                    className="picker-left"
    +                    dateFormat={false}
    +                    timeFormat={DATETIME_FORMAT}
    +                    timeConstraints={{ minutes: { step: 15 } }}
    +                    value={formData.ending_at}
    +                    renderInput={(properties) => {
    +                      const { value, ...rest } = properties;
    +                      return (
    +                        <input
    +                          value={value.match(/\d{1,2}:\d{1,2}\s?[ap]m/gm)}
    +                          {...rest}
    +                        />
    +                      );
    +                    }}
    +                    onChange={(value) => {
    +                      if (typeof value == "string") value = moment(value);
    +
    +                      const getRealDate = (start, end) => {
    +                        const starting = start;
    +                        var ending = moment(
    +                          start.format("MM-DD-YYYY") +
    +                            " " +
    +                            value.format("hh:mm a"),
    +                          "MM-DD-YYYY hh:mm a"
    +                        );
    +
    +                        if (
    +                          typeof starting !== "undefined" &&
    +                          starting.isValid()
    +                        ) {
    +                          if (ending.isBefore(starting)) {
    +                            ending = ending.add(1, "days");
    +                          }
    +
    +                          return { starting_at: starting, ending_at: ending };
    +                        }
    +                        return null;
    +                      };
    +
    +                      const mainDate = getRealDate(
    +                        formData.starting_at,
    +                        formData.ending_at
    +                      );
    +                      const multipleDates = !Array.isArray(
    +                        formData.multiple_dates
    +                      )
    +                        ? []
    +                        : formData.multiple_dates.map((d) =>
    +                            getRealDate(d.starting_at, d.ending_at)
    +                          );
    +                      onChange({
    +                        ...mainDate,
    +                        multiple_dates: multipleDates,
    +                        has_sensitive_updates: true,
    +                      });
    +                    }}
    +                  />
    +                </div>
    +              </div>
    +            </div>
    +          )}
    +          <div className="row" id="location">
    +            <div className="col-12">
    +              <label>Location</label>
    +              <Select
    +                value={catalog.venues.find(
    +                  (ven) =>
    +                    ven.value == formData.venue.id ||
    +                    ven.value == formData.venue
    +                )}
    +                options={[
    +                  {
    +                    label: "Add a location",
    +                    value: "new_venue",
    +                    component: AddOrEditLocation,
    +                  },
    +                ].concat(catalog.venues)}
    +                onChange={(selection) => {
    +                  if (selection.value == "new_venue")
    +                    bar.show({ slug: "create_location", allowLevels: true });
    +                  else
    +                    onChange({
    +                      venue: selection.value.toString(),
    +                      has_sensitive_updates: true,
    +                    });
    +                }}
    +              />
    +            </div>
    +          </div>
    +          <div className="row" id="instruction">
    +            <div className="col-12">
    +              <label>Shift Instructions (optional)</label>
    +              <TextareaAutosize
    +                minRows={2}
    +                style={{ width: "100%" }}
    +                placeholder="Dressing code, location instructions, parking instruction etc.."
    +                onChange={(event) => setDescriptionContent(event.target.value)}
    +                value={description}
    +              />
    +            </div>
    +          </div>
    +
    +          <div className="row mt-3">
    +            <div className="col-12" id="who-can-apply">
    +              <h4>Who can apply to this shift?</h4>
    +            </div>
    +          </div>
    +          <div className="row">
    +            <div className="col-12">
    +              {expired ? (
    +                <div className="alert alert-warning">
    +                  This shift has an expired date, therefore you cannot invite
    +                  anyone but you can still use it for payroll purposes.
    +                </div>
    +              ) : (
    +                <Select
    +                  value={catalog.applicationRestrictions.find(
    +                    (a) => a.value == formData.application_restriction
    +                  )}
    +                  onChange={(selection) =>
    +                    onChange({
    +                      application_restriction: selection.value.toString(),
    +                    })
    +                  }
    +                  options={catalog.applicationRestrictions}
    +                />
    +              )}
    +            </div>
    +          </div>
    +          {!expired && formData.application_restriction == "FAVORITES" ? (
    +            <div className="row">
    +              <div className="col-12">
    +                <label>From these favorite lists</label>
    +                <Select
    +                  isMulti
    +                  value={formData.allowedFavlists}
    +                  onChange={(opt) => onChange({ allowedFavlists: opt })}
    +                  options={catalog.favlists}
    +                ></Select>
    +              </div>
    +            </div>
    +          ) : !expired && formData.application_restriction == "ANYONE" ? (
    +            <div className="row mt-3">
    +              <div className="col-5">
    +                <label className="mt-2">Minimum rating</label>
    +              </div>
    +              <div className="col-7">
    +                <Select
    +                  value={catalog.stars.find(
    +                    (s) => s.value == formData.minimum_allowed_rating
    +                  )}
    +                  onChange={(selection) =>
    +                    onChange({ minimum_allowed_rating: selection.value })
    +                  }
    +                  options={catalog.stars}
    +                />
    +              </div>
    +            </div>
    +          ) : (
    +            !expired && (
    +              <div className="row">
    +                <div className="col-12">
    +                  <label>Search people in JobCore:</label>
    +                  <SearchCatalogSelect
    +                    isMulti={true}
    +                    value={formData.pending_invites}
    +                    onChange={(selections) => {
    +                      const invite = selections.find(
    +                        (opt) => opt.value == "invite_talent_to_jobcore"
    +                      );
    +                      if (invite)
    +                        bar.show({
    +                          allowLevels: true,
    +                          slug: "invite_talent_to_jobcore",
    +                          onSave: (emp) =>
    +                            onChange({
    +                              pending_jobcore_invites:
    +                                formData.pending_jobcore_invites.concat(emp),
    +                            }),
    +                        });
    +                      else onChange({ pending_invites: selections });
    +                      if (
    +                        Array.isArray(formData.pending_invites) &&
    +                        formData.pending_invites.length == 1 &&
    +                        formData.position
    +                      ) {
    +                        getPreviousShift(formData.pending_invites[0].value);
    +                      } else {
    +                        setPreviousShifts([]);
    +                        settotalHoursEmployeeWeek(null);
    +                      }
    +                    }}
    +                    searchFunction={(search) =>
    +                      new Promise((resolve, reject) =>
    +                        GET("catalog/employees?full_name=" + search)
    +                          .then((talents) => {
    +                            resolve(
    +                              [
    +                                {
    +                                  label: `${
    +                                    talents.length == 0 ? "No one found: " : ""
    +                                  }Invite "${search}" to jobcore`,
    +                                  value: "invite_talent_to_jobcore",
    +                                },
    +                              ].concat(talents)
    +                            )
    +                            })
    +                          .catch((error) => reject(error))
    +                      )
    +                    }
    +                  />
    +                </div>
    +              </div>
    +            )
    +          )}
    +          {formData.pending_jobcore_invites.length > 0 ? (
    +            <div className="row">
    +              <div className="col-12">
    +                <p className="m-0 p-0">
    +                  The following people will be invited to this shift after they
    +                  accept your invitation to jobcore:
    +                </p>
    +                {formData.pending_jobcore_invites.map((emp, i) => (
    +                  <span key={i} className="badge">
    +                    {emp.first_name} {emp.last_name}{" "}
    +                    <i className="fas fa-trash-alt"></i>
    +                  </span>
    +                ))}
    +              </div>
    +            </div>
    +          ) : (
    +            ""
    +          )}
    +
    +          {totalHoursEmployeeWeek ? (
    +            <div className="alert alert-warning mt-3" role="alert">
    +              <span>This employee have</span>{" "}
    +              <strong>
    +                {Math.round(totalHoursEmployeeWeek * 100) / 100 + "/40 hours "}
    +              </strong>
    +              <span>scheduled on this weeks payroll</span>
    +            </div>
    +          ) : null}
    +          <div className="btn-bar">
    +            {formData.status == "DRAFT" || formData.status == "UNDEFINED" ? ( // create shift
    +              <button
    +                type="button"
    +                className="btn btn-secondary"
    +                onClick={() => {
    +                  if (!recurrent)
    +                    formData.multiple_dates = multipleRecurrentShift;
    +                  onSave({
    +                    executed_action: isNaN(formData.id)
    +                      ? "create_shift"
    +                      : "update_shift",
    +                    status: "DRAFT",
    +                  });
    +                }}
    +              >
    +                Save as draft
    +              </button>
    +            ) : (
    +              ""
    +            )}
    +            {formData.status == "DRAFT" ? (
    +              <button
    +                type="button"
    +                className="btn btn-success"
    +                onClick={() => {
    +                  if (!formData.has_sensitive_updates && !isNaN(formData.id))
    +                    onSave({ executed_action: "update_shift", status: "OPEN" });
    +                  else {
    +                    const noti = Notify.info(
    +                      "Are you sure? All talents will have to apply again the shift because the information was updated.",
    +                      (answer) => {
    +                        if (answer)
    +                          onSave({
    +                            executed_action: isNaN(formData.id)
    +                              ? "create_shift"
    +                              : "update_shift",
    +                            status: "OPEN",
    +                          });
    +                        noti.remove();
    +                      }
    +                    );
    +                  }
    +                }}
    +              >
    +                Publish
    +              </button>
    +            ) : formData.status != "UNDEFINED" ? (
    +              <button
    +                type="button"
    +                className="btn btn-primary"
    +                onClick={() => {
    +                  const noti = Notify.info(
    +                    "Are you sure you want to unpublish this shift?",
    +                    (answer) => {
    +                      if (answer)
    +                        onSave({
    +                          executed_action: "update_shift",
    +                          status: "DRAFT",
    +                        });
    +                      noti.remove();
    +                    },
    +                    9999999999999
    +                  );
    +                }}
    +              >
    +                Unpublish shift
    +              </button>
    +            ) : (
    +              <button
    +                type="button"
    +                id="publish"
    +                className="btn btn-primary"
    +                onClick={() => {
    +                  if (!recurrent) {
    +                    saveRecurrentDates();
    +                  } else {
    +                    onSave({
    +                      executed_action: isNaN(formData.id)
    +                        ? "create_shift"
    +                        : "update_shift",
    +                      status: "OPEN",
    +                    });
    +                  }
    +                }}
    +              >
    +                Save and publish
    +              </button>
    +            )}
    +            {formData.status != "UNDEFINED" ? (
    +              <button
    +                type="button"
    +                className="btn btn-danger"
    +                onClick={() => {
    +                  const noti = Notify.info(
    +                    "Are you sure you want to cancel this shift?",
    +                    (answer) => {
    +                      if (answer)
    +                        onSave({
    +                          executed_action: "update_shift",
    +                          status: "CANCELLED",
    +                        });
    +                      noti.remove();
    +                    }
    +                  );
    +                }}
    +              >
    +                Delete
    +              </button>
    +            ) : (
    +              ""
    +            )}
    +          </div>
    +        </form>
    +      </div>
    +    </div>
    +  );
    +};
    +EditOrAddShift.propTypes = {
    +  error: PropTypes.string,
    +  oldShift: PropTypes.object,
    +  bar: PropTypes.object,
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +EditOrAddShift.defaultProps = {
    +  oldShift: null,
    +};
    +
    +/**
    + * ShiftDetails
    + */
    +export const ShiftDetails = (props) => {
    +  const creationMode = isNaN(props.formData.id);
    +  // const shift = !props.catalog.shifts ? null : props.catalog.shifts.find(s => s.id == props.formData.id);
    +  const shift = props.formData;
    +  if (!creationMode && (!shift || typeof shift === "undefined"))
    +    return <div>Loading shift...</div>;
    +  return (
    +    <Theme.Consumer>
    +      {({ bar }) => (
    +        <div>
    +          {creationMode ? (
    +            <EditOrAddShift bar={bar} {...props} />
    +          ) : (
    +            <div>
    +              {!shift.expired ? (
    +                <div className="top-bar">
    +                  <Button
    +                    icon="pencil"
    +                    color="primary"
    +                    size="small"
    +                    rounded={true}
    +                    note="Edit shift"
    +                    notePosition="left"
    +                    onClick={() =>
    +                      props.onChange({ status: "DRAFT", hide_warnings: true })
    +                    }
    +                  />
    +                  {["OPEN", "FILLED"].includes(shift.status) && (
    +                    <Button
    +                      icon="candidates"
    +                      color="primary"
    +                      size="small"
    +                      rounded={true}
    +                      onClick={() =>
    +                        bar.show({
    +                          slug: "show_shift_applications",
    +                          data: shift,
    +                          title: "Shift Applicants",
    +                          allowLevels: true,
    +                        })
    +                      }
    +                      note={
    +                        shift.candidates.length > 0
    +                          ? "The shift has applications that have not been reviewed"
    +                          : "Shift Applicants"
    +                      }
    +                      withAlert={shift.candidates.length > 0}
    +                      notePosition="left"
    +                    />
    +                  )}
    +
    +                  <Button
    +                    icon="user_check"
    +                    color="primary"
    +                    notePosition="left"
    +                    note="Shift accepted employees"
    +                    size="small"
    +                    rounded={true}
    +                    onClick={() =>
    +                      bar.show({
    +                        slug: "show_shift_employees",
    +                        data: shift,
    +                        title: "Shift Employees",
    +                        allowLevels: true,
    +                      })
    +                    }
    +                  />
    +                </div>
    +              ) : (
    +                <div className="top-bar">
    +                  <Button
    +                    icon="user_check"
    +                    color="primary"
    +                    notePosition="left"
    +                    size="small"
    +                    rounded={true}
    +                    note="Shift accepted employees"
    +                    onClick={() =>
    +                      bar.show({
    +                        slug: "show_shift_employees",
    +                        data: shift,
    +                        title: "Shift Employees",
    +                        allowLevels: true,
    +                      })
    +                    }
    +                  />
    +                  <Button
    +                    icon="dollar"
    +                    color="primary"
    +                    notePosition="left"
    +                    size="small"
    +                    rounded={true}
    +                    note={
    +                      shift.status !== "OPEN" ? (
    +                        "Shift Payroll"
    +                      ) : (
    +                        <span>
    +                          This shift is expired and the payroll has not been
    +                          processed
    +                        </span>
    +                      )
    +                    }
    +                    withAlert={shift.status !== "OPEN"}
    +                    onClick={() =>
    +                      bar.show({
    +                        slug: "select_timesheet",
    +                        data: shift,
    +                        allowLevels: true,
    +                      })
    +                    }
    +                  />
    +                </div>
    +              )}
    +              {props.formData.status === "DRAFT" ? (
    +                <EditOrAddShift bar={bar} {...props} oldShift={shift} />
    +              ) : (
    +                <ShowShift bar={bar} shift={shift} />
    +              )}
    +
    +              {moment(props.formData.ending_at).isBefore(NOW()) && (
    +                <div className="row text-center mt-4">
    +                  <div className="col">
    +                    <Button
    +                      color="primary"
    +                      onClick={() =>
    +                        bar.show({
    +                          slug: "show_employees_rating",
    +                          data: shift,
    +                          allowLevels: true,
    +                        })
    +                      }
    +                    >
    +                      Rate Employees
    +                    </Button>
    +                  </div>
    +                </div>
    +              )}
    +            </div>
    +          )}
    +        </div>
    +      )}
    +    </Theme.Consumer>
    +  );
    +};
    +ShiftDetails.propTypes = {
    +  error: PropTypes.string,
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  shift: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +const ShowShift = ({ shift, bar }) => {
    +  const totalCandidates = Array.isArray(shift.candidates)
    +    ? shift.candidates.length
    +    : 0;
    +  const totalEmployees = Array.isArray(shift.employees)
    +    ? shift.employees.length
    +    : 0;
    +  const openVacancys = shift.maximum_allowed_employees - totalEmployees;
    +  const startDate = shift.starting_at.format("ll");
    +  const startTime = shift.starting_at.format("LT");
    +  const endTime = shift.ending_at.format("LT");
    +  return (
    +    <div className="shift-details">
    +      <h3>{"Shift details"}</h3>
    +      {shift.status == "DRAFT" ? (
    +        <span href="#" className="badge badge-secondary">
    +          draft
    +        </span>
    +      ) : openVacancys == 0 ? (
    +        <span href="#" className="badge" style={{ background: "#5cb85c" }}>
    +          {totalEmployees} Filled
    +        </span>
    +      ) : (
    +        <span href="#" className="badge badge-danger">
    +          {totalCandidates}/{openVacancys}
    +        </span>
    +      )}
    +      <a href="#" className="shift-position">
    +        {shift.position.title}
    +      </a>{" "}
    +      @
    +      <a href="#" className="shift-location">
    +        {" "}
    +        {shift.venue.title}
    +      </a>
    +      <span className="shift-date">
    +        {" "}
    +        {startDate} from {startTime} to {endTime}{" "}
    +      </span>
    +      {typeof shift.price == "string" ? (
    +        <span className="shift-price"> ${shift.price}</span>
    +      ) : (
    +        <span className="shift-price">
    +          {" "}
    +          {shift.price.currencySymbol}
    +          {shift.price.amount}
    +        </span>
    +      )}
    +      <hr />
    +      <div>
    +        <ShiftEmployees catalog={{ shift: shift, showShift: true }} />
    +      </div>
    +      {/* <hr/>
    +        <ShiftApplicants catalog={{shift: shift, applicants: shift.candidates, showShift: true}}/> */}
    +    </div>
    +  );
    +};
    +ShowShift.propTypes = {
    +  shift: PropTypes.object.isRequired,
    +  bar: PropTypes.object.isRequired,
    +};
    +ShowShift.defaultProps = {
    +  shift: null,
    +  bar: null,
    +};
    +
    +/**
    + * RateShift
    + */
    +export const RateShift = () => (
    +  <div className="p-5 listcontents">
    +    <div className="row">
    +      <div className="col-12">
    +        <h4>Venue name</h4>
    +      </div>
    +    </div>
    +  </div>
    +);
    +RateShift.propTypes = {};
    +
    +/**
    + * RateShift
    + */
    +export const ShiftTalentClockins = ({ formData, onChange, onSave }) => {
    +  const { employee, clockins, shift } = formData;
    +  const { bar } = useContext(Theme.Context);
    +  const lastClockin =
    +    clockins.length === 0
    +      ? null
    +      : moment.isMoment(clockins[clockins.length - 1].ended_at)
    +      ? clockins[clockins.length - 1].ended_at
    +      : moment(clockins[clockins.length - 1].ended_at);
    +
    +  return (
    +    <div className="">
    +      <div className="row">
    +        <div className="col-12">
    +          {/*<div className="top-bar">
    +                    <Button size="small" color="primary" rounded={true} className="mr-2"
    +                        onClick={() => onChange({ new_clocking: { started_at: null, ended_at: null, shift: shift.id } })}
    +                    >add</Button>
    +                </div>*/}
    +          <h3>Clockins</h3>
    +          {clockins.length == 0 && (
    +            <p>
    +              {employee.user.first_name} {employee.user.last_name} has not
    +              clocked in to this shift yet
    +            </p>
    +          )}
    +          {(clockins.length > 0 || formData.new_clocking) && (
    +            <div className="row px-3 text-center">
    +              <div className="col">In</div>
    +              <div className="col">Out</div>
    +            </div>
    +          )}
    +          {clockins.map((c) => {
    +            let started_at = moment.isMoment(c.started_at)
    +              ? c.started_at
    +              : moment(c.started_at);
    +            let ended_at = moment.isMoment(c.ended_at)
    +              ? c.ended_at
    +              : moment(c.ended_at);
    +            return (
    +              <div key={c.id} className="row px-3 text-center">
    +                <div className="col">{started_at.format("LT")}</div>
    +                <div className="col">
    +                  {ended_at.isValid(c.ended_at) ? (
    +                    ended_at.format("LT")
    +                  ) : (
    +                    <span className="badge badge-secondary">Still Working</span>
    +                  )}
    +                </div>
    +              </div>
    +            );
    +          })}
    +          {formData.new_clocking && (
    +            <div className="row px-3 text-center">
    +              <div className="col-6">
    +                <TimePicker
    +                  showSecond={false}
    +                  defaultValue={lastClockin ? lastClockin : null}
    +                  format={TIME_FORMAT}
    +                  onChange={(value) =>
    +                    onChange({
    +                      new_clockin: {
    +                        ...formData.new_clockin,
    +                        started_at: value,
    +                      },
    +                    })
    +                  }
    +                  value={formData.new_clocking.started_at}
    +                  use12Hours
    +                  inputReadOnly
    +                />
    +              </div>
    +              <div className="col-6">
    +                <TimePicker
    +                  showSecond={false}
    +                  defaultValue={lastClockin ? lastClockin : null}
    +                  format={TIME_FORMAT}
    +                  onChange={(value) =>
    +                    onChange({
    +                      new_clockin: { ...formData.new_clockin, ended_at: value },
    +                    })
    +                  }
    +                  value={formData.new_clocking.ended_at}
    +                  use12Hours
    +                  inputReadOnly
    +                />
    +              </div>
    +              <div className="col-6 mt-2">
    +                <Button
    +                  color="primary"
    +                  className="w-100"
    +                  onClick={() =>
    +                    onSave({
    +                      executed_action: "add_clockin",
    +                      clockin: formData.new_clocking,
    +                    })
    +                  }
    +                >
    +                  Save
    +                </Button>
    +              </div>
    +              <div className="col-6 mt-2">
    +                <Button
    +                  color="secondary"
    +                  className="w-100"
    +                  onClick={() =>
    +                    onChange({ formData: { ...formData, new_clockin: null } })
    +                  }
    +                >
    +                  Cancel
    +                </Button>
    +              </div>
    +            </div>
    +          )}
    +        </div>
    +      </div>
    +    </div>
    +  );
    +};
    +ShiftTalentClockins.propTypes = {
    +  formData: PropTypes.object.isRequired,
    +  catalog: PropTypes.object.isRequired,
    +  onChange: PropTypes.func,
    +  onSave: PropTypes.func,
    +  history: PropTypes.object.isRequired,
    +};
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_subscriptions.js.html b/docs/views_subscriptions.js.html new file mode 100644 index 0000000..8da5fdb --- /dev/null +++ b/docs/views_subscriptions.js.html @@ -0,0 +1,236 @@ + + + + + JSDoc: Source: views/subscriptions.js + + + + + + + + + + +
    + +

    Source: views/subscriptions.js

    + + + + + + +
    +
    +
    import React, { useState, useEffect } from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from 'prop-types';
    +import {store, createSubscription, searchMe } from '../actions.js';
    +import { Button } from '../components/index';
    +import {Notify} from 'bc-react-notifier';
    +import { GET } from "../utils/api_wrapper.js";
    +
    +import Tooltip from 'rc-tooltip';
    +import 'rc-tooltip/assets/bootstrap_white.css';
    +
    +//gets the queryst
    +export const Subscription = (data) => {
    +
    +    const _defaults = {
    +        id: '',
    +        title: '',
    +        unique_name: '',
    +        serialize: function(){
    +
    +            const newLocation = {
    +//                status: (this.status == 'UNDEFINED') ? 'DRAFT' : this.status,
    +            };
    +
    +            return Object.assign(this, newLocation);
    +        }
    +    };
    +
    +    let _location = Object.assign(_defaults, data);
    +    return {
    +        validate: () => {
    +            //if(validator.isEmpty(_location.title)) throw new ValidationError('The location title cannot be empty');
    +            return _location;
    +        },
    +        defaults: () => {
    +            return _defaults;
    +        },
    +        getFormData: () => {
    +            const _formShift = {
    +                //title: _location.title,
    +            };
    +            return _formShift;
    +        }
    +    };
    +};
    +
    +/**
    + * YourSubscription
    + */
    +const FeatureIndicator = ({fixed, additional, className, boolean}) => <div className={`m-0 ${className}`}>
    +    { boolean !== null ?
    +        <div>{boolean ? "Yes" : "No"}</div>
    +        :
    +        <div>
    +            {fixed > 9999 ? "Unlimited" : fixed > 0 && `${fixed} / mo.`}
    +            {fixed === 0 && additional > 0 ? 
    +                "$" + additional + "/each"
    +                : !additional || additional <=0 ? 
    +                    '' 
    +                    :
    +                    <Tooltip placement="top" trigger={['hover']} overlay={<span>
    +                        {fixed > 0 && fixed < 9999 && additional > 0 && " Plus "}
    +                        {additional > 0 && "$" + additional + "/each"}
    +                        {fixed > 0 && fixed < 9999 && additional > 0 && ` after the ${fixed} limit has been reached `}
    +                    </span>}>
    +                        <i className="fas fa-plus-circle ml-1"></i>
    +                    </Tooltip>
    +            }
    +        </div>
    +    }
    +</div>;
    +FeatureIndicator.propTypes = {
    +  className: PropTypes.string,
    +  boolean: PropTypes.bool,
    +  fixed: PropTypes.number,
    +  additional: PropTypes.number
    +};
    +FeatureIndicator.defaultProps = {
    +  className: '',
    +  fixed: 0,
    +  boolean: null,
    +  additional: 0
    +};
    +export const YourSubscription = (props) => {
    +
    +    const [ employer, setEmployer ] = useState(store.getState('current_employer'));
    +    const [ plans, setPlans ] = useState([]);
    +    const [ customer, setCustomer ] = useState('');
    +    const [ subscription, setSubscription ] = useState('');
    +
    +    useEffect(() => {
    +
    +        const employerSubscription = store.subscribe('current_employer', (_employer) => setEmployer(_employer));
    +        GET('subscriptions').then(subs => setPlans(subs));
    +
    +        searchMe('subscription').then(res => {
    +            if(Array.isArray(res) && res[1 - res.length]){
    +               
    +                const cus = res[1 - res.length]['stripe_cus'];
    +                const sub = res[1 - res.length]['stripe_sub'];
    +
    +                if(cus){
    +                    setCustomer(res[1 - res.length]['stripe_cus']);
    +                }
    +                if(sub){
    +                    setSubscription(res[1 - res.length]['stripe_sub']);
    +                }
    +            }
    +        });
    +
    +        return () => {
    +            employerSubscription.unsubscribe();
    +        };
    +    }, []);
    +    
    +    
    +    
    +    if(!employer) return "Loading";
    +    return (<div>
    +        <div className="row">
    +            <div className="col-12">
    +                Hello {employer.title}
    +                <p>Current subscription: {employer.active_subscription ? employer.active_subscription.title : "No active subscription"}</p>
    +            </div>
    +        </div>
    +        <div className="row">
    +            <div className="col-3 text-center">
    +                <h2>Features</h2>
    +                <p className="m-0">Base Price</p>
    +                <p className="m-0">Job Postings /mo.</p>
    +                <p className="m-0">Maximum active employees / mo.</p>
    +                <p className="m-0">Maximum Clock In/Out / mo.</p>
    +                <p className="m-0">Talent Search	</p>
    +                <p className="m-0">Rate Talent</p>
    +                <p className="m-0">Invite Talent Manually</p>
    +                <p className="m-0">Geo Clock In/Out report</p>
    +                <p className="m-0">Payroll Reports</p>
    +                <p className="m-0">Create favorite employees list size	</p>
    +                <p className="m-0">Smart Calendar Features</p>
    +                <p className="m-0">Pre Approved Trusted Talent</p>
    +                <p className="m-0">On-line Payments</p>
    +                <p className="m-0">Pre calculated deductions</p>
    +                {/* <p className="m-0">QuickBooks Integration</p> */}
    +            </div>
    +            {plans.map(p => 
    +                <div key={p.id} className="col-2 text-center">
    +                    <h2>{p.title}</h2>
    +                    <p className="m-0">${p.price_month}/month</p>
    +                    <FeatureIndicator fixed={p.feature_max_shifts} additional={p.price_per_shifts} />
    +                    <FeatureIndicator fixed={p.feature_max_active_employees} additional={p.price_per_active_employees} />
    +                    <FeatureIndicator fixed={p.feature_max_clockins} additional={p.price_per_active_employees} />
    +                    <FeatureIndicator boolean={p.feature_talent_search} />
    +                    <FeatureIndicator boolean={true} />
    +                    <FeatureIndicator fixed={p.feature_max_invites} additional={p.price_per_invites} />
    +                    <FeatureIndicator boolean={true} />
    +                    <FeatureIndicator boolean={p.feature_payroll_report} />
    +                    <FeatureIndicator fixed={p.feature_max_favorite_list_size} />
    +                    <FeatureIndicator boolean={p.feature_smart_calendar} />
    +                    <FeatureIndicator boolean={p.feature_trusted_talents} />
    +                    <FeatureIndicator boolean={p.feature_ach_payments} />
    +                    <FeatureIndicator boolean={p.feature_calculate_deductions} />
    +                    {/* <FeatureIndicator boolean={p.feature_quickbooks_integration} /> */}
    +                    { (!employer.active_subscription || employer.active_subscription.id !== p.id) &&
    +                        <Button className="w-100 mt-2" onClick={() => {
    +                            const noti = Notify.info("Are you sure? You will lose any other subscription you may have", (answer) => {
    +                                if (answer) createSubscription({ subscription: p.id, stripe_cus: customer, stripe_sub: subscription }, props.history);
    +                                noti.remove();
    +                            });
    +                        }}>Apply</Button>
    +                    }
    +                </div>
    +            )}
    +        </div>
    +        <div className="mt-4 pt-4">
    +            <em>If you wish to cancel your subscription please contact us at support@jobcore.co</em>
    +        </div>
    +    </div>);
    +};
    +YourSubscription.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object //contains the data needed for the form to load
    +};
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/docs/views_talents.js.html b/docs/views_talents.js.html new file mode 100644 index 0000000..0cc3a96 --- /dev/null +++ b/docs/views_talents.js.html @@ -0,0 +1,692 @@ + + + + + JSDoc: Source: views/talents.js + + + + + + + + + + +
    + +

    Source: views/talents.js

    + + + + + + +
    +
    +
    import React from "react";
    +import Flux from "@4geeksacademy/react-flux-dash";
    +import PropTypes from "prop-types";
    +import { store, search, searchMe, fetchAllMe } from "../actions.js";
    +import { callback, hasTutorial } from "../utils/tutorial";
    +import {
    +  EmployeeExtendedCard,
    +  Avatar,
    +  Stars,
    +  Theme,
    +  Button,
    +  Wizard,
    +} from "../components/index";
    +import Select from "react-select";
    +import queryString from "query-string";
    +import { Session } from "bc-react-session";
    +import { Notify } from "bc-react-notifier";
    +import { ButtonGroup } from "reactstrap";
    +import { useState, useEffect } from "react";
    +import { Link } from "react-router-dom"
    +//gets the querystring and creats a formData object to be used when opening the rightbar
    +export const getTalentInitialFilters = (catalog) => {
    +  let query = queryString.parse(window.location.search);
    +  if (typeof query == "undefined") return {};
    +  if (!Array.isArray(query.positions))
    +    query.positions =
    +      typeof query.positions == "undefined" ? [] : [query.positions];
    +  if (!Array.isArray(query.badges))
    +    query.badges = typeof query.badges == "undefined" ? [] : [query.badges];
    +  return {
    +    positions: query.positions.map((pId) =>
    +      catalog.positions.find((pos) => pos.value == pId)
    +    ),
    +    badges: query.badges.map((bId) =>
    +      catalog.badges.find((b) => b.value == bId)
    +    ),
    +    rating: catalog.stars.find((rate) => rate.value == query.rating),
    +  };
    +};
    +
    +export const Talent = (data) => {
    +  const session = Session.getPayload();
    +  const _defaults = {
    +    //foo: 'bar',
    +    serialize: function () {
    +      const newShift = {
    +        //foo: 'bar'
    +        favoritelist_set: data.favoriteLists.map((fav) => fav.value),
    +      };
    +
    +      return Object.assign(this, newShift);
    +    },
    +    unserialize: function () {
    +      this.fullName = function () {
    +        return this.user.first_name.length > 0
    +          ? this.user.first_name + " " + this.user.last_name
    +          : "No name specified";
    +      };
    +      if (typeof session.user.profile.employer != "undefined") {
    +        if (typeof this.favoriteLists == "undefined")
    +          this.favoriteLists = this.favoritelist_set.filter(
    +            (fav) => fav.employer == session.user.profile.employer
    +          );
    +        else {
    +          this.favoriteLists = this.favoritelist_set.map((fav) =>
    +            store.get("favlists", fav.id || fav)
    +          );
    +        }
    +      }
    +
    +      return this;
    +    },
    +  };
    +
    +  let _entity = Object.assign(_defaults, data);
    +  return {
    +    validate: () => {
    +      return _entity;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      const _formShift = {
    +        id: _entity.id,
    +        favoriteLists: _entity.favoritelist_set.map((fav) => ({
    +          label: fav.title,
    +          value: fav.id,
    +        })),
    +      };
    +      return _formShift;
    +    },
    +    filters: () => {
    +      const _filters = {
    +        positions: _entity.positions.map((item) => item.value),
    +        rating:
    +          typeof _entity.rating == "object" ? _entity.rating.value : undefined,
    +        badges: _entity.badges.map((item) => item.value),
    +      };
    +      for (let key in _entity)
    +        if (typeof _entity[key] == "function") delete _entity[key];
    +      return Object.assign(_entity, _filters);
    +    },
    +  };
    +};
    +
    +export const ShiftInvite = (data) => {
    +  const user = Session.getPayload().user;
    +  const _defaults = {
    +    //foo: 'bar',
    +    serialize: function () {
    +      const newShiftInvite = {
    +        //foo: 'bar'
    +        sender: user.id,
    +        shifts: data.shifts.map((s) => s.id || s.value.id),
    +        employees: data.employees,
    +      };
    +
    +      return Object.assign(this, newShiftInvite);
    +    },
    +  };
    +
    +  let _entity = Object.assign(_defaults, data);
    +  return {
    +    validate: () => {
    +      return _entity;
    +    },
    +    defaults: () => {
    +      return _defaults;
    +    },
    +    getFormData: () => {
    +      const _formShift = {
    +        employees: _entity.employees || [_entity.id],
    +        shifts: _entity.shifts,
    +      };
    +      return _formShift;
    +    },
    +    filters: () => {
    +      const _filters = {
    +        //positions: _entity.positions.map( item => item.value ),
    +      };
    +      for (let key in _entity)
    +        if (typeof _entity[key] == "function") delete _entity[key];
    +      return Object.assign(_entity, _filters);
    +    },
    +  };
    +};
    +     
    +export class ManageTalents extends Flux.DashView {
    +  constructor() {
    +    super();
    +    this.state = {
    +      // catalog: {this.state.catalog},
    +      DocStatus: "",
    +      empStatus: "unverified",
    +      form: "",
    +      formLoading: false,
    +      employees: [],
    +      runTutorial: hasTutorial(),
    +      pagination: {
    +        first: "",
    +        last: "",
    +        next: "",
    +        previous: "",
    +      },
    +      steps: [
    +        {
    +          target: "#talent_search_header",
    +          content: "In this page you can search our entire talent network",
    +          placement: "right",
    +        },
    +        {
    +          target: "#filter_talent",
    +          content:
    +            "Start by filtering by name, experience, badges or minium star rating",
    +          placement: "left",
    +        },
    +      ],
    +    };
    +    this.handleStatusChange = this.handleStatusChange.bind(this);
    +  }
    +
    +  componentDidMount() {
    +    this.filter();
    +    
    +    const positions = store.getState("positions");
    +
    +    if (!positions) {
    +      this.subscribe(store, "positions", (positions) => {
    +        this.setState({ positions });
    +      });
    +    } else this.setState({ positions });
    +
    +    this.subscribe(store, "employees", (employees) => {
    +      if (Array.isArray(employees) && employees.length !== 0)
    +        this.setState({ employees });
    +    });
    +    const lists = store.getState("favlists");
    +    // this.subscribe(store, 'favlists', (lists) => {
    +    //     this.setState({ lists });
    +    // });
    +    if (!lists) fetchAllMe(["favlists"]);
    +
    +    this.props.history.listen(() => {
    +      if (this.props.history.location.pathname == "/talents") this.filter("l");
    +      else this.setState({ firstSearch: false });
    +    });
    +    this.setState({ runTutorial: true });
    +    
    +    this.handleStatusChange
    +  }
    +  componentWillUnmount() {
    +    this.handleStatusChange
    +  }
    +  handleStatusChange() {
    +    this.setState({ DocStatus: props.catalog.employee.employment_verification_status });
    +  }
    +  filter(url) {
    +    // search('employees', window.location.search);
    +    let queries = window.location.search;
    +
    +    if (queries) queries = "&" + queries.substring(1);
    +    if (url && url.length > 50) {
    +      const page = url.split("employees")[1];
    +      if (page) {
    +        search(`employees`, `${page + queries}`).then((data) => {
    +          this.setState({
    +            employees: data.results,
    +            pagination: data,
    +          });
    +        });
    +      } else null;
    +    } else {
    +      search(`employees`, `?envelope=true&limit=50${queries}`).then((data) => {
    +        this.setState({
    +          employees: data.results,
    +          pagination: data,
    +        });
    +      });
    +    }
    +  }
    +  
    +  
    +  render() {
    +    const employees = this.state.employees
    +    function checkEmployability(empl) {
    +      const today = new Date()
    +      const empDate = new Date(empl.employability_expired_at)
    +      if (empl.employability_expired_at===null || empl.employability_expired_at===undefined || empDate.getTime()===0) {
    +        return "The employee does not have a defined expiration date for employability"
    +      } else if (empDate.getTime()<today.getTime()){
    +          empl.employment_verification_status = "NOT_APPROVED"
    +        return "Is NOT eligible to work"
    +      } else {
    +        return "It IS eligible to work"
    +      }
    +    }
    +    const today = new Date()
    +    //console.log("empleados#########", employees.map(checkEmployability)) // leave this one for now [Israel]
    +    const positions = this.state.positions;
    +    if (this.state.firstSearch) return <p>Please search for an employee</p>;
    +    const allowLevels = window.location.search != "";
    +    const filteredEmployeesList = (empStatus) => {
    +      if (empStatus==="rejected") {
    +        const notAprovedEmpList = employees.filter((employees) => employees.employment_verification_status==="NOT_APPROVED")
    +        return  notAprovedEmpList
    +      } else if (empStatus==="verified") {
    +        const verifiedEmpList = employees.filter((employees) => employees.employment_verification_status==="APPROVED")
    +        const sortedVerifiedEmpList = verifiedEmpList.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))
    +        return sortedVerifiedEmpList
    +      } else {
    +        const pendingEmpList = employees.filter((employees) => employees.employment_verification_status==="PENDING")
    +        const beingReviewedEmpList = employees.filter((employees) => employees.employment_verification_status==="BEING_REVIEWED")
    +        const missDocsEmpList = employees.filter((employees) => employees.employment_verification_status==="MISSING_DOCUMENTS")
    +        const unverifiedEmpList = pendingEmpList.concat(beingReviewedEmpList, missDocsEmpList)
    +        const sortedUnverifiedEmpList = unverifiedEmpList.slice().sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at))
    +        return sortedUnverifiedEmpList
    +      }
    +    }
    +    const finalList = filteredEmployeesList(this.state.empStatus)
    +    return (
    +      <div className="p-1 listcontents">
    +        <Theme.Consumer>
    +          {({ bar }) => (
    +            <span>
    +              {/* <Wizard continuous
    +                      steps={this.state.steps}
    +                      run={this.state.runTutorial}
    +                      callback={callback}
    +                    /> */}
    +              <h1>
    +                <span id="talent_search_header">Talent Search</span>
    +              </h1>
    +              <div className="my-2">
    +                <button onClick={() => {this.setState({ empStatus: this.state.empStatus="rejected" }) 
    +                }} type="button" className="btn btn-secondary" style={this.state.empStatus==="rejected" ? {background:"red"} : {}}>Rejected</button>
    +                <button onClick={() => {this.setState({ empStatus: this.state.empStatus="verified" }) 
    +                }} type="button" className="btn btn-secondary" style={this.state.empStatus==="verified" ? {background:"green"} : {}}>Verified</button>
    +                <button onClick={() => {this.setState({ empStatus: this.state.empStatus="unverified" })
    +                }} type="button" className="btn btn-secondary" style={this.state.empStatus==="unverified" ? {background:"#FFDB58", color:"gray"} : {}}>Unverified</button>
    +              </div>
    +              {// this.state.employees.map((s, i) => (
    +                finalList.map((s, i) => (
    +                <EmployeeExtendedCard
    +                  form={this.state.form}
    +                  formLoading={this.state.formLoading}
    +                  key={i}
    +                  employee={s}
    +                  hover={true}
    +                  positions={positions}
    +                  onClick={(e) =>
    +                    bar.show({ slug: "show_single_talent", data: s })
    +                  }
    +                  defineEmployee={(e) =>
    +                    bar.show({ slug: "define_employee", data: s})
    +                  }
    +                >
    +                  <Button
    +                    className="btn btn-outline-dark"
    +                    onClick={() =>
    +                      bar.show({ slug: "add_to_favlist", data: s, allowLevels })
    +                    }
    +                  >
    +                    Add to Favorites
    +                  </Button>
    +                  <Button
    +                    className="btn btn-outline-dark"
    +                    onClick={() =>
    +                      bar.show({
    +                        slug: "invite_talent_to_shift",
    +                        data: s,
    +                        allowLevels,
    +                      })
    +                    }
    +                  >
    +                    Invite
    +                  </Button>
    +                </EmployeeExtendedCard>
    +              ))}
    +              
    +              <div className="row mt-4 justify-content-center">
    +                <div className="col">
    +                  <nav aria-label="Page navigation example">
    +                    <ul className="pagination">
    +                      {this.state.pagination.first && (
    +                        <li className="page-item">
    +                          <span
    +                            className="page-link"
    +                            aria-label="Previous"
    +                            style={{ cursor: "pointer", color: "black" }}
    +                            onClick={() =>
    +                              this.filter(this.state.pagination.first)
    +                            }
    +                          >
    +                            <span aria-hidden="true">
    +                              <i className="fas fa-chevron-left"></i>
    +                              <i className="fas fa-chevron-left"></i>
    +                            </span>
    +                            <span className="sr-only">{"First"}</span>
    +                          </span>
    +                        </li>
    +                      )}
    +                      {this.state.pagination.previous && (
    +                        <li className="page-item">
    +                          <span
    +                            className="page-link"
    +                            style={{ cursor: "pointer", color: "black" }}
    +                            onClick={() =>
    +                              this.filter(this.state.pagination.previous)
    +                            }
    +                          >
    +                            <i className="fas fa-chevron-left"></i>
    +                          </span>
    +                        </li>
    +                      )}
    +                      {this.state.pagination.next && (
    +                        <li className="page-item">
    +                          <span
    +                            className="page-link"
    +                            style={{ cursor: "pointer", color: "black" }}
    +                            onClick={() =>
    +                              this.filter(this.state.pagination.next)
    +                            }
    +                          >
    +                            <i className="fas fa-chevron-right"></i>
    +                          </span>
    +                        </li>
    +                      )}
    +
    +                      {this.state.pagination.last && (
    +                        <li className="page-item">
    +                          <span
    +                            className="page-link"
    +                            onClick={() =>
    +                              this.filter(this.state.pagination.last)
    +                            }
    +                            aria-label="Next"
    +                            style={{ cursor: "pointer", color: "black" }}
    +                          >
    +                            <span aria-hidden="true">
    +                              <i className="fas fa-chevron-right"></i>
    +                              <i className="fas fa-chevron-right"></i>
    +                            </span>
    +                            <span className="sr-only">Last</span>
    +                          </span>
    +                        </li>
    +                      )}
    +                    </ul>
    +                  </nav>
    +                </div>
    +              </div>
    +            </span>
    +          )}
    +        </Theme.Consumer>
    +        
    +      </div>
    +    );
    +  }
    +}
    +
    +/**
    + * AddShift
    + */
    +export const FilterTalents = (props) => {
    +  return (
    +    <form>
    +      <div className="row">
    +        <div className="col-6">
    +          <label>First Name:</label>
    +          <input
    +            className="form-control"
    +            value={props.formData.first_name}
    +            onChange={(e) => props.onChange({ first_name: e.target.value })}
    +          />
    +        </div>
    +        <div className="col-6">
    +          <label>Last Name:</label>
    +          <input
    +            className="form-control"
    +            value={props.formData.last_name}
    +            onChange={(e) => props.onChange({ last_name: e.target.value })}
    +          />
    +        </div>
    +      </div>
    +      <div className="row">
    +        <div className="col-12">
    +          <label>Experience in past positions:</label>
    +          <Select
    +            isMulti
    +            value={props.formData.positions}
    +            onChange={(selectedOption) =>
    +              props.onChange({ positions: selectedOption })
    +            }
    +            options={props.catalog.positions}
    +          />
    +        </div>
    +      </div>
    +
    +      {/* BADGE COMING SOON */}
    +      {/* <div className="row">
    +            <div className="col-12">
    +                <label>Badges:</label>
    +                <Select isMulti
    +                    value={props.formData.badges}
    +                    onChange={(selectedOption)=>props.onChange({badges: selectedOption})}
    +                    options={props.catalog.badges}
    +                />
    +            </div>
    +        </div> */}
    +      <div className="row">
    +        <div className="col-12">
    +          <label>Minimum start rating</label>
    +          <Select
    +            value={props.formData.rating}
    +            onChange={(opt) => props.onChange({ rating: opt })}
    +            options={props.catalog.stars}
    +          />
    +        </div>
    +      </div>
    +      <div className="btn-bar">
    +        <Button color="primary" onClick={() => props.onSave()}>
    +          Apply Filters
    +        </Button>
    +        <Button
    +          color="secondary"
    +          onClick={() => {
    +            props.formData.first_name = "";
    +            props.formData.last_name = "";
    +            props.formData.positions = [];
    +            props.formData.badges = [];
    +            props.formData.rating = "";
    +            props.onSave(false);
    +          }}
    +        >
    +          Clear Filters
    +        </Button>
    +      </div>
    +    </form>
    +  );
    +};
    +FilterTalents.propTypes = {
    +  onSave: PropTypes.func.isRequired,
    +  onCancel: PropTypes.func.isRequired,
    +  onChange: PropTypes.func.isRequired,
    +  formData: PropTypes.object,
    +  catalog: PropTypes.object, //contains the data needed for the form to load
    +};
    +
    +/**
    + * Talent Details
    + * 
    + * Before, the Stars component was rendered inside a p tag, 
    + * now its rendered inside a span tag
    + */
    +export const TalentDetails = (props) => {
    +  const employee = props.catalog.employee;
    +
    +  function reformatPhoneNumber(phoneNumberString) {
    +    var cleaned = ("" + phoneNumberString).replace(/\D/g, "");
    +    var match = cleaned.match(/^(1|)?(\d{3})(\d{3})(\d{4})$/);
    +    if (match) {
    +      var intlCode = match[1] ? "+1 " : "";
    +      return [intlCode, "(", match[2], ") ", match[3], "-", match[4]].join("");
    +    }
    +    return null;
    +  }
    +              
    +  return (
    +    <Theme.Consumer>
    +      {({ bar }) => (
    +        <li className="aplication-details">
    +          <div className="top-bar">
    +            <Button
    +              icon="envelope"
    +              color="primary"
    +              size="small"
    +              rounded={true}
    +              note="Pending Invites"
    +              notePosition="left"
    +              onClick={() =>
    +                bar.show({
    +                  slug: "show_talent_shift_invites",
    +                  data: employee,
    +                  allowLevels: true,
    +                })
    +              }
    +            />
    +          </div>
    +          <Avatar url={employee.user.profile.picture} />
    +          <span>
    +            <Stars
    +              rating={Number(employee.rating)}
    +              jobCount={employee.total_ratings}
    +            />
    +          </span>
    +          <p style={{ fontWeight: "bolder", fontSize: "24px" }}>
    +            {typeof employee.fullName == "function"
    +              ? employee.fullName()
    +              : employee.first_name + " " + employee.last_name}
    +          </p>
    +          <p>
    +            <strong>Email:</strong>{" "}
    +            {employee.user ? employee.user.email : "No email provided"}
    +          </p>
    +          <p>
    +            <strong>Phone number:</strong>{" "}
    +            {employee.user && employee.user.profile.phone_number != ""
    +              ? reformatPhoneNumber(employee.user.profile.phone_number)
    +              : "none"}
    +          </p>
    +          <p>
    +            <strong>Position(s):</strong>{" "}
    +            {employee.positions.map((p) => p.title).join(", ")}
    +          </p>
    +          <p>
    +            <strong>
    +              ${Number(employee.minimum_hourly_rate).toFixed(2)}/hr minimum
    +              expected rate
    +            </strong>
    +          </p>
    +          <p>{employee.user.profile.bio}</p>
    +
    +          {/* {employee.positions.length > 0 && <p>{employee.positions.map(p => <ul key={p.id}><li className="badge badge-primary" style={{columnCount: 3}}>{p.title}</li></ul>)}</p>} */}
    +          {employee.badges.length > 0 && (
    +            <p>
    +              {employee.badges.map((b) => (
    +                <span key={b.id} className="badge badge-secondary">
    +                  {b.title}
    +                </span>
    +              ))}
    +            </p>
    +          )}
    +          <div className="btn-bar">
    +            <Button
    +              color="primary"
    +              onClick={() =>
    +                bar.show({
    +                  slug: "invite_talent_to_shift",
    +                  data: employee,
    +                  allowLevels: true,
    +                })
    +              }
    +            >
    +              Invite to shift
    +            </Button>
    +            <Button
    +              color="success"
    +              onClick={() =>
    +                bar.show({
    +                  slug: "add_to_favlist",
    +                  data: employee,
    +                  allowLevels: true,
    +                })
    +              }
    +            >
    +              Add to favorites
    +            </Button>
    +          </div>
    +          <div className="btn-bar">
    +            <Button
    +              color="danger"
    +              onClick={() =>
    +                
    +                bar.show({
    +                  slug: "check_employee_documents",
    +                  data: employee,
    +                  allowLevels: true,
    +                })
    +              }
    +            >
    +              Check employee documents
    +            </Button>
    +            </div>
    +        </li>
    +      )}
    +    </Theme.Consumer>
    +  );
    +};
    +TalentDetails.propTypes = {
    +  catalog: PropTypes.object.isRequired,
    +};
    +
    +
    +
    + + + + +
    + + + +
    + +
    + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) +
    + + + + + diff --git a/jsdoc-custom-template/README.md b/jsdoc-custom-template/README.md new file mode 100644 index 0000000..1946bef --- /dev/null +++ b/jsdoc-custom-template/README.md @@ -0,0 +1,12 @@ +The default template for JSDoc 3 uses: [the Taffy Database library](http://taffydb.com/) and the [Underscore Template library](http://underscorejs.org/). + + +## Generating Typeface Fonts + +The default template uses the [OpenSans](https://www.google.com/fonts/specimen/Open+Sans) typeface. The font files can be regenerated as follows: + +1. Open the [OpenSans page at Font Squirrel](). +2. Click on the 'Webfont Kit' tab. +3. Either leave the subset drop-down as 'Western Latin (Default)', or, if we decide we need more glyphs, than change it to 'No Subsetting'. +4. Click the 'DOWNLOAD @FONT-FACE KIT' button. +5. For each typeface variant we plan to use, copy the 'eot', 'svg' and 'woff' files into the 'templates/default/static/fonts' directory. diff --git a/jsdoc-custom-template/publish.js b/jsdoc-custom-template/publish.js new file mode 100644 index 0000000..2e3397f --- /dev/null +++ b/jsdoc-custom-template/publish.js @@ -0,0 +1,692 @@ +const doop = require('jsdoc/util/doop'); +const env = require('jsdoc/env'); +const fs = require('jsdoc/fs'); +const helper = require('jsdoc/util/templateHelper'); +const logger = require('jsdoc/util/logger'); +const path = require('jsdoc/path'); +const taffy = require('taffydb').taffy; +const template = require('jsdoc/template'); +const util = require('util'); + +const htmlsafe = helper.htmlsafe; +const linkto = helper.linkto; +const resolveAuthorLinks = helper.resolveAuthorLinks; +const hasOwnProp = Object.prototype.hasOwnProperty; + +let data; +let view; + +let outdir = path.normalize(env.opts.destination); + +function find(spec) { + return helper.find(data, spec); +} + +function tutoriallink(tutorial) { + return helper.toTutorial(tutorial, null, { + tag: 'em', + classname: 'disabled', + prefix: 'Tutorial: ' + }); +} + +function getAncestorLinks(doclet) { + return helper.getAncestorLinks(data, doclet); +} + +function hashToLink(doclet, hash) { + let url; + + if ( !/^(#.+)/.test(hash) ) { + return hash; + } + + url = helper.createLink(doclet); + url = url.replace(/(#.+|$)/, hash); + + return `${hash}`; +} + +function needsSignature({kind, type, meta}) { + let needsSig = false; + + // function and class definitions always get a signature + if (kind === 'function' || kind === 'class') { + needsSig = true; + } + // typedefs that contain functions get a signature, too + else if (kind === 'typedef' && type && type.names && + type.names.length) { + for (let i = 0, l = type.names.length; i < l; i++) { + if (type.names[i].toLowerCase() === 'function') { + needsSig = true; + break; + } + } + } + // and namespaces that are functions get a signature (but finding them is a + // bit messy) + else if (kind === 'namespace' && meta && meta.code && + meta.code.type && meta.code.type.match(/[Ff]unction/)) { + needsSig = true; + } + + return needsSig; +} + +function getSignatureAttributes({optional, nullable}) { + const attributes = []; + + if (optional) { + attributes.push('opt'); + } + + if (nullable === true) { + attributes.push('nullable'); + } + else if (nullable === false) { + attributes.push('non-null'); + } + + return attributes; +} + +function updateItemName(item) { + const attributes = getSignatureAttributes(item); + let itemName = item.name || ''; + + if (item.variable) { + itemName = `…${itemName}`; + } + + if (attributes && attributes.length) { + itemName = util.format( '%s%s', itemName, + attributes.join(', ') ); + } + + return itemName; +} + +function addParamAttributes(params) { + return params.filter(({name}) => name && !name.includes('.')).map(updateItemName); +} + +function buildItemTypeStrings(item) { + const types = []; + + if (item && item.type && item.type.names) { + item.type.names.forEach(name => { + types.push( linkto(name, htmlsafe(name)) ); + }); + } + + return types; +} + +function buildAttribsString(attribs) { + let attribsString = ''; + + if (attribs && attribs.length) { + attribsString = htmlsafe( util.format('(%s) ', attribs.join(', ')) ); + } + + return attribsString; +} + +function addNonParamAttributes(items) { + let types = []; + + items.forEach(item => { + types = types.concat( buildItemTypeStrings(item) ); + }); + + return types; +} + +function addSignatureParams(f) { + const params = f.params ? addParamAttributes(f.params) : []; + + f.signature = util.format( '%s(%s)', (f.signature || ''), params.join(', ') ); +} + +function addSignatureReturns(f) { + const attribs = []; + let attribsString = ''; + let returnTypes = []; + let returnTypesString = ''; + const source = f.yields || f.returns; + + // jam all the return-type attributes into an array. this could create odd results (for example, + // if there are both nullable and non-nullable return types), but let's assume that most people + // who use multiple @return tags aren't using Closure Compiler type annotations, and vice-versa. + if (source) { + source.forEach(item => { + helper.getAttribs(item).forEach(attrib => { + if (!attribs.includes(attrib)) { + attribs.push(attrib); + } + }); + }); + + attribsString = buildAttribsString(attribs); + } + + if (source) { + returnTypes = addNonParamAttributes(source); + } + if (returnTypes.length) { + returnTypesString = util.format( ' → %s{%s}', attribsString, returnTypes.join('|') ); + } + + f.signature = `${f.signature || ''}${returnTypesString}`; +} + +function addSignatureTypes(f) { + const types = f.type ? buildItemTypeStrings(f) : []; + + f.signature = `${f.signature || ''}${types.length ? ` :${types.join('|')}` : ''}`; +} + +function addAttribs(f) { + const attribs = helper.getAttribs(f); + const attribsString = buildAttribsString(attribs); + + f.attribs = util.format('%s', attribsString); +} + +function shortenPaths(files, commonPrefix) { + Object.keys(files).forEach(file => { + files[file].shortened = files[file].resolved.replace(commonPrefix, '') + // always use forward slashes + .replace(/\\/g, '/'); + }); + + return files; +} + +function getPathFromDoclet({meta}) { + if (!meta) { + return null; + } + + return meta.path && meta.path !== 'null' ? + path.join(meta.path, meta.filename) : + meta.filename; +} + +function generate(title, docs, filename, resolveLinks) { + let docData; + let html; + let outpath; + + resolveLinks = resolveLinks !== false; + + docData = { + env: env, + title: title, + docs: docs + }; + + outpath = path.join(outdir, filename); + html = view.render('container.tmpl', docData); + + if (resolveLinks) { + html = helper.resolveLinks(html); // turn {@link foo} into foo + } + + fs.writeFileSync(outpath, html, 'utf8'); +} + +function generateSourceFiles(sourceFiles, encoding = 'utf8') { + Object.keys(sourceFiles).forEach(file => { + let source; + // links are keyed to the shortened path in each doclet's `meta.shortpath` property + const sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened); + + helper.registerLink(sourceFiles[file].shortened, sourceOutfile); + + try { + source = { + kind: 'source', + code: helper.htmlsafe( fs.readFileSync(sourceFiles[file].resolved, encoding) ) + }; + } + catch (e) { + logger.error('Error while generating source file %s: %s', file, e.message); + } + + generate(`Source: ${sourceFiles[file].shortened}`, [source], sourceOutfile, + false); + }); +} + +/** + * Look for classes or functions with the same name as modules (which indicates that the module + * exports only that class or function), then attach the classes or functions to the `module` + * property of the appropriate module doclets. The name of each class or function is also updated + * for display purposes. This function mutates the original arrays. + * + * @private + * @param {Array.} doclets - The array of classes and functions to + * check. + * @param {Array.} modules - The array of module doclets to search. + */ +function attachModuleSymbols(doclets, modules) { + const symbols = {}; + + // build a lookup table + doclets.forEach(symbol => { + symbols[symbol.longname] = symbols[symbol.longname] || []; + symbols[symbol.longname].push(symbol); + }); + + modules.forEach(module => { + if (symbols[module.longname]) { + module.modules = symbols[module.longname] + // Only show symbols that have a description. Make an exception for classes, because + // we want to show the constructor-signature heading no matter what. + .filter(({description, kind}) => description || kind === 'class') + .map(symbol => { + symbol = doop(symbol); + + if (symbol.kind === 'class' || symbol.kind === 'function') { + symbol.name = `${symbol.name.replace('module:', '(require("')}"))`; + } + + return symbol; + }); + } + }); +} + +function buildMemberNav(items, itemHeading, itemsSeen, linktoFn) { + let nav = ''; + + if (items.length) { + let itemsNav = ''; + + items.forEach(item => { + let displayName; + + if ( !hasOwnProp.call(item, 'longname') ) { + itemsNav += `
  • ${linktoFn('', item.name)}
  • `; + } + else if ( !hasOwnProp.call(itemsSeen, item.longname) ) { + if (env.conf.templates.default.useLongnameInNav) { + displayName = item.longname; + } else { + displayName = item.name; + } + itemsNav += `
  • ${linktoFn(item.longname, displayName.replace(/\b(module|event):/g, ''))}
  • `; + + itemsSeen[item.longname] = true; + } + }); + + if (itemsNav !== '') { + nav += `

    ${itemHeading}

      ${itemsNav}
    `; + } + } + + return nav; +} + +function linktoTutorial(longName, name) { + return tutoriallink(name); +} + +function linktoExternal(longName, name) { + return linkto(longName, name.replace(/(^"|"$)/g, '')); +} + +/** + * Create the navigation sidebar. + * @param {object} members The members that will be used to create the sidebar. + * @param {array} members.classes + * @param {array} members.externals + * @param {array} members.globals + * @param {array} members.mixins + * @param {array} members.modules + * @param {array} members.namespaces + * @param {array} members.tutorials + * @param {array} members.events + * @param {array} members.interfaces + * @return {string} The HTML for the navigation sidebar. + */ +function buildNav(members) { + let globalNav; + let nav = '

    JSDoc Home

    '; + const seen = {}; + const seenTutorials = {}; + + nav += buildMemberNav(members.modules, 'Modules', {}, linkto); + nav += buildMemberNav(members.externals, 'Externals', seen, linktoExternal); + nav += buildMemberNav(members.namespaces, 'Namespaces', seen, linkto); + nav += buildMemberNav(members.classes, 'Classes', seen, linkto); + nav += buildMemberNav(members.interfaces, 'Interfaces', seen, linkto); + nav += buildMemberNav(members.events, 'Events', seen, linkto); + nav += buildMemberNav(members.mixins, 'Mixins', seen, linkto); + nav += buildMemberNav(members.tutorials, 'Tutorials', seenTutorials, linktoTutorial); + + if (members.globals.length) { + globalNav = ''; + + members.globals.forEach(({kind, longname, name}) => { + if ( kind !== 'typedef' && !hasOwnProp.call(seen, longname) ) { + globalNav += `
  • ${linkto(longname, name)}
  • `; + } + seen[longname] = true; + }); + + if (!globalNav) { + // turn the heading into a link so you can actually get to the global page + nav += `

    ${linkto('global', 'Global')}

    `; + } + else { + nav += `

    Global

      ${globalNav}
    `; + } + } + + return nav; +} + +/** + @param {TAFFY} taffyData See . + @param {object} opts + @param {Tutorial} tutorials + */ +exports.publish = (taffyData, opts, tutorials) => { + let classes; + let conf; + let externals; + let files; + let fromDir; + let globalUrl; + let indexUrl; + let interfaces; + let members; + let mixins; + let modules; + let namespaces; + let outputSourceFiles; + let packageInfo; + let packages; + const sourceFilePaths = []; + let sourceFiles = {}; + let staticFileFilter; + let staticFilePaths; + let staticFiles; + let staticFileScanner; + let templatePath; + + data = taffyData; + + conf = env.conf.templates || {}; + conf.default = conf.default || {}; + + templatePath = path.normalize(opts.template); + view = new template.Template( path.join(templatePath, 'tmpl') ); + + // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness + // doesn't try to hand them out later + indexUrl = helper.getUniqueFilename('index'); + // don't call registerLink() on this one! 'index' is also a valid longname + + globalUrl = helper.getUniqueFilename('global'); + helper.registerLink('global', globalUrl); + + // set up templating + view.layout = conf.default.layoutFile ? + path.getResourcePath(path.dirname(conf.default.layoutFile), + path.basename(conf.default.layoutFile) ) : + 'layout.tmpl'; + + // set up tutorials for helper + helper.setTutorials(tutorials); + + data = helper.prune(data); + data.sort('longname, version, since'); + helper.addEventListeners(data); + + data().each(doclet => { + let sourcePath; + + doclet.attribs = ''; + + if (doclet.examples) { + doclet.examples = doclet.examples.map(example => { + let caption; + let code; + + if (example.match(/^\s*([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i)) { + caption = RegExp.$1; + code = RegExp.$3; + } + + return { + caption: caption || '', + code: code || example + }; + }); + } + if (doclet.see) { + doclet.see.forEach((seeItem, i) => { + doclet.see[i] = hashToLink(doclet, seeItem); + }); + } + + // build a list of source files + if (doclet.meta) { + sourcePath = getPathFromDoclet(doclet); + sourceFiles[sourcePath] = { + resolved: sourcePath, + shortened: null + }; + if (!sourceFilePaths.includes(sourcePath)) { + sourceFilePaths.push(sourcePath); + } + } + }); + + // update outdir if necessary, then create outdir + packageInfo = ( find({kind: 'package'}) || [] )[0]; + if (packageInfo && packageInfo.name) { + outdir = path.join( outdir, packageInfo.name, (packageInfo.version || '') ); + } + fs.mkPath(outdir); + + // copy the template's static files to outdir + fromDir = path.join(templatePath, 'static'); + staticFiles = fs.ls(fromDir, 3); + + staticFiles.forEach(fileName => { + const toDir = fs.toDir( fileName.replace(fromDir, outdir) ); + + fs.mkPath(toDir); + fs.copyFileSync(fileName, toDir); + }); + + // copy user-specified static files to outdir + if (conf.default.staticFiles) { + // The canonical property name is `include`. We accept `paths` for backwards compatibility + // with a bug in JSDoc 3.2.x. + staticFilePaths = conf.default.staticFiles.include || + conf.default.staticFiles.paths || + []; + staticFileFilter = new (require('jsdoc/src/filter').Filter)(conf.default.staticFiles); + staticFileScanner = new (require('jsdoc/src/scanner').Scanner)(); + + staticFilePaths.forEach(filePath => { + let extraStaticFiles; + + filePath = path.resolve(env.pwd, filePath); + extraStaticFiles = staticFileScanner.scan([filePath], 10, staticFileFilter); + + extraStaticFiles.forEach(fileName => { + const sourcePath = fs.toDir(filePath); + const toDir = fs.toDir( fileName.replace(sourcePath, outdir) ); + + fs.mkPath(toDir); + fs.copyFileSync(fileName, toDir); + }); + }); + } + + if (sourceFilePaths.length) { + sourceFiles = shortenPaths( sourceFiles, path.commonPrefix(sourceFilePaths) ); + } + data().each(doclet => { + let docletPath; + const url = helper.createLink(doclet); + + helper.registerLink(doclet.longname, url); + + // add a shortened version of the full path + if (doclet.meta) { + docletPath = getPathFromDoclet(doclet); + docletPath = sourceFiles[docletPath].shortened; + if (docletPath) { + doclet.meta.shortpath = docletPath; + } + } + }); + + data().each(doclet => { + const url = helper.longnameToUrl[doclet.longname]; + + if (url.includes('#')) { + doclet.id = helper.longnameToUrl[doclet.longname].split(/#/).pop(); + } + else { + doclet.id = doclet.name; + } + + if ( needsSignature(doclet) ) { + addSignatureParams(doclet); + addSignatureReturns(doclet); + addAttribs(doclet); + } + }); + + // do this after the urls have all been generated + data().each(doclet => { + doclet.ancestors = getAncestorLinks(doclet); + + if (doclet.kind === 'member') { + addSignatureTypes(doclet); + addAttribs(doclet); + } + + if (doclet.kind === 'constant') { + addSignatureTypes(doclet); + addAttribs(doclet); + doclet.kind = 'member'; + } + }); + + members = helper.getMembers(data); + members.tutorials = tutorials.children; + + // output pretty-printed source files by default + outputSourceFiles = conf.default && conf.default.outputSourceFiles !== false; + + // add template helpers + view.find = find; + view.linkto = linkto; + view.resolveAuthorLinks = resolveAuthorLinks; + view.tutoriallink = tutoriallink; + view.htmlsafe = htmlsafe; + view.outputSourceFiles = outputSourceFiles; + + // once for all + view.nav = buildNav(members); + attachModuleSymbols( find({ longname: {left: 'module:'} }), members.modules ); + + // generate the pretty-printed source files first so other pages can link to them + if (outputSourceFiles) { + generateSourceFiles(sourceFiles, opts.encoding); + } + + if (members.globals.length) { generate('Global', [{kind: 'globalobj'}], globalUrl); } + + // index page displays information from package.json and lists files + files = find({kind: 'file'}); + packages = find({kind: 'package'}); + + generate('JSDoc Home', + packages.concat( + [{ + kind: 'mainpage', + readme: opts.readme, + longname: (opts.mainpagetitle) ? opts.mainpagetitle : 'Main Page' + }] + ).concat(files), indexUrl); + + // set up the lists that we'll use to generate pages + classes = taffy(members.classes); + modules = taffy(members.modules); + namespaces = taffy(members.namespaces); + mixins = taffy(members.mixins); + externals = taffy(members.externals); + interfaces = taffy(members.interfaces); + + Object.keys(helper.longnameToUrl).forEach(longname => { + const myClasses = helper.find(classes, {longname: longname}); + const myExternals = helper.find(externals, {longname: longname}); + const myInterfaces = helper.find(interfaces, {longname: longname}); + const myMixins = helper.find(mixins, {longname: longname}); + const myModules = helper.find(modules, {longname: longname}); + const myNamespaces = helper.find(namespaces, {longname: longname}); + + if (myModules.length) { + generate(`Module: ${myModules[0].name}`, myModules, helper.longnameToUrl[longname]); + } + + if (myClasses.length) { + generate(`Class: ${myClasses[0].name}`, myClasses, helper.longnameToUrl[longname]); + } + + if (myNamespaces.length) { + generate(`Namespace: ${myNamespaces[0].name}`, myNamespaces, helper.longnameToUrl[longname]); + } + + if (myMixins.length) { + generate(`Mixin: ${myMixins[0].name}`, myMixins, helper.longnameToUrl[longname]); + } + + if (myExternals.length) { + generate(`External: ${myExternals[0].name}`, myExternals, helper.longnameToUrl[longname]); + } + + if (myInterfaces.length) { + generate(`Interface: ${myInterfaces[0].name}`, myInterfaces, helper.longnameToUrl[longname]); + } + }); + + // TODO: move the tutorial functions to templateHelper.js + function generateTutorial(title, tutorial, filename) { + const tutorialData = { + title: title, + header: tutorial.title, + content: tutorial.parse(), + children: tutorial.children + }; + const tutorialPath = path.join(outdir, filename); + let html = view.render('tutorial.tmpl', tutorialData); + + // yes, you can use {@link} in tutorials too! + html = helper.resolveLinks(html); // turn {@link foo} into foo + + fs.writeFileSync(tutorialPath, html, 'utf8'); + } + + // tutorials can have only one parent so there is no risk for loops + function saveChildren({children}) { + children.forEach(child => { + generateTutorial(`Tutorial: ${child.title}`, child, helper.tutorialToUrl(child.name)); + saveChildren(child); + }); + } + + saveChildren(tutorials); +}; diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.eot b/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..5d20d916338a5890a033952e2e07ba7380f5a7d3 GIT binary patch literal 19544 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 literal 0 HcmV?d00001 diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.svg b/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.svg new file mode 100644 index 0000000..3ed7be4 --- /dev/null +++ b/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.svg @@ -0,0 +1,1830 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.woff b/jsdoc-custom-template/static/fonts/OpenSans-Bold-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..1205787b0ed50db71ebd4f8a7f85d106721ff258 GIT binary patch literal 22432 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 ig3W + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jsdoc-custom-template/static/fonts/OpenSans-BoldItalic-webfont.woff b/jsdoc-custom-template/static/fonts/OpenSans-BoldItalic-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..ed760c0628b6a0026041f5b8bba466a0471fd2e0 GIT binary patch literal 23048 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Italic-webfont.woff b/jsdoc-custom-template/static/fonts/OpenSans-Italic-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..ff652e64356b538c001423b6aedefcf1ee66cd17 GIT binary patch literal 23188 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 literal 0 HcmV?d00001 diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.svg b/jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 0000000..11a472c --- /dev/null +++ b/jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.woff b/jsdoc-custom-template/static/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e786074813a27d0a7a249047832988d5bf0fe756 GIT binary patch literal 22248 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 literal 0 HcmV?d00001 diff --git a/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.eot b/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..8f445929ffb03b50e98c2a2f7d831a0cb1b276a2 GIT binary patch literal 20535 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 Z%0#`NexefPFV?Xj51H#Y#AC7WXn+Jg($4?@ literal 0 HcmV?d00001 diff --git a/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.svg b/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.svg new file mode 100644 index 0000000..431d7e3 --- /dev/null +++ b/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.svg @@ -0,0 +1,1835 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.woff b/jsdoc-custom-template/static/fonts/OpenSans-LightItalic-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..43e8b9e6cc061ff17fd2903075cbde12715512b3 GIT binary patch literal 23400 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 + diff --git a/jsdoc-custom-template/tmpl/container.tmpl b/jsdoc-custom-template/tmpl/container.tmpl new file mode 100644 index 0000000..1b94004 --- /dev/null +++ b/jsdoc-custom-template/tmpl/container.tmpl @@ -0,0 +1,196 @@ + + + + + + + + + +
    + +
    + +

    + +

    + +
    + + + + +
    + + + +
    + +
    +
    + + +
    + + + + + + + + + +
    + + + + + +

    Example 1? 's':'' ?>

    + + + +
    + + +

    Extends

    + + + + + +

    Requires

    + +
      +
    • +
    + + + +

    Classes

    + +
    +
    +
    +
    + + + +

    Interfaces

    + +
    +
    +
    +
    + + + +

    Mixins

    + +
    +
    +
    +
    + + + +

    Namespaces

    + +
    +
    +
    +
    + + + +

    Members

    + + + + + + + +

    Methods

    + + + + + + + +

    Type Definitions

    + + + + + + + + + +

    Events

    + + + + + +
    + +
    + + + diff --git a/jsdoc-custom-template/tmpl/details.tmpl b/jsdoc-custom-template/tmpl/details.tmpl new file mode 100644 index 0000000..4a5bd49 --- /dev/null +++ b/jsdoc-custom-template/tmpl/details.tmpl @@ -0,0 +1,143 @@ +" + data.defaultvalue + ""; + defaultObjectClass = ' class="object-value"'; +} +?> + + +
    Properties:
    + + + + + +
    + + +
    Version:
    +
    + + + +
    Since:
    +
    + + + +
    Inherited From:
    +
    • + +
    + + + +
    Overrides:
    +
    • + +
    + + + +
    Implementations:
    +
      + +
    • + +
    + + + +
    Implements:
    +
      + +
    • + +
    + + + +
    Mixes In:
    + +
      + +
    • + +
    + + + +
    Deprecated:
    • Yes
    + + + +
    Author:
    +
    +
      +
    • +
    +
    + + + + + + + + +
    License:
    +
    + + + +
    Default Value:
    +
      + > +
    + + + +
    Source:
    +
    • + , +
    + + + +
    Tutorials:
    +
    +
      +
    • +
    +
    + + + +
    See:
    +
    +
      +
    • +
    +
    + + + +
    To Do:
    +
    +
      +
    • +
    +
    + +
    diff --git a/jsdoc-custom-template/tmpl/example.tmpl b/jsdoc-custom-template/tmpl/example.tmpl new file mode 100644 index 0000000..e87caa5 --- /dev/null +++ b/jsdoc-custom-template/tmpl/example.tmpl @@ -0,0 +1,2 @@ + +
    diff --git a/jsdoc-custom-template/tmpl/examples.tmpl b/jsdoc-custom-template/tmpl/examples.tmpl new file mode 100644 index 0000000..04d975e --- /dev/null +++ b/jsdoc-custom-template/tmpl/examples.tmpl @@ -0,0 +1,13 @@ + +

    + +
    + \ No newline at end of file diff --git a/jsdoc-custom-template/tmpl/exceptions.tmpl b/jsdoc-custom-template/tmpl/exceptions.tmpl new file mode 100644 index 0000000..9cef6c7 --- /dev/null +++ b/jsdoc-custom-template/tmpl/exceptions.tmpl @@ -0,0 +1,32 @@ + + +
    +
    +
    + +
    +
    +
    +
    +
    +
    + Type +
    +
    + +
    +
    +
    +
    +
    + +
    + + + + + +
    + diff --git a/jsdoc-custom-template/tmpl/layout.tmpl b/jsdoc-custom-template/tmpl/layout.tmpl new file mode 100644 index 0000000..bc7e335 --- /dev/null +++ b/jsdoc-custom-template/tmpl/layout.tmpl @@ -0,0 +1,38 @@ + + + + + JSDoc: <?js= title ?> + + + + + + + + + + +
    + +

    + + +
    + + + +
    + +
    + + + + + diff --git a/jsdoc-custom-template/tmpl/mainpage.tmpl b/jsdoc-custom-template/tmpl/mainpage.tmpl new file mode 100644 index 0000000..64e9e59 --- /dev/null +++ b/jsdoc-custom-template/tmpl/mainpage.tmpl @@ -0,0 +1,14 @@ + + + +

    + + + +
    +
    +
    + diff --git a/jsdoc-custom-template/tmpl/members.tmpl b/jsdoc-custom-template/tmpl/members.tmpl new file mode 100644 index 0000000..154c17b --- /dev/null +++ b/jsdoc-custom-template/tmpl/members.tmpl @@ -0,0 +1,38 @@ + +

    + + +

    + + + +
    + +
    + + + +
    Type:
    +
      +
    • + +
    • +
    + + + + + +
    Fires:
    +
      +
    • +
    + + + +
    Example 1? 's':'' ?>
    + + diff --git a/jsdoc-custom-template/tmpl/method.tmpl b/jsdoc-custom-template/tmpl/method.tmpl new file mode 100644 index 0000000..0125fe2 --- /dev/null +++ b/jsdoc-custom-template/tmpl/method.tmpl @@ -0,0 +1,131 @@ + + + +

    Constructor

    + + + +

    + + + +

    + + + + +
    + +
    + + + +
    Extends:
    + + + + +
    Type:
    +
      +
    • + +
    • +
    + + + +
    This:
    +
    + + + +
    Parameters:
    + + + + + + +
    Requires:
    +
      +
    • +
    + + + +
    Fires:
    +
      +
    • +
    + + + +
    Listens to Events:
    +
      +
    • +
    + + + +
    Listeners of This Event:
    +
      +
    • +
    + + + +
    Modifies:
    + 1) { ?>
      +
    • +
    + + + + +
    Throws:
    + 1) { ?>
      +
    • +
    + + + + +
    Returns:
    + 1) { ?>
      +
    • +
    + + + + +
    Yields:
    + 1) { ?>
      +
    • +
    + + + + +
    Example 1? 's':'' ?>
    + + diff --git a/jsdoc-custom-template/tmpl/modifies.tmpl b/jsdoc-custom-template/tmpl/modifies.tmpl new file mode 100644 index 0000000..16ccbf8 --- /dev/null +++ b/jsdoc-custom-template/tmpl/modifies.tmpl @@ -0,0 +1,14 @@ + + + +
    +
    + Type +
    +
    + +
    +
    + diff --git a/jsdoc-custom-template/tmpl/params.tmpl b/jsdoc-custom-template/tmpl/params.tmpl new file mode 100644 index 0000000..1fb4049 --- /dev/null +++ b/jsdoc-custom-template/tmpl/params.tmpl @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    + + + + + + <optional>
    + + + + <nullable>
    + + + + <repeatable>
    + +
    + + + + +
    Properties
    + +
    diff --git a/jsdoc-custom-template/tmpl/properties.tmpl b/jsdoc-custom-template/tmpl/properties.tmpl new file mode 100644 index 0000000..40e0909 --- /dev/null +++ b/jsdoc-custom-template/tmpl/properties.tmpl @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeAttributesDefaultDescription
    + + + + + + <optional>
    + + + + <nullable>
    + +
    + + + + +
    Properties
    +
    diff --git a/jsdoc-custom-template/tmpl/returns.tmpl b/jsdoc-custom-template/tmpl/returns.tmpl new file mode 100644 index 0000000..d070459 --- /dev/null +++ b/jsdoc-custom-template/tmpl/returns.tmpl @@ -0,0 +1,19 @@ + +
    + +
    + + + +
    +
    + Type +
    +
    + +
    +
    + \ No newline at end of file diff --git a/jsdoc-custom-template/tmpl/source.tmpl b/jsdoc-custom-template/tmpl/source.tmpl new file mode 100644 index 0000000..e559b5d --- /dev/null +++ b/jsdoc-custom-template/tmpl/source.tmpl @@ -0,0 +1,8 @@ + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/jsdoc-custom-template/tmpl/tutorial.tmpl b/jsdoc-custom-template/tmpl/tutorial.tmpl new file mode 100644 index 0000000..88a0ad5 --- /dev/null +++ b/jsdoc-custom-template/tmpl/tutorial.tmpl @@ -0,0 +1,19 @@ +
    + +
    + 0) { ?> +
      +
    • +
    + + +

    +
    + +
    + +
    + +
    diff --git a/jsdoc-custom-template/tmpl/type.tmpl b/jsdoc-custom-template/tmpl/type.tmpl new file mode 100644 index 0000000..ec2c6c0 --- /dev/null +++ b/jsdoc-custom-template/tmpl/type.tmpl @@ -0,0 +1,7 @@ + + +| + \ No newline at end of file diff --git a/jsdoc-readme.md b/jsdoc-readme.md new file mode 100644 index 0000000..a14e947 --- /dev/null +++ b/jsdoc-readme.md @@ -0,0 +1,7 @@ +## Welcome to the documentation of JobCore's employer-web-client repo. + +### Here, you can explore the documentation of each function and block of code we have. + +### To navigate this documentation, please look at the list of names on the right side of the screen, under "Global." + +### There, you will find each documentation file. \ No newline at end of file diff --git a/jsdoc.json b/jsdoc.json new file mode 100644 index 0000000..45df6e4 --- /dev/null +++ b/jsdoc.json @@ -0,0 +1,18 @@ +{ + "source": { + "include": ["src"], + "includePattern": ".js$", + "excludePattern": "(node_modules/|docs)" + }, + "plugins": ["plugins/markdown"], + "templates": { + "cleverLinks": true, + "monospaceLinks": true + }, + "opts": { + "recurse": true, + "destination": "./docs/", + "template": "./jsdoc-custom-template", + "readme": "./jsdoc-readme.md" + } +} diff --git a/objeto b/objeto deleted file mode 100644 index 6ba0b06..0000000 --- a/objeto +++ /dev/null @@ -1,372 +0,0 @@ -// This is the object of an employee, let's call him Person_1 - -{ - "id": 18, - "positions": [ - { - "id": 1, - "picture": "", - "title": "Server", - "description": "", - "meta_description": "", - "meta_keywords": "", - "created_at": "2018-09-13T19:45:00Z", - "updated_at": "2018-09-13T19:45:00Z", - "status": "ACTIVE", - "pay_rate": [] - }, - { - "id": 2, - "picture": "", - "title": "Kitchen Assistant", - "description": "", - "meta_description": "", - "meta_keywords": "", - "created_at": "2018-09-13T19:45:00Z", - "updated_at": "2018-09-13T19:45:00Z", - "status": "ACTIVE", - "pay_rate": [] - }, - { - "id": 3, - "picture": "", - "title": "Floor Manager", - "description": "", - "meta_description": "", - "meta_keywords": "", - "created_at": "2018-09-13T19:45:00Z", - "updated_at": "2018-09-13T19:45:00Z", - "status": "ACTIVE", - "pay_rate": [] - }, - { - "id": 4, - "picture": "", - "title": "Executive Chef", - "description": "", - "meta_description": "", - "meta_keywords": "", - "created_at": "2018-09-13T19:45:00Z", - "updated_at": "2018-09-13T19:45:00Z", - "status": "ACTIVE", - "pay_rate": [] - }, - { - "id": 5, - "picture": "", - "title": "Runner", - "description": "", - "meta_description": "", - "meta_keywords": "", - "created_at": "2018-09-13T19:45:00Z", - "updated_at": "2018-09-13T19:45:00Z", - "status": "ACTIVE", - "pay_rate": [] - }, - { - "id": 6, - "picture": "", - "title": "Warehouse Utility", - "description": "", - "meta_description": "", - "meta_keywords": "", - "created_at": "2018-09-13T19:45:00Z", - "updated_at": "2018-09-13T19:45:00Z", - "status": "ACTIVE", - "pay_rate": [] - } - ], - "badges": [], - "favoritelist_set": [], - "user": { - "first_name": "Dalai", - "last_name": "Lama", - "email": "dalailama@jobcore.co", - "profile": { - "picture": "https://res.cloudinary.com/hplilzdkc/image/upload/v1650054127/57/profile57.jpg", - "bio": "", - "phone_number": "7863299422" - } - }, - "minimum_hourly_rate": "18.0", - "stop_receiving_invites": false, - "rating": null, - "total_ratings": 0, - "total_pending_payments": 0, - "maximum_job_distance_miles": 50, - "job_count": 0, - "created_at": "2022-04-14T17:25:07.821760Z", - "updated_at": "2022-05-26T18:56:37.236311Z", - "response_time": 0, - "total_invites": 0, - "employability_expired_at": "2022-06-03T00:00:00Z", - "employment_verification_status": "APPROVED", - "filing_status": "SINGLE", - "allowances": 0, - "w4_year": 0, - "step2c_checked": false, - "dependants_deduction": "0.00", - "other_income": "0.00", - "additional_deductions": "0.00", - "extra_withholding": "0.00" -} - - -// This is the object of a shift that has one clock-in registered by Person_1 - -{ - "id": 49, - "venue": { - "title": "250 Catalonia Avenue", - "id": 32, - "latitude": "25.744481", - "longitude": "-80.259618", - "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Server", - "id": 1 - }, - "status": "FILLED", - "clockin": [ - { - "id": 17, - "started_at": "2022-09-29T16:34:14Z", - "latitude_in": "25.74446770000", - "longitude_in": "-80.25945610000", - "distance_in_miles": "0.016", - "latitude_out": "0.00000000000", - "longitude_out": "0.00000000000", - "distance_out_miles": "0.000", - "ended_at": null, - "automatically_closed": false, - "created_at": "2022-09-29T16:34:15.071796Z", - "updated_at": "2022-09-29T16:34:15.074379Z", - "status": "PENDING", - "employee": 18, - "shift": 49, - "author": 57 - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "8.0", - "starting_at": "2022-09-29T16:45:00Z", - "ending_at": "2022-09-29T18:30:25.064000Z", - "created_at": "2022-09-29T16:28:43.344518Z", - "description": "", - "employer": 84, - "author": null, - "candidates": [], - "employees": [ - 18 - ] -} - -// Now, Person_1 has clocked-out to eat his lunch. He will be back - -{ - "count": 1, - "first": null, - "next": null, - "previous": null, - "last": null, - "results": [ - { - "id": 49, - "venue": { - "title": "250 Catalonia Avenue", - "id": 32, - "latitude": "25.744481", - "longitude": "-80.259618", - "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Server", - "id": 1 - }, - "status": "FILLED", - "clockin": [ - { - "id": 17, - "started_at": "2022-09-29T16:34:14Z", - "latitude_in": "25.74446770000", - "longitude_in": "-80.25945610000", - "distance_in_miles": "0.016", - "latitude_out": "25.74446770000", - "longitude_out": "-80.25945610000", - "distance_out_miles": "0.016", - "ended_at": "2022-09-29T16:36:36Z", - "automatically_closed": false, - "created_at": "2022-09-29T16:34:15.071796Z", - "updated_at": "2022-09-29T16:36:37.171524Z", - "status": "PENDING", - "employee": 18, - "shift": 49, - "author": 57 - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "8.0", - "starting_at": "2022-09-29T16:45:00Z", - "ending_at": "2022-09-29T18:30:25.064000Z", - "created_at": "2022-09-29T16:28:43.344518Z", - "description": "", - "employer": 84, - "author": null, - "candidates": [], - "employees": [ - 18 - ] - } - ] -} - - -// Person_1 has finished his lunch, so now he has clocked-in back to keep working - -{ - "count": 1, - "first": null, - "next": null, - "previous": null, - "last": null, - "results": [ - { - "id": 49, - "venue": { - "title": "250 Catalonia Avenue", - "id": 32, - "latitude": "25.744481", - "longitude": "-80.259618", - "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Server", - "id": 1 - }, - "status": "FILLED", - "clockin": [ - { - "id": 17, - "started_at": "2022-09-29T16:34:14Z", - "latitude_in": "25.74446770000", - "longitude_in": "-80.25945610000", - "distance_in_miles": "0.016", - "latitude_out": "25.74446770000", - "longitude_out": "-80.25945610000", - "distance_out_miles": "0.016", - "ended_at": "2022-09-29T16:36:36Z", - "automatically_closed": false, - "created_at": "2022-09-29T16:34:15.071796Z", - "updated_at": "2022-09-29T16:36:37.171524Z", - "status": "PENDING", - "employee": 18, - "shift": 49, - "author": 57 - }, - { - "id": 18, - "started_at": "2022-09-29T16:39:02Z", - "latitude_in": "25.74446770000", - "longitude_in": "-80.25945610000", - "distance_in_miles": "0.016", - "latitude_out": "0.00000000000", - "longitude_out": "0.00000000000", - "distance_out_miles": "0.000", - "ended_at": null, - "automatically_closed": false, - "created_at": "2022-09-29T16:39:03.074089Z", - "updated_at": "2022-09-29T16:39:03.077391Z", - "status": "PENDING", - "employee": 18, - "shift": 49, - "author": 57 - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "8.0", - "starting_at": "2022-09-29T16:45:00Z", - "ending_at": "2022-09-29T18:30:25.064000Z", - "created_at": "2022-09-29T16:28:43.344518Z", - "description": "", - "employer": 84, - "author": null, - "candidates": [], - "employees": [ - 18 - ] - } - ] -} - - -// Person_1 has finished working for the day, now he is ready to clock-out and go home! -{ - "id": 49, - "venue": { - "title": "250 Catalonia Avenue", - "id": 32, - "latitude": "25.744481", - "longitude": "-80.259618", - "street_address": "250 Catalonia Avenue, Coral Gables, FL, USA", - "zip_code": 33134 - }, - "position": { - "title": "Server", - "id": 1 - }, - "status": "FILLED", - "clockin": [ - { - "id": 17, - "started_at": "2022-09-29T16:34:14Z", - "latitude_in": "25.74446770000", - "longitude_in": "-80.25945610000", - "distance_in_miles": "0.016", - "latitude_out": "25.74446770000", - "longitude_out": "-80.25945610000", - "distance_out_miles": "0.016", - "ended_at": "2022-09-29T16:36:36Z", - "automatically_closed": false, - "created_at": "2022-09-29T16:34:15.071796Z", - "updated_at": "2022-09-29T16:36:37.171524Z", - "status": "PENDING", - "employee": 18, - "shift": 49, - "author": 57 - }, - { - "id": 18, - "started_at": "2022-09-29T16:39:02Z", - "latitude_in": "25.74446770000", - "longitude_in": "-80.25945610000", - "distance_in_miles": "0.016", - "latitude_out": "25.74446770000", - "longitude_out": "-80.25945610000", - "distance_out_miles": "0.016", - "ended_at": "2022-09-29T16:43:50Z", - "automatically_closed": false, - "created_at": "2022-09-29T16:39:03.074089Z", - "updated_at": "2022-09-29T16:43:51.131201Z", - "status": "PENDING", - "employee": 18, - "shift": 49, - "author": 57 - } - ], - "maximum_allowed_employees": 1, - "minimum_hourly_rate": "8.0", - "starting_at": "2022-09-29T16:45:00Z", - "ending_at": "2022-09-29T18:30:25.064000Z", - "created_at": "2022-09-29T16:28:43.344518Z", - "description": "", - "employer": 84, - "author": null, - "candidates": [], - "employees": [ - 18 - ] -} \ No newline at end of file diff --git a/out/Employees.js.html b/out/Employees.js.html deleted file mode 100644 index d11b395..0000000 --- a/out/Employees.js.html +++ /dev/null @@ -1,151 +0,0 @@ - - - - - JSDoc: Source: Employees.js - - - - - - - - - - -
    - -

    Source: Employees.js

    - - - - - - -
    -
    -
    import React from "react";
    -import { PieChart } from '../../charts';
    -import { EmployeesData } from "./EmployeesData";
    -
    -// Colors
    -const purple = "#5c00b8";
    -const lightPink = "#eb00eb";
    -
    -/**
    - * @function
    - * @description Creates a page with a table and a graph of all the active/inactive employees.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires PieChart
    - * @requires EmployeesData
    - */
    -export const Employees = () => {
    -
    -    // Data for pie chart -------------------------------------------------------------------------------------
    -
    -    // Taking out the "Totals" from the chart view
    -    let pieData = EmployeesData.filter((item) => { return item.description !== "Total Employees" }) // Taking out the "Totals" from the chart view
    -
    -    // Preparing data to be passed to the chart component
    -    const employeesData = {
    -        labels: pieData.map((data) => data.description),
    -        datasets: [{
    -            label: "Employees",
    -            data: pieData.map((data) => data.qty),
    -            backgroundColor: [
    -                purple, lightPink
    -            ],
    -        }]
    -    }
    -
    -    // Return ----------------------------------------------------------------------------------------------------
    -
    -    return (
    -        <div className="row d-flex d-inline-flex justify-content-between mb-4 w-100">
    -            {/* Left Column Starts */}
    -            <div className="col">
    -                <div className="row d-flex flex-column justify-content-between">
    -                    {/* Employees Table Starts */}
    -                    <div className="col text-center">
    -                        <h2 className="mb-4">Employees Table</h2>
    -
    -                        <table className="table table-bordered text-center">
    -                            <thead className="thead-dark">
    -                                {/* Table columns */}
    -                                <tr>
    -                                    <th scope="col"><h3 className="m-0">Description</h3></th>
    -                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    -                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    -                                </tr>
    -                            </thead>
    -
    -                            <tbody>
    -                                {/* Mapping the data to diplay it as table rows */}
    -                                {EmployeesData.map((item, i) => {
    -                                    return item.description === "Total Employees" ? (
    -                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    -                                            <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    -                                            <td><h3 className="m-0">{item.qty}</h3></td>
    -                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    -                                        </tr>
    -                                    ) :
    -                                        (
    -                                            <tr key={i}>
    -                                                <th scope="row"><h3 className="m-0">{item.description}</h3></th>
    -                                                <td><h3 className="m-0">{item.qty}</h3></td>
    -                                                <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    -                                            </tr>
    -                                        )
    -                                })}
    -                            </tbody>
    -                        </table>
    -                    </div>
    -                    {/* Employees Table Ends */}
    -                </div>
    -            </div>
    -            {/* Left Column Ends */}
    -
    -            {/* Right Column Starts */}
    -            <div className="col">
    -                <div className="row">
    -                    {/* Employees Chart Starts*/}
    -                    <div className="col text-center">
    -                        <h2 className="mb-3">Employees Chart</h2>
    -
    -                        <div style={{ height: '13.90rem' }} className="mx-auto">
    -                            <PieChart pieData={employeesData} />
    -                        </div>
    -                    </div>
    -                    {/* Employees Chart Ends*/}
    -                </div>
    -            </div>
    -            {/* Right Column Ends */}
    -        </div>
    -    )
    -}
    -
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:26:24 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/EmployeesData.js.html b/out/EmployeesData.js.html deleted file mode 100644 index 85b3e00..0000000 --- a/out/EmployeesData.js.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - - JSDoc: Source: EmployeesData.js - - - - - - - - - - -
    - -

    Source: EmployeesData.js

    - - - - - - -
    -
    -
    import { DummyDataShifts } from "./DummyDataShifts"
    -import { DummyDataWorkers } from "./DummyDataWorkers"
    -import moment from "moment";
    -
    -// Today
    -const now = moment().format("YYYY-MM-DD")
    -
    -// Today, four weeks in the past
    -const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD")
    -
    -/**
    - * @function
    - * @description Takes in list a of shifts and generates data of inactive/active employees for Employees.js.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires moment
    - * @requires DummyDataShifts
    - * @requires DummyDataWorkers
    - * @returns Array of objects
    - */
    -const EmployeesDataGenerator = () => {
    -
    -    // First array
    -    let clockInsList = []
    -
    -    // Gathering the clock-ins of all the shifts
    -    DummyDataShifts.forEach((shift) => {
    -        shift.clockin.forEach((clockIn) => {
    -            if (shift.clockin.length > 0) {
    -                clockInsList.push(clockIn);
    -            }
    -        })
    -    })
    -
    -    // Array for all clock-ins
    -    let recentClockIns = []
    -
    -    // Filtering out clock-ins that happened longer than 4 weeks ago
    -    clockInsList.forEach((clockIn) => {
    -        let clockInStart = moment(clockIn.started_at).format("YYYY-MM-DD");
    -
    -        if (clockInStart > fourWeeksBack && clockInStart < now) {
    -            recentClockIns.push(clockIn)
    -        }
    -    })
    -
    -    // Array for worker ids
    -    let workerIDs = []
    -
    -    // Gethering worker ids from recent clock-ins
    -    recentClockIns.forEach((clockIn) => {
    -        workerIDs.push(clockIn.employee)
    -    })
    -
    -    // Filtering out repeated worker ids
    -    let filteredWorkerIDs = [...new Set(workerIDs)];
    -
    -    // Calculating total, active, and inactive workers
    -    let totalWorkers = DummyDataWorkers.length
    -    let totalActiveWorkers = filteredWorkerIDs.length
    -    let totalInactiveWorkers = totalWorkers - totalActiveWorkers
    -
    -    // Setting up objects for the semi-final array
    -    let activeWorkers = {
    -        id: 1,
    -        description: "Active Employees",
    -        qty: totalActiveWorkers
    -    }
    -    let inactiveWorkers = {
    -        id: 2,
    -        description: "Inactive Employees",
    -        qty: totalInactiveWorkers
    -    }
    -
    -    // Creating the semi-final array
    -    let semiFinalList = []
    -
    -    // Adding objects to the semi-final array
    -    semiFinalList.push(activeWorkers)
    -    semiFinalList.push(inactiveWorkers)
    -
    -    // Generating final array with percentages as new properties
    -    let finalList = semiFinalList.map(({ id, description, qty }) => ({
    -        id,
    -        description,
    -        qty,
    -        pct: ((qty * 100) / totalWorkers).toFixed(0)
    -    }));
    -
    -    // Generating the object of total workers
    -    let totalEmployees = {
    -        id: 3,
    -        description: "Total Employees",
    -        qty: totalWorkers,
    -        pct: "100"
    -    }
    -
    -    // Adding the object of total workers to the final array
    -    finalList.push(totalEmployees)
    -
    -    // Returning the final array
    -    return finalList
    -}
    -
    -// Exporting the final array
    -export const EmployeesData = EmployeesDataGenerator()
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:26:21 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/GeneralStats.js.html b/out/GeneralStats.js.html deleted file mode 100644 index 7879c53..0000000 --- a/out/GeneralStats.js.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - - JSDoc: Source: GeneralStats.js - - - - - - - - - - -
    - -

    Source: GeneralStats.js

    - - - - - - -
    -
    -
    import React from "react";
    -import { Employees } from "./Employees/Employees";
    -import { Hours } from "./Hours/Hours";
    -import { Shifts } from "./Shifts/Shifts";
    -
    -/**
    - * @function
    - * @description Creates a page with 3 tabs that show metrics about Shifts, Punctuality, and Hours.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires Hours
    - * @requires Shifts
    - * @requires Employees
    - */
    -export const GeneralStats = () => {
    -
    -    // Return ----------------------------------------------------------------------------------------------------
    -
    -    return (
    -        <>
    -          <div className="row d-flex flex-column mx-2">
    -                    {/* Tabs Controller Starts */}
    -                    <nav>
    -                        <div className="nav nav-tabs nav-fill" id="nav-tab" role="tablist">
    -                            <a className="nav-item nav-link active" id="nav-shifts-tab" data-toggle="tab" href="#nav-shifts" role="tab" aria-controls="nav-shifts" aria-selected="true"><h2>Shifts</h2></a>
    -                            <a className="nav-item nav-link" id="nav-hours-tab" data-toggle="tab" href="#nav-hours" role="tab" aria-controls="nav-hours" aria-selected="false"><h2>Hours</h2></a>
    -                            <a className="nav-item nav-link" id="nav-employees-tab" data-toggle="tab" href="#nav-employees" role="tab" aria-controls="nav-employees" aria-selected="false"><h2>Employees</h2></a>
    -                        </div>
    -                    </nav>
    -                    {/* Tabs Controller Ends */}
    -
    -                    {/* Tabs Content Starts */}
    -                    <div
    -                        className="tab-content mt-5"
    -                        id="nav-tabContent"
    -                    >
    -                        {/* Shifts Tab Starts */}
    -                        <div className="tab-pane fade show active" id="nav-shifts" role="tabpanel" aria-labelledby="nav-shifts-tab">
    -                            <Shifts />
    -                        </div>
    -                        {/* Shifts Tab Ends */}
    -
    -                        {/* Hours Tab Starts */}
    -                        <div className="tab-pane fade" id="nav-hours" role="tabpanel" aria-labelledby="nav-hours-tab">
    -                            <Hours />
    -                        </div>
    -                        {/* Hours Tab Ends */}
    -
    -                        {/* Employees Tab Starts */}
    -                        <div className="tab-pane fade" id="nav-employees" role="tabpanel" aria-labelledby="nav-employees-tab">
    -                            <Employees />
    -                        </div>
    -                        {/* Employees Tab Ends */}
    -                    </div>
    -                    {/* Tabs Content Ends */}
    -                </div>
    -        </>
    -    )
    -}
    -
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:43:55 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/HoursData.js.html b/out/HoursData.js.html deleted file mode 100644 index f967ff7..0000000 --- a/out/HoursData.js.html +++ /dev/null @@ -1,163 +0,0 @@ - - - - - JSDoc: Source: HoursData.js - - - - - - - - - - -
    - -

    Source: HoursData.js

    - - - - - - -
    -
    -
    import moment from "moment"
    -import { DummyDataShifts } from "./DummyDataShifts"
    -//import { DummyDataWorkers } from "./DummyDataWorkers"
    -
    -/**
    - * @function
    - * @description Takes in list a of shifts and generates data of the hours worked for Hours.js.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires DummyDataShifts
    - * @returns Array of objects
    - */
    -const HoursDataGenerator = () => {
    -
    -  // First array
    -  let completeList = [];
    -
    -  // Gathering both clock-ins and clock-outs
    -  DummyDataShifts.forEach((shift) => {
    -    shift.clockin.forEach((clockIn) => {
    -      if (shift.clockin.length > 0) {
    -        completeList.push({
    -          starting_at: shift.starting_at,
    -          started_at: clockIn.started_at,
    -          ending_at: shift.ending_at,
    -          ended_at: clockIn.ended_at
    -        });
    -      }
    -    });
    -  });
    -
    -  // Adding all the scheduled hours from each shift
    -  let scheduledHours = completeList.reduce(
    -    (total, { starting_at, ending_at }) =>
    -      total +
    -      moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(),
    -    0
    -  );
    -
    -  // Formatting scheduled hours
    -  let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(0);
    -
    -  // Adding all the worked hours from each shift
    -  let workedHours = completeList.reduce(
    -    (total, { started_at, ended_at }) =>
    -      total +
    -      moment.duration(moment(ended_at).diff(moment(started_at))).asHours(),
    -    0
    -  );
    -
    -  // Formatting worked hours
    -  let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(0);
    -
    -  // THIS IS A PLACEHOLDER, we double the hours worked to mimic available hours
    -  let availableHours = (workedHoursFormatted * 2).toString()
    -
    -  // Creating object for worked hours
    -  let workedHoursObj = {
    -    description: "Hours Worked",
    -    qty: workedHoursFormatted
    -  };
    -
    -  // Calculating extra worked hours
    -  let extraHours = workedHoursFormatted - scheduledHoursFormatted;
    -
    -  // Creating object for extra worked hours
    -  let extraHoursObj = {
    -    description: "Extra Hours Worked",
    -    qty: extraHours
    -  };
    -
    -  // Creating object for long breaks
    -  let longBreaksObj = {
    -    description: "Long Breaks",
    -    qty: "10"
    -  };
    -
    -  // Generate semi-final list
    -  let semiFinalList = [];
    -
    -  // Adding object of worked hours to semi-final list
    -  semiFinalList.push(workedHoursObj);
    -  semiFinalList.push(extraHoursObj);
    -  semiFinalList.push(longBreaksObj);
    -
    -  // Generating final array with percentages as new properties
    -  let finalList = semiFinalList.map(({ description, qty }) => ({
    -    description,
    -    qty,
    -    pct: ((qty * 100) / availableHours).toFixed(0)
    -  }));
    -
    -  // Generating object of available hours
    -  let availableHoursObj = {
    -    description: "Available Hours",
    -    qty: availableHours,
    -    pct: "100"
    -  };
    -
    -  // Adding object of available hours to final list
    -  finalList.push(availableHoursObj);
    -
    -  // Adding IDs to each object in the array
    -  finalList.forEach((item, i) => {
    -    item.id = i + 1;
    -  });
    -
    -  // Returning the final array
    -  return finalList
    -}
    -
    -// Exporting the final array
    -export const HoursData = HoursDataGenerator()
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:32:39 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/Metrics.html b/out/Metrics.html deleted file mode 100644 index 9e96fbf..0000000 --- a/out/Metrics.html +++ /dev/null @@ -1,187 +0,0 @@ - - - - - JSDoc: Class: Metrics - - - - - - - - - - -
    - -

    Class: Metrics

    - - - - - - -
    - -
    - -

    Metrics()

    - - -
    - -
    -
    - - - - - - -

    new Metrics()

    - - - - - - -
    - Creates the view for Metrics page, which renders tabs with different modules being called inside each one. -
    - - - - - - - - - - - - - -
    - - - - -
    Since:
    -
    • 09.28.22 by Paola Sanchez
    - - - - - - - - - - - - - - - -
    Author:
    -
    -
      -
    • Paola Sanchez
    • -
    -
    - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - -
    Requires:
    -
      -
    • module:Punctuality
    • - -
    • module:Ratings
    • - -
    • module:GeneralStats
    • - -
    • module:Queue
    • -
    - - - - - - - - - - - - - - - - - - - -
    - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:00:24 GMT+0000 (Coordinated Universal Time) -
    - - - - - \ No newline at end of file diff --git a/out/PunctualityData.js.html b/out/PunctualityData.js.html deleted file mode 100644 index c7303dd..0000000 --- a/out/PunctualityData.js.html +++ /dev/null @@ -1,259 +0,0 @@ - - - - - JSDoc: Source: PunctualityData.js - - - - - - - - - - -
    - -

    Source: PunctualityData.js

    - - - - - - -
    -
    -
    import { DummyDataShifts } from "./DummyDataShifts"
    -import moment from "moment";
    -
    -// Clock-Ins Data ------------------------------------------------------------------
    -
    -/**
    - * @function
    - * @description Takes in list a of shifts and generates data of clock-in trends for Punctuality.js.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires moment
    - * @requires DummyDataShifts
    - * @returns Array of objects
    - */
    -const ClockInsDataGenerator = () => {
    -
    -    // Clock-ins array
    -    let clockIns = [];
    -
    -    // Sorting the shifts
    -    DummyDataShifts.forEach((shift) => {
    -        shift.clockin.forEach((clockIn) => {
    -            if (shift.clockin.length > 0) {
    -                // Gathering the clock-ins
    -                clockIns.push({
    -                    starting_at: shift.starting_at,
    -                    started_at: clockIn.started_at
    -                });
    -            }
    -        });
    -    });
    -
    -    // Setting up counters
    -    let earlyClockins = 0;
    -    let lateClockins = 0;
    -
    -    // Increasing clock-in counters
    -    clockIns.forEach((shift) => {
    -        let start1 = moment(shift.starting_at);
    -        let start2 = moment(shift.started_at);
    -
    -        let startDiff = moment.duration(start2.diff(start1)).asMinutes();
    -
    -        if (startDiff >= 15) {
    -            lateClockins++;
    -        } else if (startDiff <= -30) {
    -            earlyClockins++;
    -        }
    -    });
    -
    -    // Creating clock-in objects
    -    let earlyClockinsObj = {
    -        description: "Early Clock-Ins",
    -        qty: earlyClockins
    -    };
    -    let lateClockinsObj = {
    -        description: "Late Clock-Ins",
    -        qty: lateClockins
    -    };
    -
    -    // Setting up base array for all objects
    -    let cleanedClockIns = [];
    -
    -    // Pushing objects to base array
    -    cleanedClockIns.push(earlyClockinsObj);
    -    cleanedClockIns.push(lateClockinsObj);
    -
    -    // Setting up totals
    -    let totalClockIns = clockIns.length;
    -
    -    // Generating percentages as new properties
    -    let pctClockIns = cleanedClockIns.map(({ description, qty }) => ({
    -        description,
    -        qty,
    -        pct: ((qty * 100) / totalClockIns).toFixed(0)
    -    }));
    -
    -    // Setting up object for totals
    -    let totalClockInsObj = {
    -        description: "Total Clock-Ins",
    -        qty: totalClockIns,
    -        pct: "100"
    -    };
    -
    -    // Adding totals to the array with percentages
    -    pctClockIns.push(totalClockInsObj);
    -
    -    // Addind IDs to each object
    -    pctClockIns.forEach((item, i) => {
    -        item.id = i + 1;
    -    });
    -
    -    // Returning clock-ins array
    -    return pctClockIns
    -}
    -
    -// Exporting clock-ins array
    -export const ClockInsData = ClockInsDataGenerator()
    -
    -
    -// Clock-Outs Data -----------------------------------------------------------------
    -
    -/**
    - * @function
    - * @description Takes in list a of shifts and generates data of clock-out trends for Punctuality.js.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires moment
    - * @requires DummyDataShifts
    - * @returns Array of objects
    - */
    -const ClockOutsDataGenerator = () => {
    -
    -    // Clock-outs array
    -    let clockOuts = [];
    -
    -    // Sorting the shifts
    -    DummyDataShifts.forEach((shift) => {
    -        shift.clockin.forEach((clockIn) => {
    -            if (shift.clockin.length > 0) {
    -                // Gathering the clock-outs
    -                clockOuts.push({
    -                    ending_at: shift.ending_at,
    -                    ended_at: clockIn.ended_at,
    -                    automatically_closed: clockIn.automatically_closed
    -                });
    -            }
    -        });
    -    });
    -
    -    // Setting up counters
    -    let earlyClockouts = 0;
    -    let lateClockouts = 0;
    -    let forgotClockOut= 0;
    -
    -    // Increasing clock-out counters
    -    clockOuts.forEach((shift) => {
    -        let end1 = moment(shift.ending_at);
    -        let end2 = moment(shift.ended_at);
    -
    -        let endDiff = moment.duration(end2.diff(end1)).asMinutes();
    -
    -        if (endDiff >= 30) {
    -            lateClockouts++;
    -        } else if (endDiff <= -30) {
    -            earlyClockouts++;
    -        } 
    -    });
    -
    -    // Increasing forgotClockOut counter
    -    clockOuts.forEach((shift) => {
    -        if (shift.automatically_closed === true) {
    -            forgotClockOut++;
    -        }
    -    });
    -
    -    // Creating clock-out objects
    -    let earlyClockoutsObj = {
    -        description: "Early Clock-Outs",
    -        qty: earlyClockouts
    -    };
    -    let lateClockoutsObj = {
    -        description: "Late Clock-Outs",
    -        qty: lateClockouts
    -    };
    -    let forgotClockOutObj = {
    -        description: "Forgotten Clock-Outs",
    -        qty: forgotClockOut
    -    };
    -
    -    // Setting up base array for all objects
    -    let cleanedClockOuts = [];
    -
    -    // Pushing objects to base array
    -    cleanedClockOuts.push(earlyClockoutsObj);
    -    cleanedClockOuts.push(lateClockoutsObj);
    -    cleanedClockOuts.push(forgotClockOutObj);
    -
    -    // Setting up totals
    -    let totalClockOuts = clockOuts.length;
    -
    -    // Generating percentages as new properties 
    -    let pctClockOuts = cleanedClockOuts.map(({ description, qty }) => ({
    -        description,
    -        qty,
    -        pct: ((qty * 100) / totalClockOuts).toFixed(0)
    -    }));
    -
    -    // Setting up object for totals
    -    let totalClockOutsObj = {
    -        description: "Total Clock-Outs",
    -        qty: totalClockOuts,
    -        pct: "100"
    -    };
    -
    -    // Adding totals to the array with percentages
    -    pctClockOuts.push(totalClockOutsObj);
    -
    -    // Addind IDs to each object
    -    pctClockOuts.forEach((item, i) => {
    -        item.id = i + 1;
    -    });
    -
    -    // Returning clock-outs array
    -    return pctClockOuts
    -}
    -
    -// Exporting clock-outs array
    -export const ClockOutsData = ClockOutsDataGenerator()
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:33:32 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/Ratings.js.html b/out/Ratings.js.html deleted file mode 100644 index b1e5155..0000000 --- a/out/Ratings.js.html +++ /dev/null @@ -1,162 +0,0 @@ - - - - - JSDoc: Source: Ratings.js - - - - - - - - - - -
    - -

    Source: Ratings.js

    - - - - - - -
    -
    -
    import React from "react";
    -import { PieChart } from '../charts';
    -import { RatingsData } from "./RatingsData";
    -
    -// Colors
    -const purple = "#5c00b8";
    -const lightTeal = "#00ebeb";
    -const darkTeal = "#009e9e";
    -const green = "#06ff05";
    -const lightPink = "#eb00eb";
    -const darkPink = "#b200b2";
    -
    -/**
    - * @function
    - * @description Creates a view of the number of ratings per worker.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires PieChart
    - * @requires RatingsData
    - * @returns A table and a chart displaying the ratings data
    - */
    -export const Ratings = () => {
    -
    -    // Data for pie chart -------------------------------------------------------------------------------------
    -
    -    // Taking out the "Totals" from the chart view
    -    let pieData = RatingsData.filter((item) => { return item.rating !== "Total Employees" })
    -
    -    // Preparing data to be passed to the chart component
    -    const ratingsData = {
    -        labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }),
    -        datasets: [{
    -            label: "Employee Ratings",
    -            data: pieData.map((data) => data.qty),
    -            backgroundColor: [
    -                green, darkTeal, lightPink,
    -                purple, lightTeal, darkPink
    -            ],
    -        }]
    -    }
    -
    -    // Return ----------------------------------------------------------------------------------------------------
    -
    -    return (
    -        <div className="row d-flex d-inline-flex justify-content-between w-100">
    -            {/* Left Column Starts */}
    -            <div className="col">
    -                <div className="row d-flex flex-column justify-content-between">
    -                    {/* Ratings Table Starts */}
    -                    <div className="col text-center">
    -                        <h2 className="mb-4">Employee Ratings Table</h2>
    -
    -                        <table className="table table-bordered text-center">
    -                            <thead className="thead-dark">
    -                                {/* Table columns */}
    -                                <tr>
    -                                    <th scope="col"><h3 className="m-0">Star Rating</h3></th>
    -                                    <th scope="col"><h3 className="m-0">Quantity</h3></th>
    -                                    <th scope="col"><h3 className="m-0">Percentages</h3></th>
    -                                </tr>
    -                            </thead>
    -
    -                            <tbody>
    -                                {/* Mapping the data to diplay it as table rows */}
    -                                {RatingsData.map((item, i) => {
    -                                    return item.rating === null ? (
    -                                        <tr key={i}>
    -                                            <th scope="row"><h3 className="m-0">Unavailable Rating</h3></th>
    -                                            <td><h3 className="m-0">{item.qty}</h3></td>
    -                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    -                                        </tr>
    -                                    ) : item.rating === "Total Employees" ? (
    -                                        <tr key={i} style={{ background: "rgba(107, 107, 107, 0.35)" }}>
    -                                            <th scope="row"><h3 className="m-0">{item.rating}</h3></th>
    -                                            <td><h3 className="m-0">{item.qty}</h3></td>
    -                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    -                                        </tr>
    -                                    ) : (
    -                                        <tr key={i}>
    -                                            <th scope="row"><h3 className="m-0">{`${item.rating} Star Employees`}</h3></th>
    -                                            <td><h3 className="m-0">{item.qty}</h3></td>
    -                                            <td><h3 className="m-0">{`${item.pct}%`}</h3></td>
    -                                        </tr>
    -                                    )
    -                                })}
    -                            </tbody>
    -                        </table>
    -                    </div>
    -                    {/* Ratings Table Ends */}
    -                </div>
    -            </div>
    -            {/* Left Column Ends */}
    -
    -            {/* Right Column Starts */}
    -            <div className="col">
    -                <div className="row">
    -                    {/* Ratings Chart Starts */}
    -                    <div className="col text-center">
    -                        <h2 className="mb-3">Employee Ratings Chart</h2>
    -
    -                        <div style={{ height: '26.05rem' }}>
    -                            <PieChart pieData={ratingsData} />
    -                        </div>
    -                    </div>
    -                    {/* Ratings Chart Ends */}
    -                </div>
    -            </div>
    -            {/* Right Column Ends */}
    -        </div>
    -    )
    -}
    -
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:25:21 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/RatingsData.js.html b/out/RatingsData.js.html deleted file mode 100644 index 26a84fe..0000000 --- a/out/RatingsData.js.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - JSDoc: Source: RatingsData.js - - - - - - - - - - -
    - -

    Source: RatingsData.js

    - - - - - - -
    -
    -
    import { DummyDataWorkers } from "./DummyDataWorkers";
    -
    -/**
    - * @function
    - * @description Takes in list a of employees and generates data of rating trends for Ratings.js.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires DummyDataWorkers
    - * @returns Array of objects
    - */
    -const RatingDataGenerator = () => {
    -
    -  // First array
    -  let ratings = [];
    -
    -  // Gathering all the ratings from each worker
    -  DummyDataWorkers.forEach((worker) => {
    -    ratings.push(worker.rating);
    -  });
    -
    -  // Function to make an array of objects with all the ratings
    -  const findOccurrences = (arr = []) => {
    -    const results = [];
    -
    -    arr.forEach((item) => {
    -      const index = results.findIndex((obj) => {
    -        return obj["rating"] === item;
    -      });
    -      if (index === -1) {
    -        results.push({
    -          rating: item,
    -          qty: 1
    -        });
    -      } else {
    -        results[index]["qty"]++;
    -      }
    -    });
    -
    -    return results;
    -  };
    -
    -  // Generating array
    -  let array = findOccurrences(ratings);
    -
    -  // Calculating total of the quantities
    -  let total = array.reduce((s, { qty }) => s + qty, 0);
    -
    -  // Generating and adding percentages as new properties
    -  let ratingsArray = array.map(({ rating, qty }) => ({
    -    rating,
    -    qty,
    -    pct: ((qty * 100) / total).toFixed(0)
    -  }));
    -
    -  // Organizing objects by numerical order of the "rating" property
    -  let sortedArray = ratingsArray.sort((a, b) => (a.rating - b.rating))
    -
    -  // Moving the first object ("Unavailable Rating") to the last position of the array
    -  sortedArray.push(sortedArray.shift());
    -
    -  // Generating an object with the totals
    -  let totalsObj = { rating: "Total Employees", qty: total, pct: "100"}
    -
    -  // Adding object with the totals to array
    -  sortedArray.push(totalsObj)
    -
    -  // Adding id's to each object in the array
    -  sortedArray.forEach((item, i) => {
    -    item.id = i + 1;
    -  });
    -
    -  // Returning the array
    -  return sortedArray
    -};
    -
    -// Exporting the array
    -export const RatingsData = RatingDataGenerator()
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:25:53 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/ShiftsData.js.html b/out/ShiftsData.js.html deleted file mode 100644 index 9e5ed4a..0000000 --- a/out/ShiftsData.js.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - JSDoc: Source: ShiftsData.js - - - - - - - - - - -
    - -

    Source: ShiftsData.js

    - - - - - - -
    -
    -
    import { DummyData } from "./DummyData";
    -
    -/**
    - * @function
    - * @description Takes in list a of shifts and generates data of shift statuses for Shifts.js.
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires DummyData
    - * @returns Array of objects
    - */
    -const ShiftsDataGenerator = () => {
    -
    -    // First array
    -    let shiftsList = [];
    -
    -    // Gathering all the existing shifts
    -    DummyData.forEach((shift) => {
    -        shiftsList.push({
    -            status: shift.status,
    -            clockin: shift.clockin,
    -            employees: shift.employees
    -        });
    -    });
    -
    -    // Setting up counters
    -    let open = 0
    -    let filled = 0
    -    let completed = 0
    -    let rejected = 0
    -    let total = shiftsList.length
    -
    -    // Adding values to each counter based on certain shift conditions
    -    shiftsList.forEach((item) => {
    -        if (item.status === "EXPIRED" && item.clockin.length === 0 && item.employees.length === 0) {
    -            rejected++
    -        } else if (item.status === "FILLED") {
    -            filled++
    -        } else if (item.status === "COMPLETED") {
    -            completed++
    -        } else if (item.status === "OPEN") {
    -            open++
    -        }
    -    })
    -
    -    // Creating shift objects
    -    let openShifts = {
    -        description: "Open Shifts",
    -        qty: open
    -    }
    -    let filledShifts = {
    -        description: "Filled Shifts",
    -        qty: filled
    -    }
    -    let workedShifts = {
    -        description: "Worked Shifts",
    -        qty: completed
    -    }
    -    let rejectedShifts = {
    -        description: "Rejected Shifts",
    -        qty: rejected
    -    }
    -
    -    // Setting up base array for all shift objects
    -    let cleanedArray = []
    -
    -    // Pushing shift objects to base array
    -    cleanedArray.push(openShifts)
    -    cleanedArray.push(filledShifts)
    -    cleanedArray.push(workedShifts)
    -    cleanedArray.push(rejectedShifts)
    -
    -    // Generating final array with percentages as new properties
    -    let percentagesArray = cleanedArray.map(({ description, qty }) => ({
    -        description,
    -        qty,
    -        pct: ((qty * 100) / total).toFixed(0)
    -    }));
    -
    -    // Generating the object of total shifts
    -    let totalShifs = {
    -        description: "Total Shifts Posted",
    -        qty: total,
    -        pct: "100"
    -    }
    -
    -    // Adding the object of total shifts to the final array
    -    percentagesArray.push(totalShifs)
    -
    -    // Adding id's to each object in the final array
    -    percentagesArray.forEach((item, i) => {
    -        item.id = i + 1;
    -    });
    -
    -    // Returning the final array
    -    return percentagesArray
    -};
    -
    -// Exporting the final array
    -export const ShiftsData = ShiftsDataGenerator()
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:39:39 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/WorkerStats.js.html b/out/WorkerStats.js.html deleted file mode 100644 index d53f457..0000000 --- a/out/WorkerStats.js.html +++ /dev/null @@ -1,175 +0,0 @@ - - - - - JSDoc: Source: WorkerStats.js - - - - - - - - - - -
    - -

    Source: WorkerStats.js

    - - - - - - -
    -
    -
    import React from "react";
    -import moment from "moment";
    -import Avatar from "../../../components/avatar/Avatar";
    -import { Button, Theme } from "../../../components/index";
    -
    -// This is needed to render the button "Invite to Shift"
    -const allowLevels = window.location.search != "";
    -
    -/**
    - * @function
    - * @description Creates a table of all employees with their worked/scheduled hours for Queue.js
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires moment
    - * @requires Avatar
    - * @requires Button
    - * @requires Theme
    - */
    -export const WorkerStats = (props) => {
    -    
    -    // Setting up my variables ---------------------------------------------------------------------------------
    -
    -    const worker = props.worker;
    -    const shifts = props.shifts;
    -
    -    // Scheduled Hours -----------------------------------------------------------------------------------------
    -
    -    // Filtering scheduled Shifts
    -    const workerShiftsScheduled = [];
    -
    -    shifts.forEach((shift) => {
    -        shift.employees.forEach((employee) => {
    -            if (employee === worker.id) {
    -                workerShiftsScheduled.push(shift);
    -            }
    -        });
    -    });
    -
    -    // Calculating total scheduled hours
    -    let scheduledHours = workerShiftsScheduled.reduce(
    -        (total, { starting_at, ending_at }) =>
    -            total +
    -            moment.duration(moment(ending_at).diff(moment(starting_at))).asHours(),
    -        0
    -    );
    -
    -    let scheduledHoursFormatted = (Math.round(scheduledHours * 4) / 4).toFixed(2);
    -
    -    // Worked Hours ------------------------------------------------------------------------------------------------
    -
    -    // Filtering worked Shifts
    -    const workerShiftsClockedIn = [];
    -
    -    workerShiftsScheduled.forEach((shift) => {
    -        shift.clockin.forEach((clockIn) => {
    -            if (shift.clockin.length > 0) {
    -                workerShiftsClockedIn.push(clockIn);
    -            }
    -        });
    -    });
    -
    -    // Calculating total worked hours
    -    let workedHours = workerShiftsClockedIn.reduce(
    -        (total, { started_at, ended_at }) =>
    -            total +
    -            moment.duration(moment(ended_at).diff(moment(started_at))).asHours(),
    -        0
    -    );
    -
    -    let workedHoursFormatted = (Math.round(workedHours * 4) / 4).toFixed(2);
    -
    -    // Return ------------------------------------------------------------------------------------------------------
    -
    -    return (
    -        <>
    -            <Theme.Consumer>
    -                {({ bar }) => (
    -                    <div className="row d-flex border d-inline-flex justify-content-between py-4 w-100">
    -                        {/* Employee Image/Name/Rating Starts */}
    -                        <div className="col p-0 d-flex justify-content-center">
    -                            <div className="my-auto mr-2">
    -                                <Avatar url={worker.user.profile.picture} />
    -                            </div>
    -                            <div className="ms-2 text-start my-auto d-flex flex-column">
    -                                <h5 className="m-0 p-0" align="left">{`${worker.user.first_name} ${worker.user.last_name}`}</h5>
    -                                <h5 className="m-0 p-0" align="left">{worker.rating == null ?  "No rating available" : worker.rating > 1 ? `Rating: ${worker.rating} stars` : `Rating: ${worker.rating} star`}</h5>
    -                            </div>
    -                        </div>
    -                        {/* Employee Image/Name/Rating Ends */}
    -
    -                        {/* Scheduled Hours Starts */}
    -                        <div className="col p-0 my-auto d-flex justify-content-center">
    -                            <h3 className="m-0 p-0">{`Scheduled Hours: ${scheduledHoursFormatted}`}</h3>
    -                        </div>
    -                        {/* Scheduled Hours Ends */}
    -
    -                        {/* Worked Hours Starts */}
    -                        <div className="col p-0 my-auto d-flex justify-content-center">
    -
    -                            <h3 className="m-0 p-0">{`Worked Hours: ${workedHoursFormatted}`}</h3>
    -                        </div>
    -                        {/* Worked Hours Ends */}
    -
    -                        {/* Invite Button Starts */}
    -                        <div className="col p-0 my-auto d-flex justify-content-center">
    -                            <Button
    -                                className="btn btn-dark bg-dark"
    -                                onClick={() =>
    -                                    bar.show({
    -                                        slug: "invite_talent_to_shift",
    -                                        data: worker,
    -                                        allowLevels,
    -                                    })
    -                                }
    -                            >
    -                                <h5 className="m-0">Invite to Shift</h5>
    -                            </Button>
    -                        </div>
    -                        {/* Invite Button Ends */}
    -                    </div>
    -                )}
    -            </Theme.Consumer>
    -        </>
    -    );
    -};
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:59:18 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/charts.js.html b/out/charts.js.html deleted file mode 100644 index 146895d..0000000 --- a/out/charts.js.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - JSDoc: Source: charts.js - - - - - - - - - - -
    - -

    Source: charts.js

    - - - - - - -
    -
    -
    import React from 'react';
    -import { Pie, Bar } from 'react-chartjs-2';
    -import { Chart as ChartJS } from 'chart.js/auto';
    -
    -/**
    - * @function
    - * @description Creates a pie chart with the data passed as an argument
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires Pie
    - * @param {object} pieData - Object with data like colors, labels, and values for the chart.
    - */
    -export const PieChart = ({ pieData }) => {
    -  return (
    -    <Pie
    -      data={pieData}
    -      options={{
    -        responsive: true,
    -        maintainAspectRatio: false,
    -        cutout: "0%",
    -        animation: {
    -          animateScale: true,
    -          animateRotate: true,
    -        }
    -      }}
    -    />
    -  )
    -}
    -
    -/**
    - * @function
    - * @description Creates a bar chart with the data passed as an argument
    - * @since 09.29.22 by Paola Sanchez
    - * @author Paola Sanchez
    - * @requires Bar
    - * @param {object} barData - Object with data like colors, labels, and values for the chart.
    - */
    -export const BarChart = ({ barData }) => {
    -  
    -  let delayed;
    -  return (
    -    <Bar
    -      data={barData}
    -      options={{
    -        responsive: true,
    -        maintainAspectRatio: false,
    -        animation: {
    -          onComplete: () => {
    -            delayed = true;
    -          },
    -          delay: (context) => {
    -            let delay = 0;
    -            if (context.type === 'data' && context.mode === 'default' && !delayed) {
    -              delay = context.dataIndex * 150 + context.datasetIndex * 100;
    -            }
    -            return delay;
    -          },
    -        }
    -      }}
    -    />
    -  )
    -}
    -
    -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:01:22 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/out/global.html b/out/global.html deleted file mode 100644 index 3559e63..0000000 --- a/out/global.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - JSDoc: Global - - - - - - - - - - -
    - -

    Global

    - - - - - - -
    - -
    - -

    - - -
    - -
    -
    - - - - - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - - -
    - - - - - - - - - - - - - - - - -

    Methods

    - - - - - - - -

    WorkerStats()

    - - - - - - -
    - Creates a table of all employees with their worked/scheduled hours for Queue.js -
    - - - - - - - - - - - - - -
    - - - - -
    Since:
    -
    • 09.29.22 by Paola Sanchez
    - - - - - - - - - - - - - - - -
    Author:
    -
    -
      -
    • Paola Sanchez
    • -
    -
    - - - - - - - - - -
    Source:
    -
    - - - - - - - -
    - - - -
    Requires:
    -
      -
    • module:moment
    • - -
    • module:Avatar
    • - -
    • module:Button
    • - -
    • module:Theme
    • -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    - -
    - - - - -
    - - - -
    - -
    - Documentation generated by JSDoc 3.6.11 on Thu Sep 29 2022 14:59:18 GMT+0000 (Coordinated Universal Time) -
    - - - - - \ No newline at end of file diff --git a/out/index.html b/out/index.html deleted file mode 100644 index b5d27e2..0000000 --- a/out/index.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - JSDoc: Home - - - - - - - - - -
    -

    Home

    - -

    Shifts

    -

    - Shifts.js -

    -

    - ShiftsData.js -

    - -

    Hours

    -

    - Hours.js -

    -

    - HoursData.js -

    - -
    - - - -
    - -
    - Documentation generated by - JSDoc 3.6.11 on Thu Sep 29 - 2022 14:59:18 GMT+0000 (Coordinated Universal Time) -
    - - - - - diff --git a/package.json b/package.json index 694133b..e2c2f1d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "deploy": "now --prod -b [API_HOST=https://jobcore.herokuapp.com]", "start:isratest": "API_HOST=https://8000-lavender-alligator-jdqsikyc39p.ws-us54.gitpod.io bash -c 'webpack-dev-server --config webpack.dev.js --open'", "test": "jest", - "test:coverage": "jest --coverage" + "test:coverage": "jest --coverage", + "doc": "jsdoc -c jsdoc.json" }, "author": "", "license": "ISC", diff --git a/src/js/views/metrics/charts.js b/src/js/views/metrics/charts.js index f61afdd..3414094 100644 --- a/src/js/views/metrics/charts.js +++ b/src/js/views/metrics/charts.js @@ -4,11 +4,11 @@ import { Chart as ChartJS } from 'chart.js/auto'; /** * @function - * @description Creates a pie chart with the data passed as an argument + * @description Creates a pie chart with the data passed as an argument. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires Pie - * @param {object} pieData - Object with data like colors, labels, and values for the chart. + * @param {object} pieData - Object with data like colors, labels, and values needed for the chart. */ export const PieChart = ({ pieData }) => { return ( @@ -33,7 +33,7 @@ export const PieChart = ({ pieData }) => { * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires Bar - * @param {object} barData - Object with data like colors, labels, and values for the chart. + * @param {object} barData - Object with data like colors, labels, and values needed for the chart. */ export const BarChart = ({ barData }) => { diff --git a/src/js/views/metrics/general-stats/GeneralStats.js b/src/js/views/metrics/general-stats/GeneralStats.js index 68b817a..83f2761 100644 --- a/src/js/views/metrics/general-stats/GeneralStats.js +++ b/src/js/views/metrics/general-stats/GeneralStats.js @@ -11,9 +11,14 @@ import { Shifts } from "./Shifts/Shifts"; * @requires Hours * @requires Shifts * @requires JobSeekers + * @param {object} props - Contains an array of all shifts, and an array of all the workers. */ -export const GeneralStats = () => { +export const GeneralStats = (props) => { + // Setting up main data sources + let workers = props.workers + let shifts = props.shifts + // Return ---------------------------------------------------------------------------------------------------- return ( @@ -36,19 +41,19 @@ export const GeneralStats = () => { > {/* Shifts Tab Starts */} {/* Shifts Tab Ends */} {/* Hours Tab Starts */} {/* Hours Tab Ends */} {/* Job Seekers Tab Starts */} {/* Job Seekers Tab Ends */} diff --git a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js b/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js deleted file mode 100644 index 1e2401f..0000000 --- a/src/js/views/metrics/general-stats/Hours/DummyDataShifts.js +++ /dev/null @@ -1,346 +0,0 @@ -export const DummyDataShifts = [ - { - id: 1, - employees: [7, 4], - clockin: [ - { - started_at: "2022-09-10T10:45:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:00:09Z", - employee: 7, - shift: 1 - }, - { - started_at: "2022-09-10T15:35:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 7, - shift: 1 - }, - { - started_at: "2022-09-10T10:20:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:00:09Z", - employee: 4, - shift: 1 - }, - { - started_at: "2022-09-10T15:30:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 4, - shift: 1 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 2, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T13:20:09Z", - employee: 3, - shift: 2 - }, - { - started_at: "2022-09-10T13:50:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z", - employee: 3, - shift: 2 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 3, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:15:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T12:10:09Z", - employee: 3, - shift: 3 - }, - { - started_at: "2022-09-10T12:20:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T17:00:09Z", - employee: 3, - shift: 3 - }, - { - started_at: "2022-09-10T17:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:17:09Z", - employee: 3, - shift: 3 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 4, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:30:09Z", - employee: 3, - shift: 4 - }, - { - started_at: "2022-09-10T15:42:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T18:00:09Z", - employee: 3, - shift: 4 - }, - { - started_at: "2022-09-10T18:30:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:31:09Z", - employee: 3, - shift: 4 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 5, - employees: [4], - clockin: [ - { - started_at: "2022-09-10T10:55:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:19:09Z", - employee: 4, - shift: 5 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 6, - employees: [4], - clockin: [ - { - started_at: "2022-09-10T10:40:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z", - employee: 4, - shift: 6 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 7, - employees: [7], - clockin: [ - { - started_at: "2022-09-10T11:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T14:10:09Z", - employee: 7, - shift: 7 - }, - { - started_at: "2022-09-10T14:28:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T19:20:09Z", - employee: 7, - shift: 7 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 8, - employees: [7], - clockin: [ - { - started_at: "2022-09-10T10:14:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T16:30:09Z", - employee: 7, - shift: 8 - }, - { - started_at: "2022-09-10T17:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T21:35:09Z", - employee: 7, - shift: 8 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 9, - employees: [7], - clockin: [ - { - started_at: "2022-09-10T10:08:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:00:09Z", - employee: 7, - shift: 9 - }, - { - started_at: "2022-09-10T15:36:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:15:09Z", - employee: 7, - shift: 9 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 10, - employees: [5], - clockin: [ - { - started_at: "2022-09-10T10:40:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T14:00:09Z", - employee: 5, - shift: 10 - }, - { - started_at: "2022-09-10T14:40:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:40:09Z", - employee: 5, - shift: 10 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 11, - employees: [5], - clockin: [ - { - started_at: "2022-09-10T08:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T13:00:09Z", - employee: 5, - shift: 11 - }, - { - started_at: "2022-09-10T13:45:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:45:09Z", - employee: 5, - shift: 11 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 12, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T12:00:09Z", - employee: 3, - shift: 12 - }, - { - started_at: "2022-09-10T12:34:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 3, - shift: 12 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 13, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 3, - shift: 13 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 14, - employees: [3, 5], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 3, - shift: 14 - }, - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 5, - shift: 14 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - } -]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Hours/Hours.js b/src/js/views/metrics/general-stats/Hours/Hours.js index 1bd2443..e5ad960 100644 --- a/src/js/views/metrics/general-stats/Hours/Hours.js +++ b/src/js/views/metrics/general-stats/Hours/Hours.js @@ -1,13 +1,7 @@ import React from "react"; import { PieChart } from "../../charts"; -import { HoursData } from "./HoursData"; +import { HoursDataGenerator } from "./HoursData"; -// Colors -const purple = "#5c00b8"; -const lightTeal = "#00ebeb"; -const darkTeal = "#009e9e"; -const lightPink = "#eb00eb"; -const darkPink = "#b200b2"; /** * @function @@ -17,10 +11,20 @@ const darkPink = "#b200b2"; * @requires PieChart * @requires HoursData */ -export const Hours = () => { +export const Hours = (props) => { + + // Setting up main data source + let HoursData = HoursDataGenerator(props.shifts) // Data for pie chart ------------------------------------------------------------------------------------- + // Colors + const purple = "#5c00b8"; + const lightTeal = "#00ebeb"; + const darkTeal = "#009e9e"; + const lightPink = "#eb00eb"; + const darkPink = "#b200b2"; + // Preparing data to be passed to the chart component const hoursData = { labels: HoursData.map((data) => data.description), diff --git a/src/js/views/metrics/general-stats/Hours/HoursData.js b/src/js/views/metrics/general-stats/Hours/HoursData.js index d1e6b56..8389982 100644 --- a/src/js/views/metrics/general-stats/Hours/HoursData.js +++ b/src/js/views/metrics/general-stats/Hours/HoursData.js @@ -1,15 +1,17 @@ import moment from "moment"; -import { DummyDataShifts } from "./DummyDataShifts"; /** * @function - * @description Takes in list a of shifts and generates data of the hours worked for Hours.js. + * @description Takes in list a of shifts and generates data of all the hours trends for Hours.js. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez - * @requires DummyDataShifts - * @returns Array of objects + * @requires moment + * @param {object} props - Contains an array of all the shifts. */ -const HoursDataGenerator = () => { +export const HoursDataGenerator = (props) => { + + // Assigning props to variable + let shifts = props // 1st - Separation of shifts ---------------------------------------------------------- @@ -19,8 +21,9 @@ const HoursDataGenerator = () => { // Array for single clock-ins made by single workers let singleClockInSingleWorker = []; - // Gathering both clock-ins and clock-outs - DummyDataShifts.forEach((shift) => { + // Gathering both clock-ins and clock-outs in a formatted + // way to keep at the useful data handy at all times. + shifts.forEach((shift) => { if (shift.clockin.length > 1) { multipleClockIns.push({ starting_at: shift.starting_at, @@ -63,6 +66,7 @@ const HoursDataGenerator = () => { // Adding shifts to 'MCIMWOrganized' multipleClockInsMultipleWorkers.forEach((shift) => { + // Unifying shifts that have the same worker let newObj = shift.reduce((obj, value) => { let key = value.employee; if (obj[key] == null) obj[key] = []; @@ -98,8 +102,9 @@ const HoursDataGenerator = () => { // Array for polished version of 'singleClockinsMultipleWorkers' let SCIMWPolished = []; - // Adding shifts to 'SCIMWPolished' - DummyDataShifts.forEach((originalShift) => { + // Adding shifts to 'SCIMWPolished' in a formatted + // way to keep at the useful data handy at all times. + shifts.forEach((originalShift) => { singleClockinsMultipleWorkers.forEach((filteredShift) => { if (originalShift.id === filteredShift.shift) { SCIMWPolished.push({ @@ -352,7 +357,4 @@ const HoursDataGenerator = () => { // Returning the final array return finalList; -}; - -// Exporting the final array -export const HoursData = HoursDataGenerator(); \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js b/src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js deleted file mode 100644 index 5759121..0000000 --- a/src/js/views/metrics/general-stats/JobSeekers/DummyDataShifts.js +++ /dev/null @@ -1,145 +0,0 @@ -export const DummyDataShifts = [ - { - clockin: [ - { - started_at: "2022-09-19T10:20:09Z", - ended_at: "2022-09-19T19:10:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - employee: 5, - shift: 45, - } - ] - }, - { - clockin: [ - { - started_at: "2022-09-20T09:20:09Z", - ended_at: "2022-09-20T20:45:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - employee: 5, - shift: 49, - } - ] - }, - { - clockin: [ - { - started_at: "2022-09-21T10:00:09Z", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - employee: 4, - shift: 46, - } - ] - }, - { - clockin: [ - { - started_at: "2022-09-21T10:36:09Z", - ended_at: "2022-09-21T21:00:09Z", - automatically_closed: true, - created_at: "2022-09-10T19:00:08.811375Z", - updated_at: "2022-09-10T19:00:08.818207Z", - employee: 5, - shift: 47, - } - ] - }, - { - clockin: [ - { - started_at: "2022-09-10T09:00:09Z", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - employee: 4, - shift: 50, - } - ] - }, - { - clockin: [ - { - started_at: "2022-09-10T10:05:09Z", - ended_at: "2022-09-10T20:05:09Z", - automatically_closed: false, - created_at: "2022-09-10T19:28:08.811375Z", - updated_at: "2022-09-10T19:28:08.818207Z", - employee: 5, - shift: 50, - } - ] - }, - { - clockin: [ - { - started_at: "2022-01-10T10:05:09Z", - distance_out_miles: "0.000", - ended_at: "2022-01-10T20:05:09Z", - automatically_closed: false, - employee: 3, - shift: 50, - } - ] - }, - { - clockin: [ - { - started_at: "2022-04-10T10:05:09Z", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - employee: 3, - shift: 50, - } - ] - }, - { - clockin: [ - { - started_at: "2022-04-10T10:05:09Z", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - employee: 2, - shift: 50, - } - ] - }, - { - clockin: [ - { - started_at: "2022-04-10T10:05:09Z", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - employee: 2, - shift: 50, - } - ] - }, - { - clockin: [ - { - started_at: "2022-04-10T10:05:09Z", - ended_at: "2022-04-10T20:05:09Z", - automatically_closed: false, - employee: 1, - shift: 50, - } - ] - }, - { - clockin: [] - }, - { - clockin: [] - }, - { - clockin: [] - }, -]; \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js b/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js deleted file mode 100644 index 29cb4d7..0000000 --- a/src/js/views/metrics/general-stats/JobSeekers/DummyDataWorkers.js +++ /dev/null @@ -1,70 +0,0 @@ -export const DummyDataWorkers = [ - { id: 4 }, - { id: 5 }, - { id: 3 }, - { id: 2 }, - { id: 1 } -] - -export const DummyDataNewWorkers = [ - { - id: 1, - created_at: "2022-09-01T23:12:48.975618Z" - }, - { - id: 2, - created_at: "2022-09-02T23:12:48.975618Z" - }, - { - id: 3, - created_at: "2022-08-25T23:12:48.975618Z" - }, - { - id: 4, - created_at: "2022-08-29T23:12:48.975618Z" - }, - { - id: 5, - created_at: "2022-08-27T23:12:48.975618Z" - }, - { - id: 6, - created_at: "2022-08-26T23:12:48.975618Z" - }, - { - id: 7, - created_at: "2022-08-30T23:12:48.975618Z" - }, - { - id: 8, - created_at: "2022-09-26T23:12:48.975618Z" - }, - { - id: 9, - created_at: "2022-09-27T23:12:48.975618Z" - }, - { - id: 10, - created_at: "2022-09-28T23:12:48.975618Z" - }, - { - id: 11, - created_at: "2022-09-29T23:12:48.975618Z" - }, - { - id: 12, - created_at: "2022-09-30T23:12:48.975618Z" - }, - { - id: 13, - created_at: "2021-10-01T23:12:48.975618Z" - }, - { - id: 14, - created_at: "2021-10-02T23:12:48.975618Z" - }, - { - id: 15, - created_at: "2021-10-03T23:12:48.975618Z" - } -] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js index 5783a95..83fc105 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js @@ -1,167 +1,208 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { PieChart, BarChart } from '../../charts'; -import { JobSeekersData, NewJobSeekersData } from "./JobSeekersData"; - -// Colors -const purple = "#5c00b8"; -const lightPink = "#eb00eb"; -const darkTeal = "#009e9e"; -const green = "#06ff05"; +import { JobSeekersDataGenerator, NewJobSeekersDataGenerator } from "./JobSeekersData"; /** * @function - * @description Creates a page with 2 tables and 2 graphs of all the active/inactive and new job seekers. + * @description Creates a page with 2 graphs and 2 charts showing trends on active, inactive, and new job seekers. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires PieChart - * @requires JobSeekersData + * @requires BarChart + * @requires NewJobSeekersDataGenerator + * @requires JobSeekersDataGenerator + * @param {object} props - Contains an array of all the shifts, and also an array of all the workers. */ -export const JobSeekers = () => { - - // Data for pie chart ------------------------------------------------------------------------------------- - - // Taking out the "Totals" from the chart view - let pieData = JobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view - - // Preparing data to be passed to the chart component - const jobSeekersData = { - labels: pieData.map((data) => data.description), - datasets: [{ - label: "Job Seekers", - data: pieData.map((data) => data.qty), - backgroundColor: [ - purple, lightPink - ], - }] - } - - // Data for bar chart ------------------------------------------------------------------------------------- - - // Preparing data to be passed to the chart component - const newJobSeekersData = { - labels: NewJobSeekersData.map((data) => data.description), - datasets: [{ - label: "New Job Seekers", - data: NewJobSeekersData.map((data) => data.qty), - backgroundColor: [ - darkTeal, green - ], - }] +export const JobSeekers = (props) => { + + // Use state to hold list of workers and list of shifts + const [workersList, setWorkersList] = useState([]) + const [shifsList, setShiftsList] = useState([]) + + // Receiving the props that contain the lists we need + const handleProps = async () => { + + // Catching the props when they arrive + let propsObj = await props + + // Checking length of lists before save them + if (propsObj.workers.length > 0) { + // Saving list of workers + setWorkersList(propsObj.workers) + // Saving list of shifts + setShiftsList(propsObj.shifts) + } else { + // Handling error with a message + console.log("Waiting for props to arrive") + } } - // Return ---------------------------------------------------------------------------------------------------- - - return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Job Seekers Table Starts */} -
    -

    Job Seekers Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {JobSeekersData.map((item, i) => { - return item.description === "Total Job Seekers" ? ( - - - - - - ) : - ( - + // Triggering handleProps when props change/arrive + useEffect(() => { + handleProps() + }, [props]) + + if (workersList.length > 0) { + + // Setting up main data sources + let JobSeekersData = JobSeekersDataGenerator(shifsList, workersList) + let NewJobSeekersData = NewJobSeekersDataGenerator(workersList) + + // Data for pie chart ------------------------------------------------------------------------------------- + + // Taking out the "Totals" from the chart view + let pieData = JobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view + + // Preparing data to be passed to the chart component + const jobSeekersData = { + labels: pieData.map((data) => data.description), + datasets: [{ + label: "Job Seekers", + data: pieData.map((data) => data.qty), + backgroundColor: [ + purple, lightPink + ], + }] + } + + // Data for bar chart ------------------------------------------------------------------------------------- + + // Colors + const purple = "#5c00b8"; + const lightPink = "#eb00eb"; + const darkTeal = "#009e9e"; + const green = "#06ff05"; + + // Preparing data to be passed to the chart component + const newJobSeekersData = { + labels: NewJobSeekersData.map((data) => data.description), + datasets: [{ + label: "New Job Seekers", + data: NewJobSeekersData.map((data) => data.qty), + backgroundColor: [ + darkTeal, green + ], + }] + } + + // Return ---------------------------------------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Job Seekers Table Starts */} +
    +

    Job Seekers Table

    + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {JobSeekersData.map((item, i) => { + return item.description === "Total Job Seekers" ? ( + - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + ) : + ( + +

    {item.description}

    +

    {item.qty}

    +

    {`${item.pct}%`}

    + + ) + })} + + +
    + {/* Job Seekers Table Ends */}
    - {/* Job Seekers Table Ends */} -
    -
    - {/* New Job Seekers Table Starts */} -
    -

    New Job Seekers Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {NewJobSeekersData.map((item, i) => { - return item.description === "Total Job Seekers" ? ( - - - - - - ) : - ( - +
    + {/* New Job Seekers Table Starts */} +
    +

    New Job Seekers Table

    + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {NewJobSeekersData.map((item, i) => { + return item.description === "Total Job Seekers" ? ( + - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + ) : + ( + +

    {item.description}

    +

    {item.qty}

    +

    {`${item.pct}%`}

    + + ) + })} + + +
    + {/* New Job Seekers Table Ends */}
    - {/* New Job Seekers Table Ends */}
    - - {/* Left Column Ends */} - - {/* Right Column Starts */} -
    -
    - {/* Job Seekers Chart Starts*/} -
    -

    Job Seekers Chart

    - -
    - + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Job Seekers Chart Starts*/} +
    +

    Job Seekers Chart

    + +
    + +
    + {/* Job Seekers Chart Ends*/}
    - {/* Job Seekers Chart Ends*/} -
    -
    - {/* New Job Seekers Chart Starts*/} -
    -

    New Job Seekers Chart

    +
    + {/* New Job Seekers Chart Starts*/} +
    +

    New Job Seekers Chart

    -
    - +
    + +
    + {/* New Job Seekers Chart Ends*/}
    - {/* New Job Seekers Chart Ends*/}
    + {/* Right Column Ends */}
    - {/* Right Column Ends */} -
    - ) + ) + } else { + return ( +

    Loading

    + ) + } } \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js index 53ebf48..14039b9 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekersData.js @@ -1,5 +1,3 @@ -import { DummyDataShifts } from "./DummyDataShifts" -import { DummyDataWorkers, DummyDataNewWorkers } from "./DummyDataWorkers" import moment from "moment"; // Today @@ -8,7 +6,6 @@ const now = moment().format("YYYY-MM-DD") // Today, four weeks in the past const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD") - // Job Seekers Data ------------------------------------------------------------------ /** @@ -17,25 +14,29 @@ const fourWeeksBack = moment().subtract(4, 'weeks').format("YYYY-MM-DD") * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires moment - * @requires DummyDataShifts - * @requires DummyDataWorkers - * @returns Array of objects + * @param {object} props - Contains an array of all the shifts, and also an array of all the workers. */ -const JobSeekersDataGenerator = () => { +export const JobSeekersDataGenerator = (shiftsProp, workersProp) => { + + // Assigning props to variables + let shifts = shiftsProp + let workers = workersProp // Array for all clock-ins let clockInsList = [] // Gathering the clock-ins of all the shifts - DummyDataShifts.forEach((shift) => { + shifts.forEach((shift) => { shift.clockin.forEach((clockIn) => { + // Keeping all clockins array with at + // least one object inside if (shift.clockin.length > 0) { clockInsList.push(clockIn); } }) }) - // Array for all clock-ins + // Array for all recent clock-ins let recentClockIns = [] // Filtering out clock-ins that happened longer than 4 weeks ago @@ -50,7 +51,7 @@ const JobSeekersDataGenerator = () => { // Array for worker ids let workerIDs = [] - // Gethering worker ids from recent clock-ins + // Gethering all worker ids from recent clock-ins recentClockIns.forEach((clockIn) => { workerIDs.push(clockIn.employee) }) @@ -59,7 +60,7 @@ const JobSeekersDataGenerator = () => { let filteredWorkerIDs = [...new Set(workerIDs)]; // Calculating total, active, and inactive workers - let totalWorkers = DummyDataWorkers.length + let totalWorkers = workers.length let totalActiveWorkers = filteredWorkerIDs.length let totalInactiveWorkers = totalWorkers - totalActiveWorkers @@ -105,10 +106,7 @@ const JobSeekersDataGenerator = () => { return finalList } -// Exporting the final array -export const JobSeekersData = JobSeekersDataGenerator() - -// Job Seekers Data ------------------------------------------------------------------ +// New Job Seekers Data ------------------------------------------------------------------ /** * @function @@ -116,16 +114,19 @@ export const JobSeekersData = JobSeekersDataGenerator() * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires moment - * @requires DummyDataNewWorkers - * @returns Array of objects + * @param {object} props - Contains an array of all the shifts, and also an array of all the workers. */ -const NewJobSeekersDataGenerator = () => { +export const NewJobSeekersDataGenerator = (props) => { + + // Assigning props to variable + // Here we only need the array of workers + let workers = props // Array for new workers let newWorkersList = [] - // Adding workers to 'newWorkersList' - DummyDataNewWorkers.forEach((worker) => { + // Adding workers to 'newWorkersList' based on their creation date + workers.forEach((worker) => { let creation_date = moment(worker.created_at).format("YYYY-MM-DD"); if (creation_date > fourWeeksBack && creation_date < now) { @@ -133,7 +134,8 @@ const NewJobSeekersDataGenerator = () => { } }) - let totalWorkers = DummyDataNewWorkers.length + // Setting up some variables for the objects + let totalWorkers = workers.length let totalNewWorkers = newWorkersList.length // Setting up objects for the semi-final array @@ -170,7 +172,4 @@ const NewJobSeekersDataGenerator = () => { // Returning the final array return finalList -} - -// Exporting the final array -export const NewJobSeekersData = NewJobSeekersDataGenerator() \ No newline at end of file +} \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Shifts/DummyData.js b/src/js/views/metrics/general-stats/Shifts/DummyData.js deleted file mode 100644 index 1dd258f..0000000 --- a/src/js/views/metrics/general-stats/Shifts/DummyData.js +++ /dev/null @@ -1,282 +0,0 @@ -export const DummyData = [ - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "COMPLETED", - clockin: [{ id: 17 }], - employees: [3] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "FILLED", - clockin: [], - employees: [4] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "EXPIRED", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, - { - status: "OPEN", - clockin: [], - employees: [] - }, -] \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/Shifts/Shifts.js b/src/js/views/metrics/general-stats/Shifts/Shifts.js index 3d0142a..0c70b25 100644 --- a/src/js/views/metrics/general-stats/Shifts/Shifts.js +++ b/src/js/views/metrics/general-stats/Shifts/Shifts.js @@ -1,28 +1,32 @@ import React from "react"; import { BarChart } from "../../charts"; -import { ShiftsData } from "./ShiftsData"; - -// Colors -const purple = "#5c00b8"; -const lightTeal = "#00ebeb"; -const darkTeal = "#009e9e"; -const lightPink = "#eb00eb"; -const darkPink = "#b200b2"; +import { ShiftsDataGenerator } from "./ShiftsData"; /** * @function * @description Creates a page with a table and a graph of all the shift statuses. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez - * @requires ShiftsData + * @requires ShiftsDataGenerator * @requires BarChart + * @param {object} props - Contains an array of all the shifts. */ -export const Shifts = () => { +export const Shifts = (props) => { + + // Setting up main data source + let ShiftsData = ShiftsDataGenerator(props.shifts) // Data for bar chart ------------------------------------------------------------------------------------- - // Taking out the "Totals" from the chart vie - let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted "}) // Taking out the "Totals" from the chart view + // Colors + const purple = "#5c00b8"; + const lightTeal = "#00ebeb"; + const darkTeal = "#009e9e"; + const lightPink = "#eb00eb"; + const darkPink = "#b200b2"; + + // Taking out the "Totals" from the chart view + let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted " }) // Taking out the "Totals" from the chart view // Preparing data to be passed to the chart component const shiftsData = { diff --git a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js index 6a1e5a7..a5e6c5d 100644 --- a/src/js/views/metrics/general-stats/Shifts/ShiftsData.js +++ b/src/js/views/metrics/general-stats/Shifts/ShiftsData.js @@ -1,20 +1,20 @@ -import { DummyData } from "./DummyData"; - /** * @function * @description Takes in list a of shifts and generates data of shift statuses for Shifts.js. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez - * @requires DummyData - * @returns Array of objects + * @param {object} props - Contains a list of all shifts. */ -const ShiftsDataGenerator = () => { +export const ShiftsDataGenerator = (props) => { + + // Assigning props to variable + let shifts = props // First array let shiftsList = []; // Gathering all the existing shifts - DummyData.forEach((shift) => { + shifts.forEach((shift) => { shiftsList.push({ status: shift.status, clockin: shift.clockin, @@ -93,7 +93,4 @@ const ShiftsDataGenerator = () => { // Returning the final array return percentagesArray -}; - -// Exporting the final array -export const ShiftsData = ShiftsDataGenerator() \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/views/metrics/metrics.js b/src/js/views/metrics/metrics.js index 2f7a6a3..f17454d 100644 --- a/src/js/views/metrics/metrics.js +++ b/src/js/views/metrics/metrics.js @@ -10,14 +10,17 @@ import { GeneralStats } from "./general-stats/GeneralStats"; import { store, search } from "../../actions"; /** - * @description Creates the view for Metrics page, which renders tabs with different modules being called inside each one. + * @description Creates the view for Metrics page, which renders 4 tabs with different components being called inside each one. * @since 09.28.22 by Paola Sanchez * @author Paola Sanchez - * * @requires Punctuality * @requires Ratings * @requires GeneralStats * @requires Queue + * @requires store + * @requires search + * @requires Session + * @requries Flux */ export class Metrics extends Flux.DashView { @@ -101,10 +104,10 @@ export class Metrics extends Flux.DashView { render() { // List of workers with verified documents - const verifiedEmpList = this.state.employees.filter((employees) => employees.employment_verification_status === "APPROVED") + let verifiedEmpList = this.state.employees.filter((employees) => employees.employment_verification_status === "APPROVED") // List of all shifts - const listOfShifts = this.state.allShifts; + let listOfShifts = this.state.allShifts; // --------------------------------------------- // Filtering expired shifts @@ -145,25 +148,25 @@ export class Metrics extends Flux.DashView { > {/* General Stats Tab Starts */} {/* General Stats Tab Ends */} {/* Punctuality Tab Starts */} {/* Punctuality Tab Ends */} {/* Ratings Tab Starts */} {/* Ratings Tab Ends */} {/* Queue Tab Starts */} {/* Queue Tab Ends */}
    diff --git a/src/js/views/metrics/punctuality/DummyDataShifts.js b/src/js/views/metrics/punctuality/DummyDataShifts.js deleted file mode 100644 index 00689a2..0000000 --- a/src/js/views/metrics/punctuality/DummyDataShifts.js +++ /dev/null @@ -1,427 +0,0 @@ -export const DummyDataShifts = [ - { - id: 1, - employees: [7, 4], - clockin: [ - { - started_at: "2022-09-10T09:05:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:00:09Z", - employee: 7, - automatically_closed: false, - shift: 1 - }, - { - started_at: "2022-09-10T15:35:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 7, - automatically_closed: false, - shift: 1 - }, - { - started_at: "2022-09-10T10:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:00:09Z", - employee: 4, - automatically_closed: false, - shift: 1 - }, - { - started_at: "2022-09-10T15:30:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 4, - automatically_closed: false, - shift: 1 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 2, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T13:20:09Z", - employee: 3, - automatically_closed: false, - shift: 2 - }, - { - started_at: "2022-09-10T13:50:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:35:09Z", - employee: 3, - automatically_closed: false, - shift: 2 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 3, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:15:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T12:10:09Z", - employee: 3, - automatically_closed: false, - shift: 3 - }, - { - started_at: "2022-09-10T12:20:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T17:00:09Z", - employee: 3, - automatically_closed: false, - shift: 3 - }, - { - started_at: "2022-09-10T17:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:17:09Z", - employee: 3, - automatically_closed: false, - shift: 3 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 4, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:30:09Z", - employee: 3, - automatically_closed: false, - shift: 4 - }, - { - started_at: "2022-09-10T15:42:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T18:00:09Z", - employee: 3, - automatically_closed: false, - shift: 4 - }, - { - started_at: "2022-09-10T18:30:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:31:09Z", - employee: 3, - automatically_closed: false, - shift: 4 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 5, - employees: [4], - clockin: [ - { - started_at: "2022-09-10T10:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:19:09Z", - employee: 4, - automatically_closed: false, - shift: 5 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 6, - employees: [4], - clockin: [ - { - started_at: "2022-09-10T10:40:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T19:25:09Z", - employee: 4, - automatically_closed: false, - shift: 6 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 7, - employees: [7], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T14:10:09Z", - employee: 7, - automatically_closed: false, - shift: 7 - }, - { - started_at: "2022-09-10T14:28:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T19:20:09Z", - employee: 7, - automatically_closed: false, - shift: 7 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 8, - employees: [7], - clockin: [ - { - started_at: "2022-09-10T10:14:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T16:30:09Z", - employee: 7, - automatically_closed: false, - shift: 8 - }, - { - started_at: "2022-09-10T17:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T21:35:09Z", - employee: 7, - automatically_closed: false, - shift: 8 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 9, - employees: [7], - clockin: [ - { - started_at: "2022-09-10T10:08:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T15:00:09Z", - employee: 7, - automatically_closed: false, - shift: 9 - }, - { - started_at: "2022-09-10T15:36:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 7, - automatically_closed: true, - shift: 9 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 10, - employees: [5], - clockin: [ - { - started_at: "2022-09-10T10:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T14:00:09Z", - employee: 5, - automatically_closed: false, - shift: 10 - }, - { - started_at: "2022-09-10T14:40:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:40:09Z", - employee: 5, - automatically_closed: false, - shift: 10 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 11, - employees: [5], - clockin: [ - { - started_at: "2022-09-10T08:00:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T13:00:09Z", - employee: 5, - automatically_closed: false, - shift: 11 - }, - { - started_at: "2022-09-10T13:45:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:45:09Z", - employee: 5, - automatically_closed: false, - shift: 11 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 12, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T12:00:09Z", - employee: 3, - automatically_closed: false, - shift: 12 - }, - { - started_at: "2022-09-10T12:34:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T20:00:09Z", - employee: 3, - automatically_closed: true, - shift: 12 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 13, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 3, - automatically_closed: true, - shift: 13 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T22:00:00Z" - }, - { - id: 14, - employees: [3, 5], - clockin: [ - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 3, - automatically_closed: false, - shift: 14 - }, - { - started_at: "2022-09-10T09:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 5, - automatically_closed: false, - shift: 14 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T20:00:00Z" - }, - { - id: 15, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T10:10:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T12:00:09Z", - employee: 3, - automatically_closed: false, - shift: 15 - }, - { - started_at: "2022-09-10T12:30:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 3, - automatically_closed: true, - shift: 15 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T22:00:00Z" - }, - { - id: 16, - employees: [3], - clockin: [ - { - started_at: "2022-09-10T10:05:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T12:00:09Z", - employee: 3, - automatically_closed: false, - shift: 16 - }, - { - started_at: "2022-09-10T12:30:09Z", - distance_in_miles: "0.009", - distance_out_miles: "0.000", - ended_at: "2022-09-10T22:00:09Z", - employee: 3, - automatically_closed: true, - shift: 16 - } - ], - starting_at: "2022-09-10T10:00:00Z", - ending_at: "2022-09-10T22:00:00Z" - } -]; \ No newline at end of file diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index fdeca93..5251945 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -1,35 +1,41 @@ import React from "react"; import { PieChart } from '../charts'; -import { ClockInsData, ClockOutsData } from "./PunctualityData"; - -// Colors -const purple = "#5c00b8"; -const lightTeal = "#00ebeb"; -const darkTeal = "#009e9e"; -const green = "#06ff05"; -const lightPink = "#eb00eb"; -const darkPink = "#b200b2"; +import { ClockInsDataGenerator, ClockOutsDataGenerator } from "./PunctualityData"; /** * @function - * @description Creates a page with 2 tables and 2 graphs of all the clock-in and clock-out trends. + * @description Creates a page with 2 tables and 2 graphs of the clock-in and clock-out trends. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires PieChart - * @requires ClockInsData - * @requires ClockOutsData + * @requires ClockInsDataGenerator + * @requires ClockOutsDataGenerator + * @param {object} props - Contains an array of all the shifts. */ -export const Punctuality = () => { +export const Punctuality = (props) => { + + // Setting up main data sources + let ClockInsData = ClockInsDataGenerator(props.shifts) + let ClockOutsData = ClockOutsDataGenerator(props.shifts) + // Data for pie charts ------------------------------------------------------------------------------------- - // Clock-Ins + // Colors + const purple = "#5c00b8"; + const lightTeal = "#00ebeb"; + const darkTeal = "#009e9e"; + const green = "#06ff05"; + const lightPink = "#eb00eb"; + const darkPink = "#b200b2"; + + // Clock-Ins ------------------------------------------------------------------------------------------------ // Taking out the "Totals" from the chart view let dataCI = ClockInsData.filter((item) => { return item.description !== "Total Clock-Ins"; - }); // Taking out the "Totals" from the chart view + }); - // Preparing data to be passed to the chart component + // Preparing the data to be passed to the chart component const clockInsData = { labels: dataCI.map((data) => data.description), datasets: [ @@ -41,14 +47,14 @@ export const Punctuality = () => { ] }; - // Clock-Outs + // Clock-Outs ------------------------------------------------------------------------------------------------ // Taking out the "Totals" from the chart view let dataCO = ClockOutsData.filter((item) => { return item.description !== "Total Clock-Outs"; }); - // Preparing data to be passed to the chart component + // Preparing the data to be passed to the chart component const clockOutsData = { labels: dataCO.map((data) => data.description), datasets: [ diff --git a/src/js/views/metrics/punctuality/PunctualityData.js b/src/js/views/metrics/punctuality/PunctualityData.js index c5e89d5..6274a93 100644 --- a/src/js/views/metrics/punctuality/PunctualityData.js +++ b/src/js/views/metrics/punctuality/PunctualityData.js @@ -1,28 +1,32 @@ -import { DummyDataShifts } from "./DummyDataShifts" import moment from "moment"; // Clock-Ins Data ------------------------------------------------------------------ /** * @function - * @description Takes in list a of shifts and generates data of clock-in trends for Punctuality.js. + * @description Generates array of objects with clock-in trends for Punctuality.js. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires moment - * @requires DummyDataShifts - * @returns Array of objects + * @param {object} props - Contains an array of all the shifts. */ -const ClockInsDataGenerator = () => { - // Clock-ins array +export const ClockInsDataGenerator = (props) => { + + // Assigning props to variables + let shifts = props + + // Array for the clock-ins let clockIns = []; - let totalPunches = []; // Sorting the shifts - DummyDataShifts.forEach((shift) => { + shifts.forEach((shift) => { shift.clockin.forEach((clockIn) => { - totalPunches.push(clockIn); + // Keeping all clockins array with at + // least one object inside if (shift.clockin.length > 0) { - // Gathering the clock-ins + // Formatting each object to keep + // both the scheduled clock-in and + // the actual "registered" clock-in. clockIns.push({ starting_at: shift.starting_at, started_at: clockIn.started_at @@ -36,7 +40,7 @@ const ClockInsDataGenerator = () => { let lateClockins = 0; let onTimeClockins = 0; - // Increasing clock-in counters + // Increasing counters based on clock-in times clockIns.forEach((shift) => { let start1 = moment(shift.starting_at); let start2 = moment(shift.started_at); @@ -103,29 +107,34 @@ const ClockInsDataGenerator = () => { return pctClockIns; }; -// Exporting clock-ins array -export const ClockInsData = ClockInsDataGenerator(); - // Clock-Outs Data ----------------------------------------------------------------- /** * @function - * @description Takes in list a of shifts and generates data of clock-out trends for Punctuality.js. + * @description Generates array of objects with clock-out trends for Punctuality.js. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires moment - * @requires DummyDataShifts - * @returns Array of objects + * @param {object} props - Contains an array of all the shifts. */ -const ClockOutsDataGenerator = () => { - // Clock-outs array +export const ClockOutsDataGenerator = (props) => { + + // Assigning props to variables + let shifts = props + + // Array for the clock-outs let clockOuts = []; // Sorting the shifts - DummyDataShifts.forEach((shift) => { + shifts.forEach((shift) => { shift.clockin.forEach((clockIn) => { + // Keeping all clockins array with at + // least one object inside if (shift.clockin.length > 0) { - // Gathering the clock-outs + // Formatting each object to keep + // both the scheduled clock-out, the + // actual "registered" clock-out, and + // whether it closed automatically or not. clockOuts.push({ ending_at: shift.ending_at, ended_at: clockIn.ended_at, @@ -141,7 +150,7 @@ const ClockOutsDataGenerator = () => { let onTimeClockouts = 0; let forgotClockOut = 0; - // Increasing clock-out counters + // Increasing counters based on clock-out times clockOuts.forEach((shift) => { let end1 = moment(shift.ending_at); let end2 = moment(shift.ended_at); @@ -157,8 +166,10 @@ const ClockOutsDataGenerator = () => { } }); - // Increasing forgotClockOut counter + // Increasing the "forgotClockOut" counter only clockOuts.forEach((shift) => { + // Note: When a shif get automatically closed, it means + // that the worker forgot to clock-out. if (shift.automatically_closed === true) { forgotClockOut++; } @@ -218,7 +229,4 @@ const ClockOutsDataGenerator = () => { // Returning clock-outs array return pctClockOuts; -}; - -// Exporting clock-outs array -export const ClockOutsData = ClockOutsDataGenerator(); \ No newline at end of file +}; \ No newline at end of file diff --git a/src/js/views/metrics/queue/DummyDataShifts.js b/src/js/views/metrics/queue/DummyDataShifts.js deleted file mode 100644 index e7adc92..0000000 --- a/src/js/views/metrics/queue/DummyDataShifts.js +++ /dev/null @@ -1,328 +0,0 @@ -export const DummyDataShifts = [ - { - id: 1, - employees: [5, 4], - clockin: [ - { - started_at: "2022-10-03T09:00:00Z", - ended_at: "2022-10-03T15:00:00Z", - employee: 5, - shift: 1 - }, - { - started_at: "2022-10-03T15:00:00Z", - ended_at: "2022-10-03T20:10:00Z", - employee: 5, - shift: 1 - }, - { - started_at: "2022-10-03T10:05:00Z", - ended_at: "2022-10-03T15:00:00Z", - employee: 4, - shift: 1 - }, - { - started_at: "2022-10-03T15:00:00Z", - ended_at: "2022-10-03T20:15:00Z", - employee: 4, - shift: 1 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 2, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T09:30:09Z", - ended_at: "2022-10-03T13:00:00Z", - employee: 5, - shift: 2 - }, - { - started_at: "2022-10-03T13:00:00Z", - ended_at: "2022-10-03T20:25:00Z", - employee: 5, - shift: 2 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 3, - employees: [4], - clockin: [ - { - started_at: "2022-10-03T09:00:00Z", - ended_at: "2022-10-03T12:07:00Z", - employee: 4, - shift: 3 - }, - { - started_at: "2022-10-03T12:20:00Z", - ended_at: "2022-10-03T17:00:00Z", - employee: 4, - shift: 3 - }, - { - started_at: "2022-10-03T17:00:00Z", - ended_at: "2022-10-03T20:20:00Z", - employee: 4, - shift: 3 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 4, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T09:10:09Z", - ended_at: "2022-10-03T15:30:09Z", - employee: 5, - shift: 4 - }, - { - started_at: "2022-10-03T15:42:09Z", - ended_at: "2022-10-03T18:00:09Z", - employee: 5, - shift: 4 - }, - { - started_at: "2022-10-03T18:30:09Z", - ended_at: "2022-10-03T20:31:09Z", - employee: 5, - shift: 4 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 5, - employees: [4], - clockin: [ - { - started_at: "2022-10-03T10:00:09Z", - ended_at: "2022-10-03T20:19:09Z", - employee: 4, - shift: 5 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 6, - employees: [4], - clockin: [ - { - started_at: "2022-10-03T10:40:09Z", - ended_at: "2022-10-03T19:25:09Z", - employee: 4, - shift: 6 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 7, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T09:10:09Z", - ended_at: "2022-10-03T14:10:09Z", - employee: 5, - shift: 7 - }, - { - started_at: "2022-10-03T14:28:09Z", - ended_at: "2022-10-03T19:20:09Z", - employee: 5, - shift: 7 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 8, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T10:14:09Z", - ended_at: "2022-10-03T16:30:09Z", - employee: 5, - shift: 8 - }, - { - started_at: "2022-10-03T17:00:09Z", - ended_at: "2022-10-03T21:35:09Z", - employee: 5, - shift: 8 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 9, - employees: [4], - clockin: [ - { - started_at: "2022-10-03T10:08:09Z", - ended_at: "2022-10-03T15:00:09Z", - employee: 4, - shift: 9 - }, - { - started_at: "2022-10-03T15:36:09Z", - ended_at: "2022-10-03T20:00:09Z", - employee: 4, - shift: 9 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 10, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T10:10:09Z", - ended_at: "2022-10-03T14:00:09Z", - employee: 5, - shift: 10 - }, - { - started_at: "2022-10-03T14:40:09Z", - ended_at: "2022-10-03T20:40:09Z", - employee: 5, - shift: 10 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 11, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T08:00:09Z", - ended_at: "2022-10-03T13:00:09Z", - employee: 5, - shift: 11 - }, - { - started_at: "2022-10-03T13:45:09Z", - ended_at: "2022-10-03T20:45:09Z", - employee: 5, - shift: 11 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 12, - employees: [4], - clockin: [ - { - started_at: "2022-10-03T09:10:09Z", - ended_at: "2022-10-03T12:00:09Z", - employee: 4, - shift: 12 - }, - { - started_at: "2022-10-03T12:34:09Z", - ended_at: "2022-10-03T20:00:09Z", - employee: 4, - shift: 12 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 13, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T09:10:09Z", - ended_at: "2022-10-03T22:00:09Z", - employee: 5, - shift: 13 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T22:00:00Z" - }, - { - id: 14, - employees: [4, 5], - clockin: [ - { - started_at: "2022-10-03T09:10:09Z", - ended_at: "2022-10-03T22:00:09Z", - employee: 4, - shift: 14 - }, - { - started_at: "2022-10-03T09:10:09Z", - ended_at: "2022-10-03T22:00:09Z", - employee: 5, - shift: 14 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T20:00:00Z" - }, - { - id: 15, - employees: [4], - clockin: [ - { - started_at: "2022-10-03T10:10:09Z", - ended_at: "2022-10-03T12:00:09Z", - employee: 4, - shift: 15 - }, - { - started_at: "2022-10-03T12:30:09Z", - ended_at: "2022-10-03T22:00:09Z", - employee: 4, - shift: 15 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T22:00:00Z" - }, - { - id: 16, - employees: [5], - clockin: [ - { - started_at: "2022-10-03T10:05:09Z", - ended_at: "2022-10-03T12:00:09Z", - employee: 5, - shift: 16 - }, - { - started_at: "2022-10-03T12:30:09Z", - ended_at: "2022-10-03T22:00:09Z", - employee: 5, - shift: 16 - } - ], - starting_at: "2022-10-03T10:00:00Z", - ending_at: "2022-10-03T22:00:00Z" - } -]; \ No newline at end of file diff --git a/src/js/views/metrics/queue/DummyDataWorkers.js b/src/js/views/metrics/queue/DummyDataWorkers.js deleted file mode 100644 index 398a2a9..0000000 --- a/src/js/views/metrics/queue/DummyDataWorkers.js +++ /dev/null @@ -1,687 +0,0 @@ -export const DummyDataWorkers = [ - { - id: 5, - user: { - first_name: "Bill", - last_name: "Clinton", - email: "a+bill@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: 3, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2018-09-13T19:45:00Z", - updated_at: "2022-05-31T19:17:05.756262Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - }, - { - id: 4, - user: { - first_name: "Frank", - last_name: "Sinatra", - email: "a+employee4@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [ - { - id: 2, - title: "Preferred Employees", - created_at: "2018-09-13T19:45:00Z", - updated_at: "2018-09-13T19:45:00Z", - auto_accept_employees_on_this_list: false, - employer: 1, - employees: [4, 3] - } - ], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: 1, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2018-09-13T19:45:00Z", - updated_at: "2022-05-31T19:16:58.179401Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - } - ]; - - /* - - , - - { - id: 7, - user: { - first_name: "Martin", - last_name: "Luther King", - email: "a+martin@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2018-09-13T19:45:00Z", - updated_at: "2022-05-31T19:16:51.944212Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - }, - { - id: 6, - user: { - first_name: "Hilary", - last_name: "Clinton", - email: "a+hilary@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2018-09-13T19:45:00Z", - updated_at: "2022-05-31T19:16:45.061163Z", - response_time: 0, - total_invites: 0, - employability_expired_at: null, - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - }, - { - id: 2, - user: { - first_name: "John", - last_name: "Lennon", - email: "a+employee2@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [ - { - id: 1, - title: "Preferred Employees", - created_at: "2018-09-13T19:45:00Z", - updated_at: "2018-09-13T19:45:00Z", - auto_accept_employees_on_this_list: true, - employer: 1, - employees: [3, 2] - } - ], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2018-09-13T19:45:00Z", - updated_at: "2022-05-31T19:16:38.278596Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 3], - badges: [1, 2] - }, - { - id: 8, - user: { - first_name: "Bob", - last_name: "Patiño", - email: "a+employer2@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2019-12-02T19:31:08.229524Z", - updated_at: "2022-05-31T19:16:31.891312Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 3, - user: { - first_name: "Paul", - last_name: "McCartney", - email: "a+employee3@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [ - { - id: 1, - title: "Preferred Employees", - created_at: "2018-09-13T19:45:00Z", - updated_at: "2018-09-13T19:45:00Z", - auto_accept_employees_on_this_list: true, - employer: 1, - employees: [3, 2] - }, - { - id: 2, - title: "Preferred Employees", - created_at: "2018-09-13T19:45:00Z", - updated_at: "2018-09-13T19:45:00Z", - auto_accept_employees_on_this_list: false, - employer: 1, - employees: [4, 3] - } - ], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2018-09-13T19:45:00Z", - updated_at: "2022-05-31T19:16:25.336247Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [3, 4] - }, - { - id: 1, - user: { - first_name: "Alejo", - last_name: "Sanchez", - email: "a+employer@jobcore.co", - profile: { - picture: "", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2019-12-02T14:03:05.532124Z", - updated_at: "2022-05-31T19:16:18.809900Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-03T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - }, - { - id: 9, - user: { - first_name: "Sea", - last_name: "Aegean", - email: "agaege@gmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2019-12-15T17:06:58.768466Z", - updated_at: "2022-05-31T19:16:11.267685Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 10, - user: { - first_name: "Aeg", - last_name: "Aegean", - email: "aegaeg@gmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2019-12-15T17:09:40.996187Z", - updated_at: "2022-05-31T19:16:04.729902Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 11, - user: { - first_name: "Angel", - last_name: "Lacret", - email: "alacret@gmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2019-12-23T02:15:55.733701Z", - updated_at: "2022-05-31T19:15:59.168859Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-03T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 12, - user: { - first_name: "Esteban", - last_name: "Contreras", - email: "estebancont11@gmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2019-12-23T14:27:39.797020Z", - updated_at: "2022-05-31T19:15:52.039462Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 13, - user: { - first_name: "Cesar", - last_name: "Morales", - email: "david_avid1@hotmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", - bio: "Software developer react", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: true, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 48, - job_count: 0, - created_at: "2019-12-26T15:30:22.006415Z", - updated_at: "2022-05-31T19:15:44.543932Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-04T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 14, - user: { - first_name: "Moises", - last_name: "Marquina", - email: "marquinaabreu@gmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2020-01-10T20:48:46.219434Z", - updated_at: "2022-05-31T19:15:37.198090Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-05T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - }, - { - id: 16, - user: { - first_name: "Ronald", - last_name: "Macdonald", - email: "ronaldmac@jobcore.co", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile3.png", - bio: "", - phone_number: "7863299422" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2022-04-14T14:57:59.993342Z", - updated_at: "2022-05-31T19:15:22.175867Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-03T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - }, - { - id: 17, - user: { - first_name: "Bart", - last_name: "Simpson", - email: "bartsimpson@jobcore.co", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile2.png", - bio: "", - phone_number: "7863299422" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2022-04-14T16:06:13.564446Z", - updated_at: "2022-05-31T19:08:50.298810Z", - response_time: 0, - total_invites: 0, - employability_expired_at: "2100-06-02T00:00:00Z", - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [], - badges: [] - }, - { - id: 15, - user: { - first_name: "Luis", - last_name: "Llave", - email: "luisllave12357@gmail.com", - profile: { - picture: - "https://res.cloudinary.com/hq02xjols/image/upload/v1560365062/static/default_profile1.png", - bio: "", - phone_number: "" - } - }, - favoritelist_set: [], - minimum_hourly_rate: "8.0", - stop_receiving_invites: false, - rating: null, - total_ratings: 0, - total_pending_payments: 0, - maximum_job_distance_miles: 50, - job_count: 0, - created_at: "2020-03-10T20:42:35.885388Z", - updated_at: "2022-04-11T16:21:59.973971Z", - response_time: 0, - total_invites: 0, - employability_expired_at: null, - employment_verification_status: "APPROVED", - filing_status: "SINGLE", - allowances: 0, - w4_year: 0, - step2c_checked: false, - dependants_deduction: "0.00", - other_income: "0.00", - additional_deductions: "0.00", - extra_withholding: "0.00", - positions: [1, 2, 3, 4, 5, 6], - badges: [] - } - - */ - \ No newline at end of file diff --git a/src/js/views/metrics/queue/Queue.js b/src/js/views/metrics/queue/Queue.js index 0af86f7..6136324 100644 --- a/src/js/views/metrics/queue/Queue.js +++ b/src/js/views/metrics/queue/Queue.js @@ -5,8 +5,6 @@ import moment from "moment"; import { QueueData } from "./QueueData"; import { Button } from "../../../components/index"; -import { DummyDataShifts } from "./DummyDataShifts"; -import { DummyDataWorkers } from "./DummyDataWorkers"; /** * @function @@ -17,14 +15,16 @@ import { DummyDataWorkers } from "./DummyDataWorkers"; * @requires DatePicker * @requires QueueData * @requires Button - * @requires DummyDataShifts - * @requires DummyDataWorkers - * @param {object} props - Contains an array of all shifts, and an array of all workers + * @param {object} props - Contains an array of all shifts, and an array of all workers. */ export const Queue = (props) => { // Setting up my variables --------------------------------------------------------------------------------- + // Setting up main data source + let allShifts = props.shifts; + let workers = props.workers; + // Date selected through the DatePicker const [selectedDate, setSelectedDate] = useState(new Date()); @@ -38,17 +38,14 @@ export const Queue = (props) => { moment().endOf("isoWeek").format("YYYY-MM-DD") ); - //const allShifts = props.shifts; - const allShifts = DummyDataShifts; - - //const workers = props.workers; - const workers = DummyDataWorkers - // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- const filterShifts = () => { + + // Array for filtered shifts let filteredShifts = []; + // Keeping shifts that exist within the selected dates allShifts.forEach((shift) => { let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); @@ -63,23 +60,28 @@ export const Queue = (props) => { } }); + // Returning filtered shifts return filteredShifts; }; // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- useEffect(() => { + + // Setting up the new Monday let formattedStart = moment(selectedDate) .startOf("isoWeek") .format("YYYY-MM-DD"); setStart(formattedStart); + // Setting up the new Sunday let formattedEnd = moment(selectedDate) .endOf("isoWeek") .format("YYYY-MM-DD"); setEnd(formattedEnd); + }, [selectedDate]); // Return ---------------------------------------------------------------------------------------------------- @@ -101,6 +103,7 @@ export const Queue = (props) => {

    Select a day of the desired week:

    + {/* Calendar/DatePicker */}
    { - // Setting up my variables -------------------------------- + //Assigning props to variables ---------------------------- + + // This is a single worker brought from the mapping + // of all workers back in Queue.js const worker = props.worker; + + // These are all the shifts, with different workers const shifts = props.shifts; // Worker Shifts ------------------------------------------ + + // Array to hold shifts from the single worker const workerShifts = []; + // Keeping the shifts whose worker id matches + // the id of the single worker shifts.forEach((shift) => { shift.employees.forEach((employee) => { if (employee === worker.id) { @@ -34,8 +44,12 @@ export const QueueData = (props) => { }); // Worker Clock-ins --------------------------------------- + + // Array to hold clock-ins from the single worker let workerClockIns = []; + // Keeping the clock-ins whose worker id matches + // the id of the single worker workerShifts.forEach((shift) => { shift.clockin.forEach((clockIn) => { if (clockIn.employee === worker.id) { @@ -45,8 +59,12 @@ export const QueueData = (props) => { }); // Scheduled Hours --------------------------------------- + + // Array to hold scheduled hours from the single worker let scheduledHours = []; + // Calculating the scheduled hours of each shift and + // passing that data as an object to "scheduledHours" workerShifts.forEach((shift) => { let start = moment(shift.starting_at); let end = moment(shift.ending_at); @@ -59,15 +77,21 @@ export const QueueData = (props) => { }); }); - let totalScheduledHours = scheduledHours.reduce((acc, obj) => { - return acc + obj.scheduled_hours; + // Adding all the scheduled hours to form a total + let totalScheduledHours = scheduledHours.reduce((accumulator, shift) => { + return accumulator + shift.scheduled_hours; }, 0); + // Formatting the total to 2 decimal places let totalScheduledHoursF = totalScheduledHours.toFixed(2) // Worked Hours ---------------------------------------- + + // Array to hold worked hours from the single worker let workedHours = []; + // Calculating the worked hours of each shift and + // passing that data as an object to "workedHours" workerClockIns.forEach((shift) => { let start = moment(shift.started_at); let end = moment(shift.ended_at); @@ -80,10 +104,12 @@ export const QueueData = (props) => { }); }); - let totalWorkedHours = workedHours.reduce((acc, obj) => { - return acc + obj.worked_hours; + // Adding all the worked hours to form a total + let totalWorkedHours = workedHours.reduce((accumulator, shift) => { + return accumulator + shift.worked_hours; }, 0); + // Formatting the total to 2 decimal places let totalWorkedHoursF = totalWorkedHours.toFixed(2) // Return ------------------------------------------------------------------------------------------------------ diff --git a/src/js/views/metrics/ratings/DummyDataWorkers.js b/src/js/views/metrics/ratings/DummyDataWorkers.js deleted file mode 100644 index aecd2a6..0000000 --- a/src/js/views/metrics/ratings/DummyDataWorkers.js +++ /dev/null @@ -1,28 +0,0 @@ -export const DummyDataWorkers = [ - { rating: 3 }, - { rating: 1 }, - { rating: 1 }, - { rating: 1 }, - { rating: 3 }, - { rating: null }, - { rating: 4 }, - { rating: 5 }, - { rating: null }, - { rating: 5 }, - { rating: 1 }, - { rating: 2 }, - { rating: 2 }, - { rating: 2 }, - { rating: 1 }, - { rating: 3 }, - { rating: 3 }, - { rating: 3 }, - { rating: 3 }, - { rating: 4 }, - { rating: 4 }, - { rating: 4 }, - { rating: 4 }, - { rating: 4 }, - { rating: 4 }, - { rating: 4 } - ]; \ No newline at end of file diff --git a/src/js/views/metrics/ratings/Ratings.js b/src/js/views/metrics/ratings/Ratings.js index 3e81dd0..e73497e 100644 --- a/src/js/views/metrics/ratings/Ratings.js +++ b/src/js/views/metrics/ratings/Ratings.js @@ -1,111 +1,211 @@ -import React from "react"; -import { PieChart } from '../charts'; -import { RatingsData } from "./RatingsData"; - -// Colors -const purple = "#5c00b8"; -const lightTeal = "#00ebeb"; -const darkTeal = "#009e9e"; -const green = "#06ff05"; -const lightPink = "#eb00eb"; -const darkPink = "#b200b2"; +import React, { useEffect, useState } from "react" +import { PieChart } from "../charts" /** * @function - * @description Creates a view of the number of ratings per worker. + * @description Creates a pie chart and a table reflecting how many job seekers are in each category of star ratings (1 to 5 stars.) * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez * @requires PieChart - * @requires RatingsData - * @returns A table and a chart displaying the ratings data + * @param {object} props - Contains an array of all shifts, and an array of all workers. */ -export const Ratings = () => { - - // Data for pie chart ------------------------------------------------------------------------------------- - - // Taking out the "Totals" from the chart view - let pieData = RatingsData.filter((item) => { return item.rating !== "Total Employees" }) - - // Preparing data to be passed to the chart component - const ratingsData = { - labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }), - datasets: [{ - label: "Employee Ratings", - data: pieData.map((data) => data.qty), - backgroundColor: [ - green, darkTeal, lightPink, - purple, lightTeal, darkPink - ], - }] +export const Ratings = (props) => { + + // Use state to hold list of workers + const [workersList, setWorkersList] = useState([]) + + // Receiving the props that contain the list of workers + const handleProps = async () => { + + // Catching the props when they arrive + let workersObj = await props + + // Checking length of list before saving it + if (workersObj.workers.length > 0) { + // Saving list of workers + setWorkersList(workersObj.workers) + } else { + // Handling error with a message + console.log("Waiting for props to arrive") + } } - // Return ---------------------------------------------------------------------------------------------------- - - return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Ratings Table Starts */} -
    -

    Employee Ratings Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {RatingsData.map((item, i) => { - return item.rating === null ? ( - - - - - - ) : item.rating === "Total Employees" ? ( - - - - - - ) : ( - - - - - - ) - })} - -

    Star Rating

    Quantity

    Percentages

    Unavailable Rating

    {item.qty}

    {`${item.pct}%`}

    {item.rating}

    {item.qty}

    {`${item.pct}%`}

    {`${item.rating} Star Employees`}

    {item.qty}

    {`${item.pct}%`}

    + // Triggering handleProps when props change/arrive + useEffect(() => { + handleProps() + }, [props]) + + // Rendering based on length of workersList + if (workersList.length > 0) { + + // Preparing the list for the chart data --------------------------------------- + + // Array to hold list of ratings + let ratingsList = [] + + // Gathering ratings of each worker + workersList.forEach((eachWorker) => { + ratingsList.push(eachWorker.rating) + }) + + // Function to make an array of rating quantities + const findQuantities = (passedArray) => { + + // Array to hold rating results + const results = []; + + // Counting how many times each star rating appears + passedArray?.forEach((item) => { + + // Generating indexes + const index = results.findIndex((obj) => { + return obj["rating"] === item; + }); + + // Using the indexes to count rating instances + if (index === -1) { + results.push({ + rating: item, + qty: 1 + }); + + } else { + results[index]["qty"]++; + } + }); + + // Returning array + return results; + }; + + // Generating array of rating quantities + let ratingsQty = findQuantities(ratingsList); + + // Calculating total of all the quantities + let total = ratingsQty.reduce((s, { qty }) => s + qty, 0); + + // Generating and adding percentages as new properties + let ratingsPct = ratingsQty.map(({ rating, qty }) => ({ + rating, + qty, + pct: ((qty * 100) / total).toFixed(0) + })); + + // Organizing objects by numerical order of the "rating" properties + let ratingsFinal = ratingsPct.sort((a, b) => (a.rating - b.rating)) + + // Moving the first object ("Unavailable Rating") to the last position of the array + ratingsFinal.push(ratingsFinal.shift()); + + // Generating an object with the totals + let totalsObj = { rating: "Total Employees", qty: total, pct: "100" } + + // Adding object with totals to the array + ratingsFinal.push(totalsObj) + + // Adding id's to every object in the array + ratingsFinal.forEach((item, i) => { + item.id = i + 1; + }); + + // Preparing the chart data ---------------------------------------------------- + + // Colors + const purple = "#5c00b8"; + const lightTeal = "#00ebeb"; + const darkTeal = "#009e9e"; + const green = "#06ff05"; + const lightPink = "#eb00eb"; + const darkPink = "#b200b2"; + + // Taking out the "Totals" from the pie chart view + let pieData = ratingsFinal.filter((item) => { return item.rating !== "Total Employees" }) + + // Preparing data to be passed to the chart component + const ratingsData = { + labels: pieData.map((data) => { return data.rating === null ? "Unavailable Rating" : ` ${data.rating} Star Employees` }), + datasets: [{ + label: "Employee Ratings", + data: pieData.map((data) => data.qty), + backgroundColor: [ + green, darkTeal, lightPink, + purple, lightTeal, darkPink + ], + }] + } + + // Return ---------------------------------------------------------------------- + + return ( +
    + {/* Left Column Starts */} +
    +
    + {/* Ratings Table Starts */} +
    +

    Employee Ratings Table

    + + + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {ratingsFinal.map((item, i) => { + return item.rating === null ? ( + + + + + + ) : item.rating === "Total Employees" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Star Rating

    Quantity

    Percentages

    Unavailable Rating

    {item.qty}

    {`${item.pct}%`}

    {item.rating}

    {item.qty}

    {`${item.pct}%`}

    {`${item.rating} Star Employees`}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Ratings Table Ends */}
    - {/* Ratings Table Ends */}
    -
    - {/* Left Column Ends */} + {/* Left Column Ends */} - {/* Right Column Starts */} -
    -
    - {/* Ratings Chart Starts */} -
    -

    Employee Ratings Chart

    + {/* Right Column Starts */} +
    +
    + {/* Ratings Chart Starts */} +
    +

    Employee Ratings Chart

    -
    - +
    + +
    + {/* Ratings Chart Ends */}
    - {/* Ratings Chart Ends */}
    + {/* Right Column Ends */}
    - {/* Right Column Ends */} -
    - ) -} + ) + } else { + return ( +

    Loading

    + ) + } +} \ No newline at end of file diff --git a/src/js/views/metrics/ratings/RatingsData.js b/src/js/views/metrics/ratings/RatingsData.js deleted file mode 100644 index f4f0d41..0000000 --- a/src/js/views/metrics/ratings/RatingsData.js +++ /dev/null @@ -1,77 +0,0 @@ -import { DummyDataWorkers } from "./DummyDataWorkers"; - -/** - * @function - * @description Takes in list a of employees and generates data of rating trends for Ratings.js. - * @since 09.29.22 by Paola Sanchez - * @author Paola Sanchez - * @requires DummyDataWorkers - * @returns Array of objects - */ -const RatingDataGenerator = () => { - - // First array - let ratings = []; - - // Gathering all the ratings from each worker - DummyDataWorkers.forEach((worker) => { - ratings.push(worker.rating); - }); - - // Function to make an array of objects with all the ratings - const findOccurrences = (arr = []) => { - const results = []; - - arr.forEach((item) => { - const index = results.findIndex((obj) => { - return obj["rating"] === item; - }); - if (index === -1) { - results.push({ - rating: item, - qty: 1 - }); - } else { - results[index]["qty"]++; - } - }); - - return results; - }; - - // Generating array - let array = findOccurrences(ratings); - - // Calculating total of the quantities - let total = array.reduce((s, { qty }) => s + qty, 0); - - // Generating and adding percentages as new properties - let ratingsArray = array.map(({ rating, qty }) => ({ - rating, - qty, - pct: ((qty * 100) / total).toFixed(0) - })); - - // Organizing objects by numerical order of the "rating" property - let sortedArray = ratingsArray.sort((a, b) => (a.rating - b.rating)) - - // Moving the first object ("Unavailable Rating") to the last position of the array - sortedArray.push(sortedArray.shift()); - - // Generating an object with the totals - let totalsObj = { rating: "Total Employees", qty: total, pct: "100"} - - // Adding object with the totals to array - sortedArray.push(totalsObj) - - // Adding id's to each object in the array - sortedArray.forEach((item, i) => { - item.id = i + 1; - }); - - // Returning the array - return sortedArray -}; - -// Exporting the array -export const RatingsData = RatingDataGenerator() \ No newline at end of file From 2852768e1aa43383b1314b9e059944861bfdc9d1 Mon Sep 17 00:00:00 2001 From: paola9896 Date: Wed, 5 Oct 2022 18:39:53 +0000 Subject: [PATCH 08/10] fixed graph colors on JobSeekers --- docs/MakePayment.html | 4 ++-- docs/Metrics.html | 4 ++-- docs/actions.js.html | 4 ++-- docs/global.html | 4 ++-- docs/index.html | 8 ++++---- docs/utils_api_wrapper.js.html | 4 ++-- docs/views_applications.js.html | 4 ++-- docs/views_deductions.js.html | 4 ++-- docs/views_favorites.js.html | 4 ++-- docs/views_invites.js.html | 4 ++-- docs/views_locations.js.html | 4 ++-- docs/views_metrics_charts.js.html | 4 ++-- .../views_metrics_general-stats_GeneralStats.js.html | 4 ++-- docs/views_metrics_general-stats_Hours_Hours.js.html | 4 ++-- ...ews_metrics_general-stats_Hours_HoursData.js.html | 4 ++-- ...trics_general-stats_JobSeekers_JobSeekers.js.html | 4 ++-- ...s_general-stats_JobSeekers_JobSeekersData.js.html | 4 ++-- ...views_metrics_general-stats_Shifts_Shifts.js.html | 4 ++-- ...s_metrics_general-stats_Shifts_ShiftsData.js.html | 4 ++-- docs/views_metrics_metrics.js.html | 4 ++-- docs/views_metrics_punctuality_Punctuality.js.html | 4 ++-- ...views_metrics_punctuality_PunctualityData.js.html | 4 ++-- docs/views_metrics_queue_Queue.js.html | 4 ++-- docs/views_metrics_queue_QueueData.js.html | 4 ++-- docs/views_metrics_ratings_Ratings.js.html | 4 ++-- docs/views_payments.js.html | 4 ++-- docs/views_payroll.js.html | 4 ++-- docs/views_profile.js.html | 4 ++-- docs/views_ratings.js.html | 4 ++-- docs/views_shifts.js.html | 4 ++-- docs/views_subscriptions.js.html | 4 ++-- docs/views_talents.js.html | 4 ++-- jsdoc-custom-template/publish.js | 4 ++-- .../metrics/general-stats/JobSeekers/JobSeekers.js | 12 ++++++------ 34 files changed, 74 insertions(+), 74 deletions(-) diff --git a/docs/MakePayment.html b/docs/MakePayment.html index 7775d02..5d9821c 100644 --- a/docs/MakePayment.html +++ b/docs/MakePayment.html @@ -155,13 +155,13 @@

    new MakePa


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/Metrics.html b/docs/Metrics.html index 8946845..912ea6d 100644 --- a/docs/Metrics.html +++ b/docs/Metrics.html @@ -207,13 +207,13 @@

    Classes


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/actions.js.html b/docs/actions.js.html index 307c9e0..28b2a01 100644 --- a/docs/actions.js.html +++ b/docs/actions.js.html @@ -1392,13 +1392,13 @@

    Source: actions.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/global.html b/docs/global.html index 7259fdd..3cea2d7 100644 --- a/docs/global.html +++ b/docs/global.html @@ -4988,13 +4988,13 @@
    Parameters:

    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/index.html b/docs/index.html index 8b021e4..3d5cba9 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,7 +2,7 @@ - JSDoc: JSDoc Home + JSDoc: JSDoc of employer-web-client @@ -17,7 +17,7 @@
    -

    JSDoc Home

    +

    JSDoc of employer-web-client

    @@ -57,13 +57,13 @@

    There, you will find each documentation file.


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/utils_api_wrapper.js.html b/docs/utils_api_wrapper.js.html index bef2185..23cacc0 100644 --- a/docs/utils_api_wrapper.js.html +++ b/docs/utils_api_wrapper.js.html @@ -354,13 +354,13 @@

    Source: utils/api_wrapper.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_applications.js.html b/docs/views_applications.js.html index fa5f1c5..717c4b9 100644 --- a/docs/views_applications.js.html +++ b/docs/views_applications.js.html @@ -361,13 +361,13 @@

    Source: views/applications.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_deductions.js.html b/docs/views_deductions.js.html index 078af15..64fcd67 100644 --- a/docs/views_deductions.js.html +++ b/docs/views_deductions.js.html @@ -237,13 +237,13 @@

    Source: views/deductions.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_favorites.js.html b/docs/views_favorites.js.html index 7ee612a..9fa3ec0 100644 --- a/docs/views_favorites.js.html +++ b/docs/views_favorites.js.html @@ -551,13 +551,13 @@

    Source: views/favorites.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_invites.js.html b/docs/views_invites.js.html index c760763..035be2d 100644 --- a/docs/views_invites.js.html +++ b/docs/views_invites.js.html @@ -317,13 +317,13 @@

    Source: views/invites.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_locations.js.html b/docs/views_locations.js.html index 8fd4c06..0a8d120 100644 --- a/docs/views_locations.js.html +++ b/docs/views_locations.js.html @@ -544,13 +544,13 @@

    Source: views/locations.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_charts.js.html b/docs/views_metrics_charts.js.html index ecb122d..cf15252 100644 --- a/docs/views_metrics_charts.js.html +++ b/docs/views_metrics_charts.js.html @@ -97,13 +97,13 @@

    Source: views/metrics/charts.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_GeneralStats.js.html b/docs/views_metrics_general-stats_GeneralStats.js.html index d31ac4b..297da8c 100644 --- a/docs/views_metrics_general-stats_GeneralStats.js.html +++ b/docs/views_metrics_general-stats_GeneralStats.js.html @@ -100,13 +100,13 @@

    Source: views/metrics/general-stats/GeneralStats.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_Hours_Hours.js.html b/docs/views_metrics_general-stats_Hours_Hours.js.html index 24da0ce..6c170a5 100644 --- a/docs/views_metrics_general-stats_Hours_Hours.js.html +++ b/docs/views_metrics_general-stats_Hours_Hours.js.html @@ -139,13 +139,13 @@

    Source: views/metrics/general-stats/Hours/Hours.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_Hours_HoursData.js.html b/docs/views_metrics_general-stats_Hours_HoursData.js.html index efa3b44..f8b1b28 100644 --- a/docs/views_metrics_general-stats_Hours_HoursData.js.html +++ b/docs/views_metrics_general-stats_Hours_HoursData.js.html @@ -395,13 +395,13 @@

    Source: views/metrics/general-stats/Hours/HoursData.js
    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html b/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html index 6337d09..05443ea 100644 --- a/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html +++ b/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html @@ -243,13 +243,13 @@

    Source: views/metrics/general-stats/JobSeekers/JobSeekers
    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html b/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html index d9d36f4..ef971bd 100644 --- a/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html +++ b/docs/views_metrics_general-stats_JobSeekers_JobSeekersData.js.html @@ -210,13 +210,13 @@

    Source: views/metrics/general-stats/JobSeekers/JobSeekers
    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_Shifts_Shifts.js.html b/docs/views_metrics_general-stats_Shifts_Shifts.js.html index 8124c07..4ce04ea 100644 --- a/docs/views_metrics_general-stats_Shifts_Shifts.js.html +++ b/docs/views_metrics_general-stats_Shifts_Shifts.js.html @@ -142,13 +142,13 @@

    Source: views/metrics/general-stats/Shifts/Shifts.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html b/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html index ba990f3..8748d5b 100644 --- a/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html +++ b/docs/views_metrics_general-stats_Shifts_ShiftsData.js.html @@ -131,13 +131,13 @@

    Source: views/metrics/general-stats/Shifts/ShiftsData.js<
    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_metrics.js.html b/docs/views_metrics_metrics.js.html index be489aa..42debcb 100644 --- a/docs/views_metrics_metrics.js.html +++ b/docs/views_metrics_metrics.js.html @@ -213,13 +213,13 @@

    Source: views/metrics/metrics.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_punctuality_Punctuality.js.html b/docs/views_metrics_punctuality_Punctuality.js.html index 8461472..064259d 100644 --- a/docs/views_metrics_punctuality_Punctuality.js.html +++ b/docs/views_metrics_punctuality_Punctuality.js.html @@ -218,13 +218,13 @@

    Source: views/metrics/punctuality/Punctuality.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_punctuality_PunctualityData.js.html b/docs/views_metrics_punctuality_PunctualityData.js.html index 23b9276..57887cd 100644 --- a/docs/views_metrics_punctuality_PunctualityData.js.html +++ b/docs/views_metrics_punctuality_PunctualityData.js.html @@ -267,13 +267,13 @@

    Source: views/metrics/punctuality/PunctualityData.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_queue_Queue.js.html b/docs/views_metrics_queue_Queue.js.html index 03e45fd..4b7b683 100644 --- a/docs/views_metrics_queue_Queue.js.html +++ b/docs/views_metrics_queue_Queue.js.html @@ -184,13 +184,13 @@

    Source: views/metrics/queue/Queue.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_queue_QueueData.js.html b/docs/views_metrics_queue_QueueData.js.html index a61510b..c9f6513 100644 --- a/docs/views_metrics_queue_QueueData.js.html +++ b/docs/views_metrics_queue_QueueData.js.html @@ -203,13 +203,13 @@

    Source: views/metrics/queue/QueueData.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_metrics_ratings_Ratings.js.html b/docs/views_metrics_ratings_Ratings.js.html index e5d4fa7..8b57d9a 100644 --- a/docs/views_metrics_ratings_Ratings.js.html +++ b/docs/views_metrics_ratings_Ratings.js.html @@ -246,13 +246,13 @@

    Source: views/metrics/ratings/Ratings.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_payments.js.html b/docs/views_payments.js.html index e36708f..8d994ef 100644 --- a/docs/views_payments.js.html +++ b/docs/views_payments.js.html @@ -220,13 +220,13 @@

    Source: views/payments.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_payroll.js.html b/docs/views_payroll.js.html index 6a609fe..70954f4 100644 --- a/docs/views_payroll.js.html +++ b/docs/views_payroll.js.html @@ -4873,13 +4873,13 @@

    Source: views/payroll.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_profile.js.html b/docs/views_profile.js.html index 2c2894f..d9383ee 100644 --- a/docs/views_profile.js.html +++ b/docs/views_profile.js.html @@ -1028,13 +1028,13 @@

    Source: views/profile.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_ratings.js.html b/docs/views_ratings.js.html index c900e1c..7bd345f 100644 --- a/docs/views_ratings.js.html +++ b/docs/views_ratings.js.html @@ -545,13 +545,13 @@

    Source: views/ratings.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_shifts.js.html b/docs/views_shifts.js.html index b95598c..d8c8700 100644 --- a/docs/views_shifts.js.html +++ b/docs/views_shifts.js.html @@ -3246,13 +3246,13 @@

    Source: views/shifts.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_subscriptions.js.html b/docs/views_subscriptions.js.html index 8da5fdb..273baa1 100644 --- a/docs/views_subscriptions.js.html +++ b/docs/views_subscriptions.js.html @@ -221,13 +221,13 @@

    Source: views/subscriptions.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/docs/views_talents.js.html b/docs/views_talents.js.html index 0cc3a96..5f70b71 100644 --- a/docs/views_talents.js.html +++ b/docs/views_talents.js.html @@ -677,13 +677,13 @@

    Source: views/talents.js


    - Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:50:45 GMT+0000 (Coordinated Universal Time) + Documentation generated by JSDoc 3.6.11 on Wed Oct 05 2022 17:57:01 GMT+0000 (Coordinated Universal Time)
    diff --git a/jsdoc-custom-template/publish.js b/jsdoc-custom-template/publish.js index 2e3397f..522746b 100644 --- a/jsdoc-custom-template/publish.js +++ b/jsdoc-custom-template/publish.js @@ -355,7 +355,7 @@ function linktoExternal(longName, name) { */ function buildNav(members) { let globalNav; - let nav = '

    JSDoc Home

    '; + let nav = '

    JSDoc of employer-web-client

    '; const seen = {}; const seenTutorials = {}; @@ -613,7 +613,7 @@ exports.publish = (taffyData, opts, tutorials) => { files = find({kind: 'file'}); packages = find({kind: 'package'}); - generate('JSDoc Home', + generate('JSDoc of employer-web-client', packages.concat( [{ kind: 'mainpage', diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js index 83fc105..6eb58ad 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js @@ -47,6 +47,12 @@ export const JobSeekers = (props) => { // Setting up main data sources let JobSeekersData = JobSeekersDataGenerator(shifsList, workersList) let NewJobSeekersData = NewJobSeekersDataGenerator(workersList) + + // Colors + const purple = "#5c00b8"; + const lightPink = "#eb00eb"; + const darkTeal = "#009e9e"; + const green = "#06ff05"; // Data for pie chart ------------------------------------------------------------------------------------- @@ -67,12 +73,6 @@ export const JobSeekers = (props) => { // Data for bar chart ------------------------------------------------------------------------------------- - // Colors - const purple = "#5c00b8"; - const lightPink = "#eb00eb"; - const darkTeal = "#009e9e"; - const green = "#06ff05"; - // Preparing data to be passed to the chart component const newJobSeekersData = { labels: NewJobSeekersData.map((data) => data.description), From ecf74c021369d59d75561f4c968daf5c9a4b391e Mon Sep 17 00:00:00 2001 From: paola9896 Date: Wed, 12 Oct 2022 19:04:55 +0000 Subject: [PATCH 09/10] Temporal pause on Metrics --- ..._metrics_general-stats_Hours_Hours.js.html | 2 +- ...eneral-stats_JobSeekers_JobSeekers.js.html | 4 +- ...etrics_general-stats_Shifts_Shifts.js.html | 2 +- ...ws_metrics_punctuality_Punctuality.js.html | 4 +- docs/views_metrics_ratings_Ratings.js.html | 2 +- docs/views_payroll.js.html | 2 +- package-lock.json | 19 + package.json | 1 + src/js/views/metrics/dummy-data/ShiftsData.js | 570 ++++++++++++++++++ .../views/metrics/dummy-data/WorkersData.js | 203 +++++++ .../metrics/general-stats/GeneralStats.js | 68 +-- .../metrics/general-stats/Hours/Hours.js | 194 ++++-- .../general-stats/JobSeekers/JobSeekers.js | 269 ++++++--- .../metrics/general-stats/Shifts/Shifts.js | 538 +++++++++++++++-- src/js/views/metrics/metrics.js | 16 +- .../views/metrics/punctuality/Punctuality.js | 277 ++++++--- src/js/views/metrics/queue/Queue.js | 25 +- src/js/views/metrics/ratings/Ratings.js | 2 +- src/js/views/payroll.js | 2 +- 19 files changed, 1830 insertions(+), 370 deletions(-) create mode 100644 src/js/views/metrics/dummy-data/ShiftsData.js create mode 100644 src/js/views/metrics/dummy-data/WorkersData.js diff --git a/docs/views_metrics_general-stats_Hours_Hours.js.html b/docs/views_metrics_general-stats_Hours_Hours.js.html index 6c170a5..b18292f 100644 --- a/docs/views_metrics_general-stats_Hours_Hours.js.html +++ b/docs/views_metrics_general-stats_Hours_Hours.js.html @@ -76,7 +76,7 @@

    Source: views/metrics/general-stats/Hours/Hours.js

    <div className="col text-center"> <h2 className="mb-4">Hours Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> diff --git a/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html b/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html index 05443ea..97f2153 100644 --- a/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html +++ b/docs/views_metrics_general-stats_JobSeekers_JobSeekers.js.html @@ -124,7 +124,7 @@

    Source: views/metrics/general-stats/JobSeekers/JobSeekers <div className="col text-center"> <h2 className="mb-4">Job Seekers Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> @@ -163,7 +163,7 @@

    Source: views/metrics/general-stats/JobSeekers/JobSeekers <div className="col text-center"> <h2 className="mb-4">New Job Seekers Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> diff --git a/docs/views_metrics_general-stats_Shifts_Shifts.js.html b/docs/views_metrics_general-stats_Shifts_Shifts.js.html index 4ce04ea..c4ebadb 100644 --- a/docs/views_metrics_general-stats_Shifts_Shifts.js.html +++ b/docs/views_metrics_general-stats_Shifts_Shifts.js.html @@ -79,7 +79,7 @@

    Source: views/metrics/general-stats/Shifts/Shifts.js

    <div className="col text-center"> <h2 className="mb-4">Shifts Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> diff --git a/docs/views_metrics_punctuality_Punctuality.js.html b/docs/views_metrics_punctuality_Punctuality.js.html index 064259d..843aa70 100644 --- a/docs/views_metrics_punctuality_Punctuality.js.html +++ b/docs/views_metrics_punctuality_Punctuality.js.html @@ -105,7 +105,7 @@

    Source: views/metrics/punctuality/Punctuality.js

    <div className="col text-center"> <h2 className="mb-4">Clock-Ins Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> @@ -143,7 +143,7 @@

    Source: views/metrics/punctuality/Punctuality.js

    <div className="col text-center"> <h2 className="mb-4">Clock-Outs Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> diff --git a/docs/views_metrics_ratings_Ratings.js.html b/docs/views_metrics_ratings_Ratings.js.html index 8b57d9a..1b35271 100644 --- a/docs/views_metrics_ratings_Ratings.js.html +++ b/docs/views_metrics_ratings_Ratings.js.html @@ -173,7 +173,7 @@

    Source: views/metrics/ratings/Ratings.js

    <div className="col text-center"> <h2 className="mb-4">Employee Ratings Table</h2> - <table className="table table-bordered text-center"> + <table className="table table-bordered border-dark text-center"> <thead className="thead-dark"> {/* Table columns */} <tr> diff --git a/docs/views_payroll.js.html b/docs/views_payroll.js.html index 70954f4..ee123c2 100644 --- a/docs/views_payroll.js.html +++ b/docs/views_payroll.js.html @@ -4492,7 +4492,7 @@

    Source: views/payroll.js

    return ( <table - className="table table-sm table-bordered" + className="table table-sm table-bordered border-dark" style={{ fontSize: "12px", border: "1px solid black" }} > <thead style={{ background: "transparent" }}> diff --git a/package-lock.json b/package-lock.json index 7e647c9..35f0458 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26779,6 +26779,11 @@ "cropperjs": "^1.5.9" } }, + "react-date-object": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.5.tgz", + "integrity": "sha512-iAtF2nz+jZjy3w36qKx2MsxXqbiL7UgGljz9jPjLWxWCl4rDTZUMS/i7hKKiVbL1lJlmubz1Xu6W3i6CW06kTg==" + }, "react-datepicker": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.7.0.tgz", @@ -27661,6 +27666,11 @@ } } }, + "react-element-popper": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.6.tgz", + "integrity": "sha512-8va7mUmrKIkUnaM2t5Dyctd8cjgVgVcrv5vVD0FRay0sN6EPBCKa0bDi1/KmVDAjfgSIn7zQnjtc4VojcGrkgQ==" + }, "react-error-overlay": { "version": "6.0.10", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.10.tgz", @@ -27757,6 +27767,15 @@ "prop-types": "^15.7.2" } }, + "react-multi-date-picker": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-3.3.1.tgz", + "integrity": "sha512-W6SUBCULMgfNJstQx7BNzojkNdG9LVpOdYJIMjqTS1scWZ9BqG8nIIOhHUAObrTEdwz2S7eMxcDg6UeXe5D6uw==", + "requires": { + "react-date-object": "^2.1.5", + "react-element-popper": "^2.1.6" + } + }, "react-onclickoutside": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.7.1.tgz", diff --git a/package.json b/package.json index e2c2f1d..55d1bbd 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "react-dropzone": "^10.1.5", "react-joyride": "^2.2.1", "react-loader-spinner": "^3.1.14", + "react-multi-date-picker": "^3.3.1", "react-pdf": "^5.3.2", "react-places-autocomplete": "^7.2.0", "react-plaid-link": "^1.5.0", diff --git a/src/js/views/metrics/dummy-data/ShiftsData.js b/src/js/views/metrics/dummy-data/ShiftsData.js new file mode 100644 index 0000000..95ad9a5 --- /dev/null +++ b/src/js/views/metrics/dummy-data/ShiftsData.js @@ -0,0 +1,570 @@ +export const ShiftsData = [ + { + id: 1, + employees: [5, 4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:00:00Z", + ended_at: "2022-10-12T15:00:00Z", + automatically_closed: false, + employee: 5, + shift: 1 + }, + { + started_at: "2022-10-12T15:00:00Z", + ended_at: "2022-10-12T20:10:00Z", + automatically_closed: false, + employee: 5, + shift: 1 + }, + { + started_at: "2022-10-12T10:05:00Z", + ended_at: "2022-10-12T15:00:00Z", + automatically_closed: false, + employee: 4, + shift: 1 + }, + { + started_at: "2022-10-12T15:00:00Z", + ended_at: "2022-10-12T20:15:00Z", + automatically_closed: false, + employee: 4, + shift: 1 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 2, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:30:09Z", + ended_at: "2022-10-12T13:00:00Z", + automatically_closed: false, + employee: 5, + shift: 2 + }, + { + started_at: "2022-10-12T13:00:00Z", + ended_at: "2022-10-12T20:25:00Z", + automatically_closed: false, + employee: 5, + shift: 2 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 3, + employees: [4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:00:00Z", + ended_at: "2022-10-12T12:07:00Z", + automatically_closed: false, + employee: 4, + shift: 3 + }, + { + started_at: "2022-10-12T12:20:00Z", + ended_at: "2022-10-12T17:00:00Z", + automatically_closed: false, + employee: 4, + shift: 3 + }, + { + started_at: "2022-10-12T17:00:00Z", + ended_at: "2022-10-12T20:20:00Z", + automatically_closed: false, + employee: 4, + shift: 3 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 4, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:10:09Z", + ended_at: "2022-10-12T15:30:09Z", + automatically_closed: false, + employee: 5, + shift: 4 + }, + { + started_at: "2022-10-12T15:42:09Z", + ended_at: "2022-10-12T18:00:09Z", + automatically_closed: false, + employee: 5, + shift: 4 + }, + { + started_at: "2022-10-12T18:30:09Z", + ended_at: "2022-10-12T20:31:09Z", + automatically_closed: false, + employee: 5, + shift: 4 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 5, + employees: [4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:00:09Z", + ended_at: "2022-10-12T20:19:09Z", + automatically_closed: true, + employee: 4, + shift: 5 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 6, + employees: [4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:40:09Z", + ended_at: "2022-10-12T19:25:09Z", + automatically_closed: false, + employee: 4, + shift: 6 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 7, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:10:09Z", + ended_at: "2022-10-12T14:10:09Z", + automatically_closed: false, + employee: 5, + shift: 7 + }, + { + started_at: "2022-10-12T14:28:09Z", + ended_at: "2022-10-12T19:20:09Z", + automatically_closed: false, + employee: 5, + shift: 7 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 8, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:14:09Z", + ended_at: "2022-10-12T16:30:09Z", + automatically_closed: false, + employee: 5, + shift: 8 + }, + { + started_at: "2022-10-12T17:00:09Z", + ended_at: "2022-10-12T21:35:09Z", + automatically_closed: false, + employee: 5, + shift: 8 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 9, + employees: [4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:08:09Z", + ended_at: "2022-10-12T15:00:09Z", + automatically_closed: false, + employee: 4, + shift: 9 + }, + { + started_at: "2022-10-12T15:36:09Z", + ended_at: "2022-10-12T20:00:09Z", + automatically_closed: false, + employee: 4, + shift: 9 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 10, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:10:09Z", + ended_at: "2022-10-12T14:00:09Z", + automatically_closed: false, + employee: 5, + shift: 10 + }, + { + started_at: "2022-10-12T14:40:09Z", + ended_at: "2022-10-12T20:40:09Z", + automatically_closed: false, + employee: 5, + shift: 10 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 11, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T08:00:09Z", + ended_at: "2022-10-12T13:00:09Z", + automatically_closed: false, + employee: 5, + shift: 11 + }, + { + started_at: "2022-10-12T13:45:09Z", + ended_at: "2022-10-12T20:45:09Z", + automatically_closed: false, + employee: 5, + shift: 11 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 12, + employees: [4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:10:09Z", + ended_at: "2022-10-12T12:00:09Z", + automatically_closed: false, + employee: 4, + shift: 12 + }, + { + started_at: "2022-10-12T12:34:09Z", + ended_at: "2022-10-12T20:00:09Z", + automatically_closed: false, + employee: 4, + shift: 12 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 13, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:10:09Z", + ended_at: "2022-10-12T22:00:09Z", + automatically_closed: true, + employee: 5, + shift: 13 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T22:00:00Z" + }, + { + id: 14, + employees: [4, 5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T09:10:09Z", + ended_at: "2022-10-12T22:00:09Z", + automatically_closed: false, + employee: 4, + shift: 14 + }, + { + started_at: "2022-10-12T09:10:09Z", + ended_at: "2022-10-12T22:00:09Z", + automatically_closed: true, + employee: 5, + shift: 14 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T20:00:00Z" + }, + { + id: 15, + employees: [4], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:10:09Z", + ended_at: "2022-10-12T12:00:09Z", + automatically_closed: false, + employee: 4, + shift: 15 + }, + { + started_at: "2022-10-12T12:30:09Z", + ended_at: "2022-10-12T22:00:09Z", + automatically_closed: true, + employee: 4, + shift: 15 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T22:00:00Z" + }, + { + id: 16, + employees: [5], + status: "COMPLETED", + clockin: [ + { + started_at: "2022-10-12T10:05:09Z", + ended_at: "2022-10-12T12:00:09Z", + automatically_closed: false, + employee: 5, + shift: 16 + }, + { + started_at: "2022-10-12T12:30:09Z", + ended_at: "2022-10-12T22:00:09Z", + automatically_closed: false, + employee: 5, + shift: 16 + } + ], + starting_at: "2022-10-12T10:00:00Z", + ending_at: "2022-10-12T22:00:00Z" + }, + { + id: 17, + status: "FILLED", + clockin: [], + employees: [2] + }, + { + id: 18, + status: "FILLED", + clockin: [], + employees: [1] + }, + { + id: 19, + status: "FILLED", + clockin: [], + employees: [8] + }, + { + id: 20, + status: "FILLED", + clockin: [], + employees: [9] + }, + { + id: 21, + status: "FILLED", + clockin: [], + employees: [15] + }, + { + id: 22, + status: "FILLED", + clockin: [], + employees: [13] + }, + { + id: 23, + status: "FILLED", + clockin: [], + employees: [1] + }, + { + id: 24, + status: "FILLED", + clockin: [], + employees: [8] + }, + { + id: 25, + status: "FILLED", + clockin: [], + employees: [11] + }, + { + id: 26, + status: "FILLED", + clockin: [], + employees: [2] + }, + { + id: 27, + status: "FILLED", + clockin: [], + employees: [10] + }, + { + id: 28, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 29, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 30, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 31, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 32, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 33, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 34, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 35, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 36, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 37, + status: "EXPIRED", + clockin: [], + employees: [] + }, + { + id: 38, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 39, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 40, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 41, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 42, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 43, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 44, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 45, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 46, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 47, + status: "OPEN", + clockin: [], + employees: [] + }, + { + id: 48, + status: "OPEN", + clockin: [], + employees: [] + } + ]; + \ No newline at end of file diff --git a/src/js/views/metrics/dummy-data/WorkersData.js b/src/js/views/metrics/dummy-data/WorkersData.js new file mode 100644 index 0000000..ee2ca79 --- /dev/null +++ b/src/js/views/metrics/dummy-data/WorkersData.js @@ -0,0 +1,203 @@ +export const WorkersData = [ + { + id: 1, + user: { + first_name: "Andrea", + last_name: "Villa", + profile: { + picture: "" + } + }, + created_at: "2022-09-28T23:12:48.00Z", + rating: 4 + }, + { + id: 2, + user: { + first_name: "Maria", + last_name: "Cuevas", + profile: { + picture: "" + } + }, + created_at: "2022-08-27T23:12:48.00Z", + rating: 4 + }, + { + id: 3, + user: { + first_name: "Eric", + last_name: "Schumer", + profile: { + picture: "" + } + }, + rating: 5, + total_ratings: 0, + created_at: "2022-09-02T23:12:48.00Z", + employment_verification_status: "APPROVED" + }, + { + id: 4, + user: { + first_name: "Hans", + last_name: "Zimmer", + profile: { + picture: "" + } + }, + rating: 3, + total_ratings: 0, + created_at: "2022-08-25T23:12:48.00Z", + employment_verification_status: "APPROVED" + }, + { + id: 5, + user: { + first_name: "John", + last_name: "Doe", + profile: { + picture: "" + } + }, + rating: 4, + total_ratings: 0, + created_at: "2022-08-29T23:12:48.00Z", + employment_verification_status: "APPROVED" + }, + { + id: 6, + user: { + first_name: "Alexander", + last_name: "Smith", + profile: { + picture: "" + } + }, + created_at: "2022-08-27T23:12:48.00Z", + rating: 4 + }, + { + id: 7, + user: { + first_name: "Des", + last_name: "Maxwell", + profile: { + picture: "" + } + }, + rating: 2, + total_ratings: 0, + created_at: "2022-09-01T23:12:48.00Z", + employment_verification_status: "APPROVED" + }, + { + id: 8, + user: { + first_name: "Estella", + last_name: "Reyes", + profile: { + picture: "" + } + }, + created_at: "2022-08-26T23:12:48.00Z", + rating: 3 + }, + { + id: 9, + user: { + first_name: "Henry", + last_name: "Stevens", + profile: { + picture: "" + } + }, + created_at: "2022-08-30T23:12:48.00Z", + rating: 2 + }, + { + id: 10, + user: { + first_name: "Cacia", + last_name: "Alvarez", + profile: { + picture: "" + } + }, + created_at: "2022-09-26T23:12:48.00Z", + rating: 2 + }, + { + id: 11, + user: { + first_name: "Esteban", + last_name: "Rodriguez", + profile: { + picture: "" + } + }, + created_at: "2022-09-29T23:12:48.00Z", + rating: null + }, + { + id: 12, + user: { + first_name: "Jessie", + last_name: "Simmons", + profile: { + picture: "" + } + }, + created_at: "2022-09-30T23:12:48.00Z", + rating: 1 + }, + { + id: 13, + user: { + first_name: "Erika", + last_name: "Maxwell", + profile: { + picture: "" + } + }, + created_at: "2021-10-01T23:12:48.00Z", + rating: 5 + }, + { + id: 14, + user: { + first_name: "Juan", + last_name: "Ponce", + profile: { + picture: "" + } + }, + created_at: "2021-10-02T23:12:48.00Z", + rating: 1 + }, + { + id: 15, + user: { + first_name: "Julio", + last_name: "Perez", + profile: { + picture: "" + } + }, + created_at: "2021-10-03T23:12:48.00Z", + rating: 2 + }, + { + id: 16, + user: { + first_name: "Carlos", + last_name: "Ocacio", + profile: { + picture: "" + } + }, + created_at: "2022-09-27T23:12:48.00Z", + rating: null + } + ]; + \ No newline at end of file diff --git a/src/js/views/metrics/general-stats/GeneralStats.js b/src/js/views/metrics/general-stats/GeneralStats.js index 83f2761..6ad3c3b 100644 --- a/src/js/views/metrics/general-stats/GeneralStats.js +++ b/src/js/views/metrics/general-stats/GeneralStats.js @@ -18,47 +18,45 @@ export const GeneralStats = (props) => { // Setting up main data sources let workers = props.workers let shifts = props.shifts - + // Return ---------------------------------------------------------------------------------------------------- return ( - <> -
    - {/* Tabs Controller Starts */} - - {/* Tabs Controller Ends */} +
    + {/* Tabs Controller Starts */} + + {/* Tabs Controller Ends */} - {/* Tabs Content Starts */} - + {/* Tabs Content Ends */} +
    ) } diff --git a/src/js/views/metrics/general-stats/Hours/Hours.js b/src/js/views/metrics/general-stats/Hours/Hours.js index e5ad960..d5c852b 100644 --- a/src/js/views/metrics/general-stats/Hours/Hours.js +++ b/src/js/views/metrics/general-stats/Hours/Hours.js @@ -1,7 +1,9 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { PieChart } from "../../charts"; import { HoursDataGenerator } from "./HoursData"; - +import "react-datepicker/dist/react-datepicker.css"; +import DatePicker from "react-datepicker"; +import moment from "moment"; /** * @function @@ -13,8 +15,72 @@ import { HoursDataGenerator } from "./HoursData"; */ export const Hours = (props) => { + let allShifts = props.shifts + + // DatePicker --------------------------------------------------------------------------------------------- + + // Date selected through the DatePicker + const [selectedDate, setSelectedDate] = useState(new Date()); + + // Monday of X week (default: current week) + const [start, setStart] = useState( + moment().startOf("isoWeek").format("YYYY-MM-DD") + ); + + // Sunday of X week (default: current week) + const [end, setEnd] = useState( + moment().endOf("isoWeek").format("YYYY-MM-DD") + ); + + // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- + const filterShifts = () => { + + // Array for filtered shifts + let filteredShifts = []; + + // Keeping shifts that exist within the selected dates + allShifts.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); + let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); + + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + }); + + // Returning filtered shifts + return filteredShifts; + }; + + // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- + + useEffect(() => { + + // Setting up the new Monday + let formattedStart = moment(selectedDate) + .startOf("isoWeek") + .format("YYYY-MM-DD"); + + setStart(formattedStart); + + // Setting up the new Sunday + let formattedEnd = moment(selectedDate) + .endOf("isoWeek") + .format("YYYY-MM-DD"); + + setEnd(formattedEnd); + + }, [selectedDate]); + + let newShifts = filterShifts() + // Setting up main data source - let HoursData = HoursDataGenerator(props.shifts) + let HoursData = HoursDataGenerator(newShifts) // Data for pie chart ------------------------------------------------------------------------------------- @@ -40,64 +106,88 @@ export const Hours = (props) => { // Return ---------------------------------------------------------------------------------------------------- return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Hours Table Starts */} -
    -

    Hours Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {HoursData.map((item, i) => { - return item.description === "Available Hours" ? ( - - - - - - ) : ( - - - - - - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    +
    +
    + {/* Week Announcer */} +
    +

    {`Week of ${start} - ${end}`}

    +
    + + {/* Week Selector Text */} +
    +

    Select a day of the desired week:

    +
    + + {/* Week Selector Tool */} +
    + setSelectedDate(date)} + />
    - {/* Hours Table Ends */}
    - {/* Left Column Ends */} - {/* Right Column Starts */} -
    -
    - {/* Hours Chart Starts*/} -
    -

    Hours Chart

    +
    + {/* Left Column Starts */} +
    +
    + {/* Hours Table Starts */} +
    +

    Hours Table

    + + + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {HoursData.map((item, i) => { + return item.description === "Available Hours" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Hours Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Hours Chart Starts*/} +
    +

    Hours Chart

    -
    - +
    + +
    + {/* Hours Chart Ends*/}
    - {/* Hours Chart Ends*/}
    + {/* Right Column Ends */}
    - {/* Right Column Ends */}
    ) } diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js index 6eb58ad..20d1a65 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js @@ -1,6 +1,9 @@ import React, { useEffect, useState } from "react"; import { PieChart, BarChart } from '../../charts'; import { JobSeekersDataGenerator, NewJobSeekersDataGenerator } from "./JobSeekersData"; +import "react-datepicker/dist/react-datepicker.css"; +import DatePicker from "react-datepicker"; +import moment from "moment"; /** * @function @@ -42,20 +45,82 @@ export const JobSeekers = (props) => { handleProps() }, [props]) + // DatePicker ------------------------------------------------------------------------------------------------- + + // Date selected through the DatePicker + const [selectedDate, setSelectedDate] = useState(new Date()); + + // Monday of X week (default: current week) + const [start, setStart] = useState( + moment().startOf("isoWeek").format("YYYY-MM-DD") + ); + + // Sunday of X week (default: current week) + const [end, setEnd] = useState( + moment().endOf("isoWeek").format("YYYY-MM-DD") + ); + + // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- + const filterShifts = () => { + + // Array for filtered shifts + let filteredShifts = []; + + // Keeping shifts that exist within the selected dates + shifsList?.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); + let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); + + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + }); + + // Returning filtered shifts + return filteredShifts; + }; + + // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- + + useEffect(() => { + + // Setting up the new Monday + let formattedStart = moment(selectedDate) + .startOf("isoWeek") + .format("YYYY-MM-DD"); + + setStart(formattedStart); + + // Setting up the new Sunday + let formattedEnd = moment(selectedDate) + .endOf("isoWeek") + .format("YYYY-MM-DD"); + + setEnd(formattedEnd); + + }, [selectedDate]); + if (workersList.length > 0) { + let specialShifts = filterShifts() + // Setting up main data sources - let JobSeekersData = JobSeekersDataGenerator(shifsList, workersList) + let JobSeekersData = JobSeekersDataGenerator(specialShifts, workersList) let NewJobSeekersData = NewJobSeekersDataGenerator(workersList) - + + // Data for pie chart ------------------------------------------------------------------------------------- + // Colors const purple = "#5c00b8"; const lightPink = "#eb00eb"; const darkTeal = "#009e9e"; const green = "#06ff05"; - // Data for pie chart ------------------------------------------------------------------------------------- - // Taking out the "Totals" from the chart view let pieData = JobSeekersData.filter((item) => { return item.description !== "Total Job Seekers" }) // Taking out the "Totals" from the chart view @@ -88,116 +153,136 @@ export const JobSeekers = (props) => { // Return ---------------------------------------------------------------------------------------------------- return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Job Seekers Table Starts */} -
    -

    Job Seekers Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {JobSeekersData.map((item, i) => { - return item.description === "Total Job Seekers" ? ( - - - - - - ) : - ( - +
    +
    +
    +
    +

    Select a day of the desired week:

    +
    + + {/* Calendar/DatePicker */} +
    + setSelectedDate(date)} + /> +
    +
    +
    + +
    + + {/* Left Column Starts */} +
    +
    + + {/* Job Seekers Table Starts */} +
    +

    Job Seekers Table

    + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {JobSeekersData.map((item, i) => { + return item.description === "Total Job Seekers" ? ( + - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + ) : + ( + +

    {item.description}

    +

    {item.qty}

    +

    {`${item.pct}%`}

    + + ) + })} + + +
    + {/* Job Seekers Table Ends */}
    - {/* Job Seekers Table Ends */} -
    -
    - {/* New Job Seekers Table Starts */} -
    -

    New Job Seekers Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {NewJobSeekersData.map((item, i) => { - return item.description === "Total Job Seekers" ? ( - - - - - - ) : - ( - +
    + {/* New Job Seekers Table Starts */} +
    +

    New Job Seekers Table

    + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {NewJobSeekersData.map((item, i) => { + return item.description === "Total Job Seekers" ? ( + - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + ) : + ( + +

    {item.description}

    +

    {item.qty}

    +

    {`${item.pct}%`}

    + + ) + })} + + +
    + {/* New Job Seekers Table Ends */}
    - {/* New Job Seekers Table Ends */}
    -
    - {/* Left Column Ends */} + {/* Left Column Ends */} - {/* Right Column Starts */} -
    -
    - {/* Job Seekers Chart Starts*/} -
    -

    Job Seekers Chart

    + {/* Right Column Starts */} +
    +
    + {/* Job Seekers Chart Starts*/} +
    +

    Job Seekers Chart

    -
    - +
    + +
    + {/* Job Seekers Chart Ends*/}
    - {/* Job Seekers Chart Ends*/} -
    -
    - {/* New Job Seekers Chart Starts*/} -
    -

    New Job Seekers Chart

    +
    + {/* New Job Seekers Chart Starts*/} +
    +

    New Job Seekers Chart

    -
    - +
    + +
    + {/* New Job Seekers Chart Ends*/}
    - {/* New Job Seekers Chart Ends*/}
    + {/* Right Column Ends */}
    - {/* Right Column Ends */}
    ) } else { diff --git a/src/js/views/metrics/general-stats/Shifts/Shifts.js b/src/js/views/metrics/general-stats/Shifts/Shifts.js index 0c70b25..2c95f0a 100644 --- a/src/js/views/metrics/general-stats/Shifts/Shifts.js +++ b/src/js/views/metrics/general-stats/Shifts/Shifts.js @@ -1,6 +1,9 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { BarChart } from "../../charts"; import { ShiftsDataGenerator } from "./ShiftsData"; +import weekends from "react-multi-date-picker/plugins/highlight_weekends" +import DatePicker from "react-multi-date-picker"; +import moment from "moment"; /** * @function @@ -12,9 +15,265 @@ import { ShiftsDataGenerator } from "./ShiftsData"; * @param {object} props - Contains an array of all the shifts. */ export const Shifts = (props) => { + /* Variables --------------------------------------------------- */ - // Setting up main data source - let ShiftsData = ShiftsDataGenerator(props.shifts) + /* Putting props into variable */ + let shiftProps = props.shifts; + + /* Start of given time period */ + const [start, setStart] = useState(moment().format("MM-DD-YYYY")); + /* End of given time period */ + const [end, setEnd] = useState(moment().format("MM-DD-YYYY")); + + /* Variables to store selected time period */ + const [period, setPeriod] = useState(); + + /* Variables to store selected dates */ + const [day, setDay] = useState(new Date()); + const [week, setWeek] = useState([ + new Date(), + new Date() + ]) + const [month, setMonth] = useState(new Date()); + const [year, setYear] = useState(new Date()); + + /* Variables to store date defaults*/ + const [defaultDay, setDefaultDay] = useState(new Date().toLocaleDateString()); + const [defaultWeek, setDefaultWeek] = useState( + new Date().toLocaleDateString() + ); + const [defaultMonth, setDefaultMonth] = useState( + new Date().toLocaleDateString("en-us", { month: "long", year: "numeric" }) + ); + const [defaultYear, setDefaultYear] = useState(new Date().getFullYear()); + + /* Handlers --------------------------------------------------- */ + + /* Day Handler */ + const handleDay = (day) => { + setDay(day); + setDefaultDay(day); + }; + + /* Week Handler */ + const handleWeek = (week) => { + setWeek(week); + setDefaultWeek(week); + }; + + /* Month Handler */ + const handleMonth = (month) => { + setMonth(month); + setDefaultMonth(month); + }; + + /* Year Handler */ + const handleYear = (year) => { + setYear(year); + setDefaultYear(year); + }; + + /* Calculate start and end of day */ + const startEndOfDay = () => { + // Transforming day value into string + let dayAsString = defaultDay.toString(); + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const dayFormatted = dayAsString.split(search).join(replaceWith); + + // Setting start and end + setStart(dayFormatted); + setEnd(dayFormatted); + }; + + /* Calculate start and end of week */ + const startEndOfWeek = () => { + // Transforming week value into string + let weekAsString = defaultWeek.toString(); + + let weeks = weekAsString.split(","); + + let endOfWeekPlaceholder = moment().isoWeekday(7).format("MM-DD-YYYY"); + + if (weeks.length === 1) { + weeks.push(endOfWeekPlaceholder); + } + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const weekStart = weeks[0].split(search).join(replaceWith); + const weekEnd = weeks[1].split(search).join(replaceWith); + + let weekStartF = moment(weekEnd).isoWeekday(1).format("MM-DD-YYYY"); + let weekEndF = moment(weekEnd).isoWeekday(7).format("MM-DD-YYYY"); + + // Setting start and end + setStart(weekStartF); + setEnd(weekEndF); + }; + + /* Calculate start and end of month */ + const startEndOfMonth = () => { + // Transforming month value into string + let monthAsString = defaultMonth.toString(); + + // Listing all months by number + const months = [ + { name: "January", number: "01" }, + { name: "February", number: "02" }, + { name: "March", number: "03" }, + { name: "April", number: "04" }, + { name: "May", number: "05" }, + { name: "June", number: "06" }, + { name: "July", number: "07" }, + { name: "August", number: "08" }, + { name: "September", number: "09" }, + { name: "October", number: "10" }, + { name: "November", number: "11" }, + { name: "December", number: "12" } + ]; + + // Determining year (number) of month value + let yearOfMonth = monthAsString.slice(-4); + + // Determining month (number) of month value + let monthOfMonth = ""; + + months.forEach((eachMonth) => { + if (monthAsString.includes(eachMonth.name)) { + monthOfMonth += eachMonth.number; + } + }); + + // Completing start of month + let completeMonthStart = `${yearOfMonth}-${monthOfMonth}-01`; + + // Setting up start + let startOfMonth = moment(completeMonthStart) + .startOf("month") + .format("MM-DD-YYYY"); + setStart(startOfMonth); + + // Setting up end + let endOfMonth = moment(startOfMonth).endOf("month").format("MM-DD-YYYY"); + setEnd(endOfMonth); + }; + + /* Calculate start and end of year */ + const startEndOfYear = () => { + // Transforming year value into string + let yearAsString = defaultYear.toString(); + + // Setting up start + let startOfYear = `${yearAsString}-01-01` + let startOfYearF = moment(startOfYear).startOf("year").format("MM-DD-YYYY"); + setStart(startOfYearF) + + // Setting up end + let endOfYearF = moment(startOfYearF).endOf("year").format("MM-DD-YYYY"); + setEnd(endOfYearF) + }; + + /* + Variables with the current start and end values + of day, week month, and year + */ + let currentDay = moment().format("MM-DD-YYYY"); + let currentWeekStart = moment().isoWeekday(1).format("MM-DD-YYYY"); + let currentWeekEnd = moment().isoWeekday(7).format("MM-DD-YYYY"); + let currentMonthStart = moment().startOf("month").format("MM-DD-YYYY"); + let currentMonthEnd = moment().endOf("month").format("MM-DD-YYYY"); + let currentYearStart = moment().startOf("year").format("MM-DD-YYYY"); + let currentYearEnd = moment().endOf("year").format("MM-DD-YYYY"); + + /* + UseEffect for the first render of + the page + */ + useEffect(() => { + setPeriod("Day"); + setStart(currentDay); + setEnd(currentDay); + }, []); + + /* + Use effects that get triggered + when the period value changes + */ + useEffect(() => { + if (period === "Day") { + setStart(currentDay); + setEnd(currentDay); + } else if (period === "Week") { + setStart(currentWeekStart); + setEnd(currentWeekEnd); + } else if (period === "Month") { + setStart(currentMonthStart); + setEnd(currentMonthEnd); + } else if (period === "Year") { + setStart(currentYearStart); + setEnd(currentYearEnd); + } else { + null; + } + }, [period]); + + /* + Use effects that get triggered + when the default values change + */ + useEffect(() => { + startEndOfDay(); + }, [defaultDay]); + + useEffect(() => { + startEndOfWeek(); + }, [defaultWeek]); + + useEffect(() => { + startEndOfMonth(); + }, [defaultMonth]); + + useEffect(() => { + startEndOfYear(); + }, [defaultYear]); + + /* + Function that filters shifts based on the + start and end of the selected date + */ + const filterShifts = () => { + + // Array for filtered shifts + let filteredShifts = []; + + // Keeping shifts that exist within the selected dates + shiftProps?.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("MM-DD-YYYY"); + let shiftEnd = moment(shift.ending_at).format("MM-DD-YYYY"); + + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + }); + + // Returning filtered shifts + return filteredShifts; + }; + + // Capturing filtered shifts + let shiftsFiltered = filterShifts(); + + // Setting up main data source for chart and table + let ShiftsData = ShiftsDataGenerator(shiftsFiltered); // Data for bar chart ------------------------------------------------------------------------------------- @@ -26,81 +285,234 @@ export const Shifts = (props) => { const darkPink = "#b200b2"; // Taking out the "Totals" from the chart view - let barData = ShiftsData.filter((item) => { return item.description !== "Total Shifts Posted " }) // Taking out the "Totals" from the chart view + let barData = ShiftsData.filter((item) => { + return item.description !== "Total Shifts Posted "; + }); // Taking out the "Totals" from the chart view // Preparing data to be passed to the chart component const shiftsData = { labels: barData.map((data) => data.description), - datasets: [{ - label: "Shifts", - data: barData.map((data) => data.qty), - backgroundColor: [ - purple, darkPink, lightPink, lightTeal, darkTeal - ], - }] - } + datasets: [ + { + label: "Shifts", + data: barData.map((data) => data.qty), + backgroundColor: [purple, darkPink, lightPink, lightTeal, darkTeal] + } + ] + }; // Return ---------------------------------------------------------------------------------------------------- return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Shifts Table Starts */} -
    -

    Shifts Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {ShiftsData.map((item, i) => { - return item.description === "Total Shifts Posted" ? ( - - - - - - ) : ( - - - - - - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    +
    +
    + {/* Time period Announcer */} +
    +

    Start: {start}

    +

    End: {end}

    +
    + + {/* Dropdown */} +
    +
    + +
    + + + + +
    +
    +
    + +
    + {/* DatePicker Conditional Rendering Starts */} + <> + {period === "Day" ? ( +
    +
    +

    {`${period}: ${defaultDay}`}

    +
    + + +
    + ) : period === "Week" ? ( +
    +
    +

    {`${period}: ${defaultWeek}`}

    +
    + + +
    + ) : period === "Month" ? ( +
    +
    +

    {`${period}: ${defaultMonth}`}

    +
    + + +
    + ) : period === "Year" ? ( +
    +
    +

    {`${period}: ${defaultYear}`}

    +
    + + +
    + ) : null} + + {/* DatePicker Conditional Rendering Ends */}
    - {/* Shifts Table Ends */}
    - {/* Left Column Ends */} - {/* Right Column Starts */} -
    -
    - {/* Shifts Chart Starts*/} -
    -

    Shifts Chart

    +
    + {/* Left Column Starts */} +
    +
    + {/* Shifts Table Starts */} +
    +

    Shifts Table

    + + + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {ShiftsData.map((item, i) => { + return item.description === "Total Shifts Posted" ? ( + + + + + + ) : ( + + + + + + ); + })} + +
    +

    Description

    +
    +

    Quantity

    +
    +

    Percentages

    +
    +

    {item.description}

    +
    +

    {item.qty}

    +
    +

    {`${item.pct}%`}

    +
    +

    {item.description}

    +
    +

    {item.qty}

    +
    +

    {`${item.pct}%`}

    +
    +
    + {/* Shifts Table Ends */} +
    +
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Shifts Chart Starts*/} +
    +

    Shifts Chart

    -
    - +
    + +
    + {/* Shifts Chart Ends*/}
    - {/* Shifts Chart Ends*/}
    + {/* Right Column Ends */}
    - {/* Right Column Ends */}
    - ) -} + ); +}; \ No newline at end of file diff --git a/src/js/views/metrics/metrics.js b/src/js/views/metrics/metrics.js index f17454d..9477c2a 100644 --- a/src/js/views/metrics/metrics.js +++ b/src/js/views/metrics/metrics.js @@ -9,6 +9,9 @@ import { GeneralStats } from "./general-stats/GeneralStats"; import { store, search } from "../../actions"; +import {WorkersData} from "./dummy-data/WorkersData"; +import {ShiftsData} from "./dummy-data/ShiftsData"; + /** * @description Creates the view for Metrics page, which renders 4 tabs with different components being called inside each one. * @since 09.28.22 by Paola Sanchez @@ -109,6 +112,11 @@ export class Metrics extends Flux.DashView { // List of all shifts let listOfShifts = this.state.allShifts; + // Temporal dummy data + + let dummyShifts = ShiftsData + let dummyWorkers = WorkersData + // --------------------------------------------- // Filtering expired shifts // let listOfShifts = @@ -148,25 +156,25 @@ export class Metrics extends Flux.DashView { > {/* General Stats Tab Starts */} {/* General Stats Tab Ends */} {/* Punctuality Tab Starts */} {/* Punctuality Tab Ends */} {/* Ratings Tab Starts */} {/* Ratings Tab Ends */} {/* Queue Tab Starts */} {/* Queue Tab Ends */}
    diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index 5251945..dee211f 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -1,6 +1,9 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; import { PieChart } from '../charts'; import { ClockInsDataGenerator, ClockOutsDataGenerator } from "./PunctualityData"; +import "react-datepicker/dist/react-datepicker.css"; +import DatePicker from "react-datepicker"; +import moment from "moment"; /** * @function @@ -14,9 +17,70 @@ import { ClockInsDataGenerator, ClockOutsDataGenerator } from "./PunctualityData */ export const Punctuality = (props) => { + // Putting props into variable + let shiftProps = props.shifts; + + // Date selected through the DatePicker + const [selectedDate, setSelectedDate] = useState(new Date()); + + // Monday of X week (default: current week) + const [start, setStart] = useState( + moment().startOf("isoWeek").format("YYYY-MM-DD") + ); + + // Sunday of X week (default: current week) + const [end, setEnd] = useState( + moment().endOf("isoWeek").format("YYYY-MM-DD") + ); + + // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- + const filterShifts = () => { + // Array for filtered shifts + let filteredShifts = []; + + // Keeping shifts that exist within the selected dates + shiftProps?.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); + let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); + + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + }); + + // Returning filtered shifts + return filteredShifts; + }; + + // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- + + useEffect(() => { + // Setting up the new Monday + let formattedStart = moment(selectedDate) + .startOf("isoWeek") + .format("YYYY-MM-DD"); + + setStart(formattedStart); + + // Setting up the new Sunday + let formattedEnd = moment(selectedDate) + .endOf("isoWeek") + .format("YYYY-MM-DD"); + + setEnd(formattedEnd); + }, [selectedDate]); + + // Capturing filtered shifts + let shiftsFiltered = filterShifts(); + // Setting up main data sources - let ClockInsData = ClockInsDataGenerator(props.shifts) - let ClockOutsData = ClockOutsDataGenerator(props.shifts) + let ClockInsData = ClockInsDataGenerator(shiftsFiltered); + let ClockOutsData = ClockOutsDataGenerator(shiftsFiltered); // Data for pie charts ------------------------------------------------------------------------------------- @@ -69,114 +133,137 @@ export const Punctuality = (props) => { // Return ---------------------------------------------------------------------------------------------------- return ( -
    - {/* Left Column Starts */} -
    -
    - {/* Clock-Ins Table Starts */} -
    -

    Clock-Ins Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {ClockInsData.map((item, i) => { - return item.description === "Total Clock-Ins" ? ( - - - - - - ) : ( - - - - - - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    +
    +
    + {/* Week Announcer */} +
    +

    {`Week of ${start} - ${end}`}

    - {/* Clock-Ins Table Ends */} -
    -
    - {/* Clock-Outs Table Starts */} -
    -

    Clock-Outs Table

    - - - - {/* Table columns */} - - - - - - - - - {/* Mapping the data to diplay it as table rows */} - {ClockOutsData.map((item, i) => { - return item.description === "Total Clock-Outs" ? ( - - - - - - ) : ( - - - - - - ) - })} - -

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + {/* Week Selector Text */} +
    +

    Select a day of the desired week:

    +
    + + {/* Week Selector Tool */} +
    + setSelectedDate(date)} + />
    - {/* Clock-Outs Table Ends */}
    - {/* Left Column Ends */} +
    + {/* Left Column Starts */} +
    +
    + {/* Clock-Ins Table Starts */} +
    +

    Clock-Ins Table

    + + + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {ClockInsData.map((item, i) => { + return item.description === "Total Clock-Ins" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    +
    + {/* Clock-Ins Table Ends */} +
    - {/* Right Column Starts */} -
    -
    - {/* Clock-Ins Chart Starts */} -
    -

    Clock-Ins Chart

    +
    + {/* Clock-Outs Table Starts */} +
    +

    Clock-Outs Table

    -
    - + + + {/* Table columns */} + + + + + + + + + {/* Mapping the data to diplay it as table rows */} + {ClockOutsData.map((item, i) => { + return item.description === "Total Clock-Outs" ? ( + + + + + + ) : ( + + + + + + ) + })} + +

    Description

    Quantity

    Percentages

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    {item.description}

    {item.qty}

    {`${item.pct}%`}

    + {/* Clock-Outs Table Ends */}
    - {/* Clock-Ins Chart Ends */}
    + {/* Left Column Ends */} + + {/* Right Column Starts */} +
    +
    + {/* Clock-Ins Chart Starts */} +
    +

    Clock-Ins Chart

    + +
    + +
    +
    + {/* Clock-Ins Chart Ends */} +
    -
    - {/* Clock-Outs Chart Starts */} -
    -

    Clock-Outs Chart

    +
    + {/* Clock-Outs Chart Starts */} +
    +

    Clock-Outs Chart

    -
    - +
    + +
    + {/* Clock-Outs Chart Ends */}
    - {/* Clock-Outs Chart Ends */}
    + {/* Right Column Ends */}
    - {/* Right Column Ends */}
    ) } diff --git a/src/js/views/metrics/queue/Queue.js b/src/js/views/metrics/queue/Queue.js index 6136324..f536677 100644 --- a/src/js/views/metrics/queue/Queue.js +++ b/src/js/views/metrics/queue/Queue.js @@ -92,18 +92,19 @@ export const Queue = (props) => { {/* Top Column Starts */}
    {/* Controls for the Week Starts */} - {/* Col 1 */} -
    + + {/* Week Announcer*/} +

    {`Week of ${start} - ${end}`}

    - {/* Col 2 */} -
    + {/* Week Selector Tex */} +

    Select a day of the desired week:

    - {/* Calendar/DatePicker */} + {/* Week Selector Tool */}
    {
    - {/* Col 3 */} -
    -
    - -
    - -
    - -
    -
    {/* Controls for the Week Ends */}
    {/* Top Column Ends */} diff --git a/src/js/views/metrics/ratings/Ratings.js b/src/js/views/metrics/ratings/Ratings.js index e73497e..c6881ed 100644 --- a/src/js/views/metrics/ratings/Ratings.js +++ b/src/js/views/metrics/ratings/Ratings.js @@ -145,7 +145,7 @@ export const Ratings = (props) => {

    Employee Ratings Table

    - +
    {/* Table columns */} diff --git a/src/js/views/payroll.js b/src/js/views/payroll.js index f54b9d9..9ab2935 100644 --- a/src/js/views/payroll.js +++ b/src/js/views/payroll.js @@ -4464,7 +4464,7 @@ export class PayrollReport extends Flux.DashView { return (
    From 01ac33304ef6d66f42edb44b387e383085265589 Mon Sep 17 00:00:00 2001 From: paola9896 Date: Fri, 28 Oct 2022 19:23:00 +0000 Subject: [PATCH 10/10] Metrics: year selector fixed --- src/js/PrivateLayout.js | 11 +- src/js/views/metrics/dummy-data/ShiftsData.js | 196 ++++---- .../metrics/general-stats/Hours/Hours.js | 429 +++++++++++++++--- .../general-stats/JobSeekers/JobSeekers.js | 101 +---- .../metrics/general-stats/Shifts/Shifts.js | 44 +- src/js/views/metrics/metrics.js | 17 +- .../views/metrics/punctuality/Punctuality.js | 421 ++++++++++++++--- src/js/views/metrics/queue/Queue.js | 429 +++++++++++++++--- 8 files changed, 1243 insertions(+), 405 deletions(-) diff --git a/src/js/PrivateLayout.js b/src/js/PrivateLayout.js index 71584c8..91f33c6 100644 --- a/src/js/PrivateLayout.js +++ b/src/js/PrivateLayout.js @@ -796,17 +796,12 @@ class PrivateLayout extends Flux.DashView {
  • - {/* {showHideHR && ( + {showHideHR && ( Metrics - )} */} - - - - Metrics - + )}
  • { - let allShifts = props.shifts + /* Variables --------------------------------------------------- */ - // DatePicker --------------------------------------------------------------------------------------------- + /* Putting props into variable */ + let shiftProps = props.shifts; - // Date selected through the DatePicker - const [selectedDate, setSelectedDate] = useState(new Date()); + /* Start of given time period */ + const [start, setStart] = useState(moment().format("MM-DD-YYYY")); - // Monday of X week (default: current week) - const [start, setStart] = useState( - moment().startOf("isoWeek").format("YYYY-MM-DD") + /* End of given time period */ + const [end, setEnd] = useState(moment().format("MM-DD-YYYY")); + + /* Variables to store selected time period */ + const [period, setPeriod] = useState(); + + /* Variables to store selected dates */ + const [day, setDay] = useState(new Date()); + const [week, setWeek] = useState([ + new Date(), + new Date() + ]) + const [month, setMonth] = useState(new Date()); + const [year, setYear] = useState(new Date()); + + /* Variables to store date defaults*/ + const [defaultDay, setDefaultDay] = useState(new Date().toLocaleDateString()); + const [defaultWeek, setDefaultWeek] = useState( + new Date().toLocaleDateString() ); - // Sunday of X week (default: current week) - const [end, setEnd] = useState( - moment().endOf("isoWeek").format("YYYY-MM-DD") + const [defaultMonth, setDefaultMonth] = useState( + new Date().toLocaleDateString("en-us", { month: "long", year: "numeric" }) ); - // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- - const filterShifts = () => { + const [defaultYear, setDefaultYear] = useState(new Date().getFullYear()); - // Array for filtered shifts - let filteredShifts = []; + /* Handlers --------------------------------------------------- */ - // Keeping shifts that exist within the selected dates - allShifts.forEach((shift) => { - let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); - let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); - - if ( - shiftStart >= start && - shiftStart <= end && - shiftEnd >= start && - shiftEnd <= end - ) { - filteredShifts.push(shift); + /* Day Handler */ + const handleDay = (day) => { + setDay(day); + setDefaultDay(day); + }; + + /* Week Handler */ + const handleWeek = (week) => { + setWeek(week); + setDefaultWeek(week); + }; + + /* Month Handler */ + const handleMonth = (month) => { + setMonth(month); + setDefaultMonth(month); + }; + + /* Year Handler */ + const handleYear = (year) => { + setYear(year); + setDefaultYear(year); + }; + + /* Calculate start and end of day */ + const startEndOfDay = () => { + // Transforming day value into string + let dayAsString = defaultDay.toString(); + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const dayFormatted = dayAsString.split(search).join(replaceWith); + + // Setting start and end + setStart(dayFormatted); + setEnd(dayFormatted); + }; + + /* Calculate start and end of week */ + const startEndOfWeek = () => { + // Transforming week value into string + let weekAsString = defaultWeek.toString(); + + let weeks = weekAsString.split(","); + + let endOfWeekPlaceholder = moment().isoWeekday(7).format("MM-DD-YYYY"); + + if (weeks.length === 1) { + weeks.push(endOfWeekPlaceholder); + } + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const weekStart = weeks[0].split(search).join(replaceWith); + const weekEnd = weeks[1].split(search).join(replaceWith); + + let weekStartF = moment(weekEnd).isoWeekday(1).format("MM-DD-YYYY"); + let weekEndF = moment(weekEnd).isoWeekday(7).format("MM-DD-YYYY"); + + // Setting start and end + setStart(weekStartF); + setEnd(weekEndF); + }; + + /* Calculate start and end of month */ + const startEndOfMonth = () => { + // Transforming month value into string + let monthAsString = defaultMonth.toString(); + + // Listing all months by number + const months = [ + { name: "January", number: "01" }, + { name: "February", number: "02" }, + { name: "March", number: "03" }, + { name: "April", number: "04" }, + { name: "May", number: "05" }, + { name: "June", number: "06" }, + { name: "July", number: "07" }, + { name: "August", number: "08" }, + { name: "September", number: "09" }, + { name: "October", number: "10" }, + { name: "November", number: "11" }, + { name: "December", number: "12" } + ]; + + // Determining year (number) of month value + let yearOfMonth = monthAsString.slice(-4); + + // Determining month (number) of month value + let monthOfMonth = ""; + + months.forEach((eachMonth) => { + if (monthAsString.includes(eachMonth.name)) { + monthOfMonth += eachMonth.number; } }); - // Returning filtered shifts - return filteredShifts; + // Completing start of month + let completeMonthStart = `${yearOfMonth}-${monthOfMonth}-01`; + + // Setting up start + let startOfMonth = moment(completeMonthStart) + .startOf("month") + .format("MM-DD-YYYY"); + setStart(startOfMonth); + + // Setting up end + let endOfMonth = moment(startOfMonth).endOf("month").format("MM-DD-YYYY"); + setEnd(endOfMonth); }; - // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- + /* Calculate start and end of year */ + const startEndOfYear = () => { + // Transforming year value into string + let yearAsString = defaultYear.toString() + + // Calculating start and end + let startOfYear = moment(`01-01-${yearAsString}`).format("MM-DD-YYYY") + let endOfYear = moment(`12-31-${yearAsString}`).format("MM-DD-YYYY") + + // Setting up start and end + setStart(startOfYear) + setEnd(endOfYear) + }; + + /* + Variables with the current start and end values + of day, week month, and year + */ + let currentDay = moment().format("MM-DD-YYYY"); + let currentWeekStart = moment().isoWeekday(1).format("MM-DD-YYYY"); + let currentWeekEnd = moment().isoWeekday(7).format("MM-DD-YYYY"); + let currentMonthStart = moment().startOf("month").format("MM-DD-YYYY"); + let currentMonthEnd = moment().endOf("month").format("MM-DD-YYYY"); + let currentYearStart = moment().startOf("year").format("MM-DD-YYYY"); + let currentYearEnd = moment().endOf("year").format("MM-DD-YYYY"); + + /* + UseEffect for the first render of + the page + */ + useEffect(() => { + setPeriod("Day"); + setStart(currentDay); + setEnd(currentDay); + }, []); + + /* + Use effects that get triggered + when the period value changes + */ + useEffect(() => { + if (period === "Day") { + setStart(currentDay); + setEnd(currentDay); + } else if (period === "Week") { + setStart(currentWeekStart); + setEnd(currentWeekEnd); + } else if (period === "Month") { + setStart(currentMonthStart); + setEnd(currentMonthEnd); + } else if (period === "Year") { + setStart(currentYearStart); + setEnd(currentYearEnd); + } else { + null; + } + }, [period]); + + /* + Use effects that get triggered + when the default values change + */ + useEffect(() => { + startEndOfDay(); + }, [defaultDay]); + + useEffect(() => { + startEndOfWeek(); + }, [defaultWeek]); useEffect(() => { + startEndOfMonth(); + }, [defaultMonth]); - // Setting up the new Monday - let formattedStart = moment(selectedDate) - .startOf("isoWeek") - .format("YYYY-MM-DD"); + useEffect(() => { + startEndOfYear(); + }, [defaultYear]); - setStart(formattedStart); + /* + Function that filters shifts based on the + start and end of the selected date + */ + const filterShifts = () => { - // Setting up the new Sunday - let formattedEnd = moment(selectedDate) - .endOf("isoWeek") - .format("YYYY-MM-DD"); + // Array for filtered shifts + let filteredShifts = []; - setEnd(formattedEnd); + // Keeping shifts that exist within the selected dates + shiftProps?.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("MM-DD-YYYY"); + let shiftEnd = moment(shift.ending_at).format("MM-DD-YYYY"); + + let shiftYear = shiftStart.slice(-4); + let startAndEndYear = start.slice(-4); + + if (shiftYear === startAndEndYear) { + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + } + }); - }, [selectedDate]); + // Returning filtered shifts + return filteredShifts; + }; - let newShifts = filterShifts() + // Capturing filtered shifts + let shiftsFiltered = filterShifts(); - // Setting up main data source - let HoursData = HoursDataGenerator(newShifts) + // Setting up main data source for chart and table + let HoursData = HoursDataGenerator(shiftsFiltered) // Data for pie chart ------------------------------------------------------------------------------------- @@ -108,28 +309,134 @@ export const Hours = (props) => { return (
    -
    - {/* Week Announcer */} -
    -

    {`Week of ${start} - ${end}`}

    +
    + {/* Time period Announcer */} +
    +

    Start: {start}

    +

    End: {end}

    - {/* Week Selector Text */} -
    -

    Select a day of the desired week:

    + {/* Dropdown */} +
    +
    + +
    + + + + +
    +
    - {/* Week Selector Tool */} -
    - setSelectedDate(date)} - /> +
    + {/* DatePicker Conditional Rendering Starts */} + <> + {period === "Day" ? ( +
    +
    +

    {`${period}: ${defaultDay}`}

    +
    + + +
    + ) : period === "Week" ? ( +
    +
    +

    {`${period}: ${defaultWeek}`}

    +
    + + +
    + ) : period === "Month" ? ( +
    +
    +

    {`${period}: ${defaultMonth}`}

    +
    + + +
    + ) : period === "Year" ? ( +
    +
    +

    {`${period}: ${defaultYear}`}

    +
    + + +
    + ) : null} + + {/* DatePicker Conditional Rendering Ends */}
    -
    +
    {/* Left Column Starts */}
    diff --git a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js index 20d1a65..79cb33a 100644 --- a/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js +++ b/src/js/views/metrics/general-stats/JobSeekers/JobSeekers.js @@ -1,9 +1,6 @@ import React, { useEffect, useState } from "react"; import { PieChart, BarChart } from '../../charts'; import { JobSeekersDataGenerator, NewJobSeekersDataGenerator } from "./JobSeekersData"; -import "react-datepicker/dist/react-datepicker.css"; -import DatePicker from "react-datepicker"; -import moment from "moment"; /** * @function @@ -18,17 +15,19 @@ import moment from "moment"; */ export const JobSeekers = (props) => { - // Use state to hold list of workers and list of shifts + /* Variables --------------------------------------------------- */ + + /* Use state to hold list of workers and list of shifts */ const [workersList, setWorkersList] = useState([]) const [shifsList, setShiftsList] = useState([]) - // Receiving the props that contain the lists we need + /* Receiving the props that contain the lists we need */ const handleProps = async () => { - // Catching the props when they arrive + /* Catching the props when they arrive */ let propsObj = await props - // Checking length of lists before save them + /* Checking length of lists before save them */ if (propsObj.workers.length > 0) { // Saving list of workers setWorkersList(propsObj.workers) @@ -40,77 +39,15 @@ export const JobSeekers = (props) => { } } - // Triggering handleProps when props change/arrive + /* Triggering handleProps when props change/arrive */ useEffect(() => { handleProps() }, [props]) - - // DatePicker ------------------------------------------------------------------------------------------------- - - // Date selected through the DatePicker - const [selectedDate, setSelectedDate] = useState(new Date()); - - // Monday of X week (default: current week) - const [start, setStart] = useState( - moment().startOf("isoWeek").format("YYYY-MM-DD") - ); - - // Sunday of X week (default: current week) - const [end, setEnd] = useState( - moment().endOf("isoWeek").format("YYYY-MM-DD") - ); - - // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- - const filterShifts = () => { - - // Array for filtered shifts - let filteredShifts = []; - - // Keeping shifts that exist within the selected dates - shifsList?.forEach((shift) => { - let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); - let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); - - if ( - shiftStart >= start && - shiftStart <= end && - shiftEnd >= start && - shiftEnd <= end - ) { - filteredShifts.push(shift); - } - }); - - // Returning filtered shifts - return filteredShifts; - }; - - // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- - - useEffect(() => { - - // Setting up the new Monday - let formattedStart = moment(selectedDate) - .startOf("isoWeek") - .format("YYYY-MM-DD"); - - setStart(formattedStart); - - // Setting up the new Sunday - let formattedEnd = moment(selectedDate) - .endOf("isoWeek") - .format("YYYY-MM-DD"); - - setEnd(formattedEnd); - - }, [selectedDate]); - + if (workersList.length > 0) { - let specialShifts = filterShifts() - // Setting up main data sources - let JobSeekersData = JobSeekersDataGenerator(specialShifts, workersList) + let JobSeekersData = JobSeekersDataGenerator(shifsList, workersList) let NewJobSeekersData = NewJobSeekersDataGenerator(workersList) // Data for pie chart ------------------------------------------------------------------------------------- @@ -155,23 +92,15 @@ export const JobSeekers = (props) => { return (
    -
    -
    -

    Select a day of the desired week:

    -
    - - {/* Calendar/DatePicker */} -
    - setSelectedDate(date)} - /> -
    +
    +
    -
    - +
    {/* Left Column Starts */}
    diff --git a/src/js/views/metrics/general-stats/Shifts/Shifts.js b/src/js/views/metrics/general-stats/Shifts/Shifts.js index 2c95f0a..23a21ad 100644 --- a/src/js/views/metrics/general-stats/Shifts/Shifts.js +++ b/src/js/views/metrics/general-stats/Shifts/Shifts.js @@ -8,13 +8,14 @@ import moment from "moment"; /** * @function * @description Creates a page with a table and a graph of all the shift statuses. - * @since 09.29.22 by Paola Sanchez + * @since 10.28.22 by Paola Sanchez * @author Paola Sanchez - * @requires ShiftsDataGenerator * @requires BarChart + * @requires ShiftsDataGenerator * @param {object} props - Contains an array of all the shifts. */ export const Shifts = (props) => { + /* Variables --------------------------------------------------- */ /* Putting props into variable */ @@ -22,6 +23,7 @@ export const Shifts = (props) => { /* Start of given time period */ const [start, setStart] = useState(moment().format("MM-DD-YYYY")); + /* End of given time period */ const [end, setEnd] = useState(moment().format("MM-DD-YYYY")); @@ -42,9 +44,11 @@ export const Shifts = (props) => { const [defaultWeek, setDefaultWeek] = useState( new Date().toLocaleDateString() ); + const [defaultMonth, setDefaultMonth] = useState( new Date().toLocaleDateString("en-us", { month: "long", year: "numeric" }) ); + const [defaultYear, setDefaultYear] = useState(new Date().getFullYear()); /* Handlers --------------------------------------------------- */ @@ -165,16 +169,15 @@ export const Shifts = (props) => { /* Calculate start and end of year */ const startEndOfYear = () => { // Transforming year value into string - let yearAsString = defaultYear.toString(); + let yearAsString = defaultYear.toString() - // Setting up start - let startOfYear = `${yearAsString}-01-01` - let startOfYearF = moment(startOfYear).startOf("year").format("MM-DD-YYYY"); - setStart(startOfYearF) - - // Setting up end - let endOfYearF = moment(startOfYearF).endOf("year").format("MM-DD-YYYY"); - setEnd(endOfYearF) + // Calculating start and end + let startOfYear = moment(`01-01-${yearAsString}`).format("MM-DD-YYYY") + let endOfYear = moment(`12-31-${yearAsString}`).format("MM-DD-YYYY") + + // Setting up start and end + setStart(startOfYear) + setEnd(endOfYear) }; /* @@ -255,13 +258,18 @@ export const Shifts = (props) => { let shiftStart = moment(shift.starting_at).format("MM-DD-YYYY"); let shiftEnd = moment(shift.ending_at).format("MM-DD-YYYY"); - if ( - shiftStart >= start && - shiftStart <= end && - shiftEnd >= start && - shiftEnd <= end - ) { - filteredShifts.push(shift); + let shiftYear = shiftStart.slice(-4); + let startAndEndYear = start.slice(-4); + + if (shiftYear === startAndEndYear) { + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } } }); diff --git a/src/js/views/metrics/metrics.js b/src/js/views/metrics/metrics.js index 9477c2a..4f5ba3a 100644 --- a/src/js/views/metrics/metrics.js +++ b/src/js/views/metrics/metrics.js @@ -9,8 +9,8 @@ import { GeneralStats } from "./general-stats/GeneralStats"; import { store, search } from "../../actions"; -import {WorkersData} from "./dummy-data/WorkersData"; -import {ShiftsData} from "./dummy-data/ShiftsData"; +// import {WorkersData} from "./dummy-data/WorkersData"; +// import {ShiftsData} from "./dummy-data/ShiftsData"; /** * @description Creates the view for Metrics page, which renders 4 tabs with different components being called inside each one. @@ -113,9 +113,8 @@ export class Metrics extends Flux.DashView { let listOfShifts = this.state.allShifts; // Temporal dummy data - - let dummyShifts = ShiftsData - let dummyWorkers = WorkersData + // let dummyShifts = ShiftsData + // let dummyWorkers = WorkersData // --------------------------------------------- // Filtering expired shifts @@ -156,25 +155,25 @@ export class Metrics extends Flux.DashView { > {/* General Stats Tab Starts */} {/* General Stats Tab Ends */} {/* Punctuality Tab Starts */} {/* Punctuality Tab Ends */} {/* Ratings Tab Starts */} {/* Ratings Tab Ends */} {/* Queue Tab Starts */} {/* Queue Tab Ends */}
    diff --git a/src/js/views/metrics/punctuality/Punctuality.js b/src/js/views/metrics/punctuality/Punctuality.js index dee211f..fb2518d 100644 --- a/src/js/views/metrics/punctuality/Punctuality.js +++ b/src/js/views/metrics/punctuality/Punctuality.js @@ -1,8 +1,8 @@ import React, { useState, useEffect } from "react"; import { PieChart } from '../charts'; import { ClockInsDataGenerator, ClockOutsDataGenerator } from "./PunctualityData"; -import "react-datepicker/dist/react-datepicker.css"; -import DatePicker from "react-datepicker"; +import weekends from "react-multi-date-picker/plugins/highlight_weekends" +import DatePicker from "react-multi-date-picker"; import moment from "moment"; /** @@ -17,39 +17,261 @@ import moment from "moment"; */ export const Punctuality = (props) => { - // Putting props into variable + + /* Variables --------------------------------------------------- */ + + /* Putting props into variable */ let shiftProps = props.shifts; - // Date selected through the DatePicker - const [selectedDate, setSelectedDate] = useState(new Date()); + /* Start of given time period */ + const [start, setStart] = useState(moment().format("MM-DD-YYYY")); + + /* End of given time period */ + const [end, setEnd] = useState(moment().format("MM-DD-YYYY")); + + /* Variables to store selected time period */ + const [period, setPeriod] = useState(); - // Monday of X week (default: current week) - const [start, setStart] = useState( - moment().startOf("isoWeek").format("YYYY-MM-DD") + /* Variables to store selected dates */ + const [day, setDay] = useState(new Date()); + const [week, setWeek] = useState([ + new Date(), + new Date() + ]) + const [month, setMonth] = useState(new Date()); + const [year, setYear] = useState(new Date()); + + /* Variables to store date defaults*/ + const [defaultDay, setDefaultDay] = useState(new Date().toLocaleDateString()); + const [defaultWeek, setDefaultWeek] = useState( + new Date().toLocaleDateString() ); - // Sunday of X week (default: current week) - const [end, setEnd] = useState( - moment().endOf("isoWeek").format("YYYY-MM-DD") + const [defaultMonth, setDefaultMonth] = useState( + new Date().toLocaleDateString("en-us", { month: "long", year: "numeric" }) ); - // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- + const [defaultYear, setDefaultYear] = useState(new Date().getFullYear()); + + /* Handlers --------------------------------------------------- */ + + /* Day Handler */ + const handleDay = (day) => { + setDay(day); + setDefaultDay(day); + }; + + /* Week Handler */ + const handleWeek = (week) => { + setWeek(week); + setDefaultWeek(week); + }; + + /* Month Handler */ + const handleMonth = (month) => { + setMonth(month); + setDefaultMonth(month); + }; + + /* Year Handler */ + const handleYear = (year) => { + setYear(year); + setDefaultYear(year); + }; + + /* Calculate start and end of day */ + const startEndOfDay = () => { + // Transforming day value into string + let dayAsString = defaultDay.toString(); + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const dayFormatted = dayAsString.split(search).join(replaceWith); + + // Setting start and end + setStart(dayFormatted); + setEnd(dayFormatted); + }; + + /* Calculate start and end of week */ + const startEndOfWeek = () => { + // Transforming week value into string + let weekAsString = defaultWeek.toString(); + + let weeks = weekAsString.split(","); + + let endOfWeekPlaceholder = moment().isoWeekday(7).format("MM-DD-YYYY"); + + if (weeks.length === 1) { + weeks.push(endOfWeekPlaceholder); + } + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const weekStart = weeks[0].split(search).join(replaceWith); + const weekEnd = weeks[1].split(search).join(replaceWith); + + let weekStartF = moment(weekEnd).isoWeekday(1).format("MM-DD-YYYY"); + let weekEndF = moment(weekEnd).isoWeekday(7).format("MM-DD-YYYY"); + + // Setting start and end + setStart(weekStartF); + setEnd(weekEndF); + }; + + /* Calculate start and end of month */ + const startEndOfMonth = () => { + // Transforming month value into string + let monthAsString = defaultMonth.toString(); + + // Listing all months by number + const months = [ + { name: "January", number: "01" }, + { name: "February", number: "02" }, + { name: "March", number: "03" }, + { name: "April", number: "04" }, + { name: "May", number: "05" }, + { name: "June", number: "06" }, + { name: "July", number: "07" }, + { name: "August", number: "08" }, + { name: "September", number: "09" }, + { name: "October", number: "10" }, + { name: "November", number: "11" }, + { name: "December", number: "12" } + ]; + + // Determining year (number) of month value + let yearOfMonth = monthAsString.slice(-4); + + // Determining month (number) of month value + let monthOfMonth = ""; + + months.forEach((eachMonth) => { + if (monthAsString.includes(eachMonth.name)) { + monthOfMonth += eachMonth.number; + } + }); + + // Completing start of month + let completeMonthStart = `${yearOfMonth}-${monthOfMonth}-01`; + + // Setting up start + let startOfMonth = moment(completeMonthStart) + .startOf("month") + .format("MM-DD-YYYY"); + setStart(startOfMonth); + + // Setting up end + let endOfMonth = moment(startOfMonth).endOf("month").format("MM-DD-YYYY"); + setEnd(endOfMonth); + }; + + /* Calculate start and end of year */ + const startEndOfYear = () => { + // Transforming year value into string + let yearAsString = defaultYear.toString() + + // Calculating start and end + let startOfYear = moment(`01-01-${yearAsString}`).format("MM-DD-YYYY") + let endOfYear = moment(`12-31-${yearAsString}`).format("MM-DD-YYYY") + + // Setting up start and end + setStart(startOfYear) + setEnd(endOfYear) + }; + + /* + Variables with the current start and end values + of day, week month, and year + */ + let currentDay = moment().format("MM-DD-YYYY"); + let currentWeekStart = moment().isoWeekday(1).format("MM-DD-YYYY"); + let currentWeekEnd = moment().isoWeekday(7).format("MM-DD-YYYY"); + let currentMonthStart = moment().startOf("month").format("MM-DD-YYYY"); + let currentMonthEnd = moment().endOf("month").format("MM-DD-YYYY"); + let currentYearStart = moment().startOf("year").format("MM-DD-YYYY"); + let currentYearEnd = moment().endOf("year").format("MM-DD-YYYY"); + + /* + UseEffect for the first render of + the page + */ + useEffect(() => { + setPeriod("Day"); + setStart(currentDay); + setEnd(currentDay); + }, []); + + /* + Use effects that get triggered + when the period value changes + */ + useEffect(() => { + if (period === "Day") { + setStart(currentDay); + setEnd(currentDay); + } else if (period === "Week") { + setStart(currentWeekStart); + setEnd(currentWeekEnd); + } else if (period === "Month") { + setStart(currentMonthStart); + setEnd(currentMonthEnd); + } else if (period === "Year") { + setStart(currentYearStart); + setEnd(currentYearEnd); + } else { + null; + } + }, [period]); + + /* + Use effects that get triggered + when the default values change + */ + useEffect(() => { + startEndOfDay(); + }, [defaultDay]); + + useEffect(() => { + startEndOfWeek(); + }, [defaultWeek]); + + useEffect(() => { + startEndOfMonth(); + }, [defaultMonth]); + + useEffect(() => { + startEndOfYear(); + }, [defaultYear]); + + /* + Function that filters shifts based on the + start and end of the selected date + */ const filterShifts = () => { + // Array for filtered shifts let filteredShifts = []; // Keeping shifts that exist within the selected dates shiftProps?.forEach((shift) => { - let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); - let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); - - if ( - shiftStart >= start && - shiftStart <= end && - shiftEnd >= start && - shiftEnd <= end - ) { - filteredShifts.push(shift); + let shiftStart = moment(shift.starting_at).format("MM-DD-YYYY"); + let shiftEnd = moment(shift.ending_at).format("MM-DD-YYYY"); + + let shiftYear = shiftStart.slice(-4); + let startAndEndYear = start.slice(-4); + + if (shiftYear === startAndEndYear) { + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } } }); @@ -57,28 +279,10 @@ export const Punctuality = (props) => { return filteredShifts; }; - // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- - - useEffect(() => { - // Setting up the new Monday - let formattedStart = moment(selectedDate) - .startOf("isoWeek") - .format("YYYY-MM-DD"); - - setStart(formattedStart); - - // Setting up the new Sunday - let formattedEnd = moment(selectedDate) - .endOf("isoWeek") - .format("YYYY-MM-DD"); - - setEnd(formattedEnd); - }, [selectedDate]); - // Capturing filtered shifts let shiftsFiltered = filterShifts(); - // Setting up main data sources + // Setting up main data source for chart and table let ClockInsData = ClockInsDataGenerator(shiftsFiltered); let ClockOutsData = ClockOutsDataGenerator(shiftsFiltered); @@ -135,27 +339,134 @@ export const Punctuality = (props) => { return (
    -
    - {/* Week Announcer */} -
    -

    {`Week of ${start} - ${end}`}

    +
    + {/* Time period Announcer */} +
    +

    Start: {start}

    +

    End: {end}

    - {/* Week Selector Text */} -
    -

    Select a day of the desired week:

    + {/* Dropdown */} +
    +
    + +
    + + + + +
    +
    - {/* Week Selector Tool */} -
    - setSelectedDate(date)} - /> +
    + {/* DatePicker Conditional Rendering Starts */} + <> + {period === "Day" ? ( +
    +
    +

    {`${period}: ${defaultDay}`}

    +
    + + +
    + ) : period === "Week" ? ( +
    +
    +

    {`${period}: ${defaultWeek}`}

    +
    + + +
    + ) : period === "Month" ? ( +
    +
    +

    {`${period}: ${defaultMonth}`}

    +
    + + +
    + ) : period === "Year" ? ( +
    +
    +

    {`${period}: ${defaultYear}`}

    +
    + + +
    + ) : null} + + {/* DatePicker Conditional Rendering Ends */}
    -
    + +
    {/* Left Column Starts */}
    diff --git a/src/js/views/metrics/queue/Queue.js b/src/js/views/metrics/queue/Queue.js index f536677..8410b25 100644 --- a/src/js/views/metrics/queue/Queue.js +++ b/src/js/views/metrics/queue/Queue.js @@ -1,88 +1,280 @@ import React, { useState, useEffect } from "react"; -import "react-datepicker/dist/react-datepicker.css"; -import DatePicker from "react-datepicker"; -import moment from "moment"; - import { QueueData } from "./QueueData"; -import { Button } from "../../../components/index"; +import weekends from "react-multi-date-picker/plugins/highlight_weekends" +import DatePicker from "react-multi-date-picker"; +import moment from "moment"; /** * @function * @description Creates a page with a DatePicker and table of all employees with their worked/scheduled hours. * @since 09.29.22 by Paola Sanchez * @author Paola Sanchez - * @requires moment - * @requires DatePicker * @requires QueueData - * @requires Button * @param {object} props - Contains an array of all shifts, and an array of all workers. */ export const Queue = (props) => { - // Setting up my variables --------------------------------------------------------------------------------- + /* Variables --------------------------------------------------- */ + + /* Putting props into variables */ + let shiftProps = props.shifts; + let workerProps = props.workers; + + /* Start of given time period */ + const [start, setStart] = useState(moment().format("MM-DD-YYYY")); - // Setting up main data source - let allShifts = props.shifts; - let workers = props.workers; + /* End of given time period */ + const [end, setEnd] = useState(moment().format("MM-DD-YYYY")); - // Date selected through the DatePicker - const [selectedDate, setSelectedDate] = useState(new Date()); + /* Variables to store selected time period */ + const [period, setPeriod] = useState(); - // Monday of X week (default: current week) - const [start, setStart] = useState( - moment().startOf("isoWeek").format("YYYY-MM-DD") + /* Variables to store selected dates */ + const [day, setDay] = useState(new Date()); + const [week, setWeek] = useState([ + new Date(), + new Date() + ]) + const [month, setMonth] = useState(new Date()); + const [year, setYear] = useState(new Date()); + + /* Variables to store date defaults*/ + const [defaultDay, setDefaultDay] = useState(new Date().toLocaleDateString()); + const [defaultWeek, setDefaultWeek] = useState( + new Date().toLocaleDateString() ); - // Sunday of X week (default: current week) - const [end, setEnd] = useState( - moment().endOf("isoWeek").format("YYYY-MM-DD") + const [defaultMonth, setDefaultMonth] = useState( + new Date().toLocaleDateString("en-us", { month: "long", year: "numeric" }) ); - // Function that filters shifts based on the Monday and Sunday of the selected date -------------------------- + const [defaultYear, setDefaultYear] = useState(new Date().getFullYear()); - const filterShifts = () => { + /* Handlers --------------------------------------------------- */ - // Array for filtered shifts - let filteredShifts = []; + /* Day Handler */ + const handleDay = (day) => { + setDay(day); + setDefaultDay(day); + }; - // Keeping shifts that exist within the selected dates - allShifts.forEach((shift) => { - let shiftStart = moment(shift.starting_at).format("YYYY-MM-DD"); - let shiftEnd = moment(shift.ending_at).format("YYYY-MM-DD"); - - if ( - shiftStart >= start && - shiftStart <= end && - shiftEnd >= start && - shiftEnd <= end - ) { - filteredShifts.push(shift); + /* Week Handler */ + const handleWeek = (week) => { + setWeek(week); + setDefaultWeek(week); + }; + + /* Month Handler */ + const handleMonth = (month) => { + setMonth(month); + setDefaultMonth(month); + }; + + /* Year Handler */ + const handleYear = (year) => { + setYear(year); + setDefaultYear(year); + }; + + /* Calculate start and end of day */ + const startEndOfDay = () => { + // Transforming day value into string + let dayAsString = defaultDay.toString(); + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const dayFormatted = dayAsString.split(search).join(replaceWith); + + // Setting start and end + setStart(dayFormatted); + setEnd(dayFormatted); + }; + + /* Calculate start and end of week */ + const startEndOfWeek = () => { + // Transforming week value into string + let weekAsString = defaultWeek.toString(); + + let weeks = weekAsString.split(","); + + let endOfWeekPlaceholder = moment().isoWeekday(7).format("MM-DD-YYYY"); + + if (weeks.length === 1) { + weeks.push(endOfWeekPlaceholder); + } + + // Replacing "/" with "-" + const search = "/"; + const replaceWith = "-"; + const weekStart = weeks[0].split(search).join(replaceWith); + const weekEnd = weeks[1].split(search).join(replaceWith); + + let weekStartF = moment(weekEnd).isoWeekday(1).format("MM-DD-YYYY"); + let weekEndF = moment(weekEnd).isoWeekday(7).format("MM-DD-YYYY"); + + // Setting start and end + setStart(weekStartF); + setEnd(weekEndF); + }; + + /* Calculate start and end of month */ + const startEndOfMonth = () => { + // Transforming month value into string + let monthAsString = defaultMonth.toString(); + + // Listing all months by number + const months = [ + { name: "January", number: "01" }, + { name: "February", number: "02" }, + { name: "March", number: "03" }, + { name: "April", number: "04" }, + { name: "May", number: "05" }, + { name: "June", number: "06" }, + { name: "July", number: "07" }, + { name: "August", number: "08" }, + { name: "September", number: "09" }, + { name: "October", number: "10" }, + { name: "November", number: "11" }, + { name: "December", number: "12" } + ]; + + // Determining year (number) of month value + let yearOfMonth = monthAsString.slice(-4); + + // Determining month (number) of month value + let monthOfMonth = ""; + + months.forEach((eachMonth) => { + if (monthAsString.includes(eachMonth.name)) { + monthOfMonth += eachMonth.number; } }); - // Returning filtered shifts - return filteredShifts; + // Completing start of month + let completeMonthStart = `${yearOfMonth}-${monthOfMonth}-01`; + + // Setting up start + let startOfMonth = moment(completeMonthStart) + .startOf("month") + .format("MM-DD-YYYY"); + setStart(startOfMonth); + + // Setting up end + let endOfMonth = moment(startOfMonth).endOf("month").format("MM-DD-YYYY"); + setEnd(endOfMonth); }; - // UseEffect to update Mondays and Sundays when a new date is selected -------------------------------------- + /* Calculate start and end of year */ + const startEndOfYear = () => { + // Transforming year value into string + let yearAsString = defaultYear.toString() + + // Calculating start and end + let startOfYear = moment(`01-01-${yearAsString}`).format("MM-DD-YYYY") + let endOfYear = moment(`12-31-${yearAsString}`).format("MM-DD-YYYY") + + // Setting up start and end + setStart(startOfYear) + setEnd(endOfYear) + }; + /* + Variables with the current start and end values + of day, week month, and year + */ + let currentDay = moment().format("MM-DD-YYYY"); + let currentWeekStart = moment().isoWeekday(1).format("MM-DD-YYYY"); + let currentWeekEnd = moment().isoWeekday(7).format("MM-DD-YYYY"); + let currentMonthStart = moment().startOf("month").format("MM-DD-YYYY"); + let currentMonthEnd = moment().endOf("month").format("MM-DD-YYYY"); + let currentYearStart = moment().startOf("year").format("MM-DD-YYYY"); + let currentYearEnd = moment().endOf("year").format("MM-DD-YYYY"); + + /* + UseEffect for the first render of + the page + */ + useEffect(() => { + setPeriod("Day"); + setStart(currentDay); + setEnd(currentDay); + }, []); + + /* + Use effects that get triggered + when the period value changes + */ useEffect(() => { + if (period === "Day") { + setStart(currentDay); + setEnd(currentDay); + } else if (period === "Week") { + setStart(currentWeekStart); + setEnd(currentWeekEnd); + } else if (period === "Month") { + setStart(currentMonthStart); + setEnd(currentMonthEnd); + } else if (period === "Year") { + setStart(currentYearStart); + setEnd(currentYearEnd); + } else { + null; + } + }, [period]); + + /* + Use effects that get triggered + when the default values change + */ + useEffect(() => { + startEndOfDay(); + }, [defaultDay]); + + useEffect(() => { + startEndOfWeek(); + }, [defaultWeek]); + + useEffect(() => { + startEndOfMonth(); + }, [defaultMonth]); + + useEffect(() => { + startEndOfYear(); + }, [defaultYear]); + + /* + Function that filters shifts based on the + start and end of the selected date + */ + const filterShifts = () => { - // Setting up the new Monday - let formattedStart = moment(selectedDate) - .startOf("isoWeek") - .format("YYYY-MM-DD"); + // Array for filtered shifts + let filteredShifts = []; - setStart(formattedStart); + // Keeping shifts that exist within the selected dates + shiftProps?.forEach((shift) => { + let shiftStart = moment(shift.starting_at).format("MM-DD-YYYY"); + let shiftEnd = moment(shift.ending_at).format("MM-DD-YYYY"); - // Setting up the new Sunday - let formattedEnd = moment(selectedDate) - .endOf("isoWeek") - .format("YYYY-MM-DD"); + let shiftYear = shiftStart.slice(-4); + let startAndEndYear = start.slice(-4); - setEnd(formattedEnd); + if (shiftYear === startAndEndYear) { + if ( + shiftStart >= start && + shiftStart <= end && + shiftEnd >= start && + shiftEnd <= end + ) { + filteredShifts.push(shift); + } + } + }); - }, [selectedDate]); + // Returning filtered shifts + return filteredShifts; + }; // Return ---------------------------------------------------------------------------------------------------- @@ -90,37 +282,134 @@ export const Queue = (props) => {

    Table of Employee Hours

    {/* Top Column Starts */} -
    - {/* Controls for the Week Starts */} - - {/* Week Announcer*/} -
    -

    {`Week of ${start} - ${end}`}

    +
    + {/* Time period Announcer */} +
    +

    Start: {start}

    +

    End: {end}

    - {/* Week Selector Tex */} -
    -
    -

    Select a day of the desired week:

    -
    - - {/* Week Selector Tool */} -
    - setSelectedDate(date)} - /> + {/* Dropdown */} +
    +
    + +
    + + + + +
    - {/* Controls for the Week Ends */} +
    + {/* DatePicker Conditional Rendering Starts */} + <> + {period === "Day" ? ( +
    +
    +

    {`${period}: ${defaultDay}`}

    +
    + + +
    + ) : period === "Week" ? ( +
    +
    +

    {`${period}: ${defaultWeek}`}

    +
    + + +
    + ) : period === "Month" ? ( +
    +
    +

    {`${period}: ${defaultMonth}`}

    +
    + + +
    + ) : period === "Year" ? ( +
    +
    +

    {`${period}: ${defaultYear}`}

    +
    + + +
    + ) : null} + + {/* DatePicker Conditional Rendering Ends */} +
    {/* Top Column Ends */} {/* Bottom Column Starts */} {/* Table of Employees Starts */}
    - {workers?.map((singleWorker, i) => { + {workerProps?.map((singleWorker, i) => { return (
  • )CdQ3F!eKyF|9D2V!F-rhUpJ8J+l7g0OBBVM#THTI&O8)>EGR%u6Gd9D9pm5!S}%&6DxW}^f|7=Y zPoO3(pTZY#?(7(|!5}5Nn!D%DotZmlW)?smSMcEE<^aT$6gw#LlwubPI9BYTffL0! zyu-EPCnz{Y#ZR&1d{F!hr_NW!&#~mXis$jseXDo@U)-kR7sMBeUt-T&RQw9By@BF9 z3f?cpmw4m-R{RHncaC**(V--ipJ<~6LkW2fi6RVfh%vcYt9@z>&M0LBSf-Q|Et8wU zCt43_*JB)mHR71wb`K@~5Cizwp{`A2uuJ^_Bcl3k{7ree$8&@l?;^2nagS+NqCDBfkB?pJws=PbK~+A7|2 z{gCDJKI-i%m4LD$n{WIwWR|c+NRy`C1#)1sSBI7FiH6z-QkhY&Q_|%I3exQ zQ`X1M?cZH4^M&BSyr;2z$+^SZUMA*0001Z+HKHROw(}?!13=vX`$@Br+fGR zZ%e`5O6%Txi$Yrz0gF{}p>fY>OnlS0Uevf}oDXW;D{d2gcE<2)oFcV80@g$H)63L{HN*d{8kVzKVW(;E)$9N_%kx5Ku3R9WJbY?JW^G#k0Wdx>E$NBBVtKRLiL?sA*s%w`TdsNz1=+~FRNdB8&+@iBD0 zXFTC4C-8-Cwv(4U=LLQ~^Oa4^rG|OTr5?ItoaPMYxxh`%a*kVU z;HYGAjq6;IY{`*awo0DlOMw(hkrYdb(O28l;MYvSx*ChcQW4f^QL5UdE3HbqvbxB$pfSg`>Cj#;?~00;nMAg}==M6d%RaIhCe zARtS)01i=0um)3FSgr#ump{<1pq_<0a34Kp8x=7I1^|9 literal 0 HcmV?d00001 diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.eot b/jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..6bbc3cf58cb011a6b4bf3cb1612ce212608f7274 GIT binary patch literal 19836 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.woff b/jsdoc-custom-template/static/fonts/OpenSans-Regular-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e231183dce4c7b452afc9e7799586fd285e146f4 GIT binary patch literal 22660 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 ML;wL%uV=LY00kR;TmS$7 literal 0 HcmV?d00001 diff --git a/jsdoc-custom-template/static/scripts/linenumber.js b/jsdoc-custom-template/static/scripts/linenumber.js new file mode 100644 index 0000000..4354785 --- /dev/null +++ b/jsdoc-custom-template/static/scripts/linenumber.js @@ -0,0 +1,25 @@ +/*global document */ +(() => { + const source = document.getElementsByClassName('prettyprint source linenums'); + let i = 0; + let lineNumber = 0; + let lineId; + let lines; + let totalLines; + let anchorHash; + + if (source && source[0]) { + anchorHash = document.location.hash.substring(1); + lines = source[0].getElementsByTagName('li'); + totalLines = lines.length; + + for (; i < totalLines; i++) { + lineNumber++; + lineId = `line${lineNumber}`; + lines[i].id = lineId; + if (lineId === anchorHash) { + lines[i].className += ' selected'; + } + } + } +})(); diff --git a/jsdoc-custom-template/static/scripts/prettify/Apache-License-2.0.txt b/jsdoc-custom-template/static/scripts/prettify/Apache-License-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/jsdoc-custom-template/static/scripts/prettify/Apache-License-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/jsdoc-custom-template/static/scripts/prettify/lang-css.js b/jsdoc-custom-template/static/scripts/prettify/lang-css.js new file mode 100644 index 0000000..041e1f5 --- /dev/null +++ b/jsdoc-custom-template/static/scripts/prettify/lang-css.js @@ -0,0 +1,2 @@ +PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", +/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); diff --git a/jsdoc-custom-template/static/scripts/prettify/prettify.js b/jsdoc-custom-template/static/scripts/prettify/prettify.js new file mode 100644 index 0000000..eef5ad7 --- /dev/null +++ b/jsdoc-custom-template/static/scripts/prettify/prettify.js @@ -0,0 +1,28 @@ +var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; +(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= +[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;ci[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), +l=[],p={},d=0,g=e.length;d=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, +q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, +q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, +"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), +a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} +for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], +"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], +H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], +J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ +I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), +["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", +/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), +["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", +hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p=0){var k=k.match(g),f,b;if(b= +!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p th:last-child { border-right: 1px solid #ddd; } + +.ancestors, .attribs { color: #999; } +.ancestors a, .attribs a +{ + color: #999 !important; + text-decoration: none; +} + +.clear +{ + clear: both; +} + +.important +{ + font-weight: bold; + color: #950B02; +} + +.yes-def { + text-indent: -1000px; +} + +.type-signature { + color: #aaa; +} + +.name, .signature { + font-family: Consolas, Monaco, 'Andale Mono', monospace; +} + +.details { margin-top: 14px; border-left: 2px solid #DDD; } +.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; } +.details dd { margin-left: 70px; } +.details ul { margin: 0; } +.details ul { list-style-type: none; } +.details li { margin-left: 30px; padding-top: 6px; } +.details pre.prettyprint { margin: 0 } +.details .object-value { padding-top: 0; } + +.description { + margin-bottom: 1em; + margin-top: 1em; +} + +.code-caption +{ + font-style: italic; + font-size: 107%; + margin: 0; +} + +.source +{ + border: 1px solid #ddd; + width: 80%; + overflow: auto; +} + +.prettyprint.source { + width: inherit; +} + +.source code +{ + font-size: 100%; + line-height: 18px; + display: block; + padding: 4px 12px; + margin: 0; + background-color: #fff; + color: #4D4E53; +} + +.prettyprint code span.line +{ + display: inline-block; +} + +.prettyprint.linenums +{ + padding-left: 70px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.prettyprint.linenums ol +{ + padding-left: 0; +} + +.prettyprint.linenums li +{ + border-left: 3px #ddd solid; +} + +.prettyprint.linenums li.selected, +.prettyprint.linenums li.selected * +{ + background-color: lightyellow; +} + +.prettyprint.linenums li * +{ + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; +} + +.params .name, .props .name, .name code { + color: #4D4E53; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 100%; +} + +.params td.description > p:first-child, +.props td.description > p:first-child +{ + margin-top: 0; + padding-top: 0; +} + +.params td.description > p:last-child, +.props td.description > p:last-child +{ + margin-bottom: 0; + padding-bottom: 0; +} + +.disabled { + color: #454545; +} diff --git a/jsdoc-custom-template/static/styles/prettify-jsdoc.css b/jsdoc-custom-template/static/styles/prettify-jsdoc.css new file mode 100644 index 0000000..5a2526e --- /dev/null +++ b/jsdoc-custom-template/static/styles/prettify-jsdoc.css @@ -0,0 +1,111 @@ +/* JSDoc prettify.js theme */ + +/* plain text */ +.pln { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* string content */ +.str { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a keyword */ +.kwd { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a comment */ +.com { + font-weight: normal; + font-style: italic; +} + +/* a type name */ +.typ { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a literal value */ +.lit { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* punctuation */ +.pun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp open bracket */ +.opn { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* lisp close bracket */ +.clo { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a markup tag name */ +.tag { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute name */ +.atn { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a markup attribute value */ +.atv { + color: #006400; + font-weight: normal; + font-style: normal; +} + +/* a declaration */ +.dec { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* a variable name */ +.var { + color: #000000; + font-weight: normal; + font-style: normal; +} + +/* a function name */ +.fun { + color: #000000; + font-weight: bold; + font-style: normal; +} + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; +} diff --git a/jsdoc-custom-template/static/styles/prettify-tomorrow.css b/jsdoc-custom-template/static/styles/prettify-tomorrow.css new file mode 100644 index 0000000..b6f92a7 --- /dev/null +++ b/jsdoc-custom-template/static/styles/prettify-tomorrow.css @@ -0,0 +1,132 @@ +/* Tomorrow Theme */ +/* Original theme - https://github.com/chriskempson/tomorrow-theme */ +/* Pretty printing styles. Used with prettify.js. */ +/* SPAN elements with the classes below are added by prettyprint. */ +/* plain text */ +.pln { + color: #4d4d4c; } + +@media screen { + /* string content */ + .str { + color: #718c00; } + + /* a keyword */ + .kwd { + color: #8959a8; } + + /* a comment */ + .com { + color: #8e908c; } + + /* a type name */ + .typ { + color: #4271ae; } + + /* a literal value */ + .lit { + color: #f5871f; } + + /* punctuation */ + .pun { + color: #4d4d4c; } + + /* lisp open bracket */ + .opn { + color: #4d4d4c; } + + /* lisp close bracket */ + .clo { + color: #4d4d4c; } + + /* a markup tag name */ + .tag { + color: #c82829; } + + /* a markup attribute name */ + .atn { + color: #f5871f; } + + /* a markup attribute value */ + .atv { + color: #3e999f; } + + /* a declaration */ + .dec { + color: #f5871f; } + + /* a variable name */ + .var { + color: #c82829; } + + /* a function name */ + .fun { + color: #4271ae; } } +/* Use higher contrast and text-weight for printable form. */ +@media print, projection { + .str { + color: #060; } + + .kwd { + color: #006; + font-weight: bold; } + + .com { + color: #600; + font-style: italic; } + + .typ { + color: #404; + font-weight: bold; } + + .lit { + color: #044; } + + .pun, .opn, .clo { + color: #440; } + + .tag { + color: #006; + font-weight: bold; } + + .atn { + color: #404; } + + .atv { + color: #060; } } +/* Style */ +/* +pre.prettyprint { + background: white; + font-family: Consolas, Monaco, 'Andale Mono', monospace; + font-size: 12px; + line-height: 1.5; + border: 1px solid #ccc; + padding: 10px; } +*/ + +/* Specify class=linenums on a pre to get line numbering */ +ol.linenums { + margin-top: 0; + margin-bottom: 0; } + +/* IE indents via margin-left */ +li.L0, +li.L1, +li.L2, +li.L3, +li.L4, +li.L5, +li.L6, +li.L7, +li.L8, +li.L9 { + /* */ } + +/* Alternate shading for lines */ +li.L1, +li.L3, +li.L5, +li.L7, +li.L9 { + /* */ } diff --git a/jsdoc-custom-template/tmpl/augments.tmpl b/jsdoc-custom-template/tmpl/augments.tmpl new file mode 100644 index 0000000..446d28a --- /dev/null +++ b/jsdoc-custom-template/tmpl/augments.tmpl @@ -0,0 +1,10 @@ + + + +