diff --git a/.eslintrc b/.eslintrc index 9582ddfd81..ff67d35757 100644 --- a/.eslintrc +++ b/.eslintrc @@ -85,18 +85,22 @@ "react/prop-types": 0, "react/jsx-key": 0, "import/order": ["error", { - "groups": ["external", "builtin", "internal", "sibling", "parent", "index"], + "newlines-between": "always", + "alphabetize": { "order": "asc", "caseInsensitive": true }, + "warnOnUnassignedImports": true, + "groups": ["builtin", "external", "internal", "sibling", "parent", "index", "object", "type"], "pathGroups": [ - { "pattern": "prop-types", "group": "external", "position": "before"}, - { "pattern": "react-*", "group": "external", "position": "before"}, - { "pattern": "react", "group": "external", "position": "before"}, - { "pattern": "components", "group": "internal" }, - { "pattern": "assets/**", "group": "internal", "position": "after" }, - { "pattern": "apis", "group": "internal", "position": "after" }, - { "pattern": "common", "group": "internal", "position": "after" } + { "pattern": "react", "group": "builtin", "position": "before" }, + { "pattern": "common/**", "group": "internal" }, + { "pattern": "context/**", "group": "internal" }, + { "pattern": "components/**", "group": "internal" }, + { "pattern": "assets/**", "group": "internal" }, + { "pattern": "apis/**", "group": "internal" }, + { "pattern": "constants/**", "group": "internal", "position": "after" }, + { "pattern": "utils/**", "group": "internal" }, + { "pattern": "helpers/**", "group": "internal" } ], - "pathGroupsExcludedImportTypes": ["builtin"], - "alphabetize": { "order": "asc", "caseInsensitive": true } + "pathGroupsExcludedImportTypes": ["builtin"] }] } } diff --git a/.slugignore b/.slugignore new file mode 100644 index 0000000000..37c2a976eb --- /dev/null +++ b/.slugignore @@ -0,0 +1,2 @@ +/spec +/test diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index ddd546a0be..bf3209b374 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -2,3 +2,4 @@ //= link_directory ../stylesheets .css //= link_tree ../../javascript .js //= link_tree ../../../vendor/javascript .js +//= link_tree ../js .js diff --git a/public/delete.svg b/app/assets/images/delete.svg similarity index 100% rename from public/delete.svg rename to app/assets/images/delete.svg diff --git a/public/edit.svg b/app/assets/images/edit.svg similarity index 100% rename from public/edit.svg rename to app/assets/images/edit.svg diff --git a/app/assets/js/.keep b/app/assets/js/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/controllers/internal_api/v1/projects_controller.rb b/app/controllers/internal_api/v1/projects_controller.rb index ade62bd3ba..b8c1f79c09 100644 --- a/app/controllers/internal_api/v1/projects_controller.rb +++ b/app/controllers/internal_api/v1/projects_controller.rb @@ -28,7 +28,7 @@ def update def destroy authorize project - project.discard! + render json: { notice: I18n.t("projects.delete.success.message") }, status: :ok if project.discard! end private diff --git a/app/controllers/invoices/view_controller.rb b/app/controllers/invoices/view_controller.rb index 47193ac423..7031627e89 100644 --- a/app/controllers/invoices/view_controller.rb +++ b/app/controllers/invoices/view_controller.rb @@ -5,6 +5,8 @@ class Invoices::ViewController < ApplicationController skip_after_action :verify_authorized def show + invoice.viewed! if invoice.sent? + render :show, locals: { invoice: }, layout: false end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ca8926ad54..165188fe0d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -3,7 +3,7 @@ module ApplicationHelper def user_avatar(user) if user.avatar.attached? - user.avatar + url_for(user.avatar) else image_url "avatar.svg" end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index a29692dd9f..36c14807fc 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -1,14 +1,13 @@ -import "../stylesheets/application.scss"; - +import "@fontsource/plus-jakarta-sans"; import * as ActiveStorage from "@rails/activestorage"; import Rails from "@rails/ujs"; +import "alpine-turbo-drive-adapter"; import * as ReactRailsUJS from "react_ujs"; -import "alpine-turbo-drive-adapter"; require("alpinejs"); require("jquery"); -import "@fontsource/plus-jakarta-sans"; +import "../stylesheets/application.scss"; global.toastr = require("toastr"); global.toastr.options = { diff --git a/app/javascript/packs/org_timezones.js b/app/javascript/packs/org_timezones.js new file mode 100644 index 0000000000..f9f5f686c9 --- /dev/null +++ b/app/javascript/packs/org_timezones.js @@ -0,0 +1,41 @@ +/* eslint-disable space-before-function-paren */ +/* eslint-disable func-style */ + +document.addEventListener("DOMContentLoaded", function () { + let timezones = []; + + function setTimeZoneOptions(options) { + const timeZoneSelect = document.querySelector(".select-timezone"); + timeZoneSelect.innerHTML = ""; + options.forEach((option) => { + const optionElement = document.createElement("option"); + optionElement.value = option; + optionElement.innerText = option; + if (window.location.pathname === "/company/new") { + optionElement.selected = + option === "(GMT-05:00) Eastern Time (US & Canada)"; + } + timeZoneSelect.appendChild(optionElement); + }); + } + + function handleChangeCountry(event) { + const country = event.target.value; + const timeZonesForCountry = timezones[country]; + if (timeZonesForCountry) setTimeZoneOptions(timeZonesForCountry); + } + + const selectCountry = document.querySelector(".select-country"); + + selectCountry.addEventListener("change", handleChangeCountry); + + async function main() { + const response = await fetch("/internal_api/v1/timezones"); + const jsonResponse = await response.json(); + timezones = jsonResponse.timezones; + if (window.location.pathname === "/company/new") + setTimeZoneOptions(timezones[selectCountry.value]); + } + + main(); +}); diff --git a/app/javascript/packs/server_rendering.js b/app/javascript/packs/server_rendering.js index b71665a287..affcbc5082 100644 --- a/app/javascript/packs/server_rendering.js +++ b/app/javascript/packs/server_rendering.js @@ -1,7 +1,8 @@ // By default, this pack is loaded for server-side rendering. // It must expose react_ujs as `ReactRailsUJS` and prepare a require context. -import "../stylesheets/application.scss"; const componentRequireContext = require.context("src/components", true); import * as ReactRailsUJS from "react_ujs"; + +import "../stylesheets/application.scss"; ReactRailsUJS.useContext(componentRequireContext); diff --git a/app/javascript/src/apis/reports.ts b/app/javascript/src/apis/reports.ts index 652797de40..f31719df71 100644 --- a/app/javascript/src/apis/reports.ts +++ b/app/javascript/src/apis/reports.ts @@ -10,6 +10,6 @@ const download = (type, queryParams) => axios({ responseType: "blob" }); -const reports = { get, download }; +const reportsApi = { get, download }; -export default reports; +export default reportsApi; diff --git a/app/javascript/src/apis/team.ts b/app/javascript/src/apis/team.ts index a1edcb7799..fa3f5a9cf5 100644 --- a/app/javascript/src/apis/team.ts +++ b/app/javascript/src/apis/team.ts @@ -18,7 +18,7 @@ const inviteMember = payload => axios.post("/invitations",payload); const updateInvitedMember = (id,payload) => axios.put(`/invitations/${id}`,payload); const deleteInvitedMember = id => axios.delete(`/invitations/${id}`); -export { +const teamApi = { get, search, destroyTeamMember, @@ -27,3 +27,5 @@ export { deleteInvitedMember, inviteMember }; + +export default teamApi; diff --git a/app/javascript/src/apis/timeTracking.ts b/app/javascript/src/apis/timeTracking.ts index e4d55caf99..f2b3c1871a 100644 --- a/app/javascript/src/apis/timeTracking.ts +++ b/app/javascript/src/apis/timeTracking.ts @@ -4,4 +4,6 @@ const path = "/time-tracking"; const get = async () => axios.get(path); -export default { get }; +const timeTrackingApi = { get }; + +export default timeTrackingApi; diff --git a/app/javascript/src/common/AutoComplete.tsx b/app/javascript/src/common/AutoComplete.tsx index f8d8722cde..686903481c 100644 --- a/app/javascript/src/common/AutoComplete.tsx +++ b/app/javascript/src/common/AutoComplete.tsx @@ -1,4 +1,5 @@ import React, { useState } from "react"; + import Autocomplete from "react-autocomplete"; import { useNavigate } from "react-router-dom"; diff --git a/app/javascript/src/common/ChartBar/index.tsx b/app/javascript/src/common/ChartBar/index.tsx index e20d0d1608..23928ff7a4 100644 --- a/app/javascript/src/common/ChartBar/index.tsx +++ b/app/javascript/src/common/ChartBar/index.tsx @@ -1,7 +1,9 @@ import React, { Fragment } from "react"; + import ReactTooltip from "react-tooltip"; import { minutesToHHMM } from "helpers/hhmm-parser"; + import { IChartBarGraph, ISingleClient } from "./interface"; const Client = ({ element, totalMinutes, index }:ISingleClient) => { diff --git a/app/javascript/src/common/CustomDatePicker/index.tsx b/app/javascript/src/common/CustomDatePicker/index.tsx index 1985409228..30b8976a3d 100644 --- a/app/javascript/src/common/CustomDatePicker/index.tsx +++ b/app/javascript/src/common/CustomDatePicker/index.tsx @@ -1,8 +1,8 @@ import React from "react"; -import DatePicker from "react-datepicker"; + import { getMonth, getYear } from "date-fns"; import { CaretCircleLeft, CaretCircleRight } from "phosphor-react"; - +import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; const CustomDatePicker = ({ handleChange, dueDate }) => { diff --git a/app/javascript/src/common/Pagination.tsx b/app/javascript/src/common/Pagination.tsx index 260f9258b8..a79510d442 100644 --- a/app/javascript/src/common/Pagination.tsx +++ b/app/javascript/src/common/Pagination.tsx @@ -1,4 +1,5 @@ import * as React from "react"; + import cn from "classnames"; import { CaretCircleLeft, CaretCircleRight } from "phosphor-react"; diff --git a/app/javascript/src/common/SyncAutoComplete.tsx b/app/javascript/src/common/SyncAutoComplete.tsx index daf04e2bd7..099cdd9b65 100644 --- a/app/javascript/src/common/SyncAutoComplete.tsx +++ b/app/javascript/src/common/SyncAutoComplete.tsx @@ -1,8 +1,9 @@ // NOTE: This file is for synchronous auto complete. import React, { useState, useEffect } from "react"; -import Autocomplete from "react-autocomplete"; + import { MagnifyingGlass } from "phosphor-react"; +import Autocomplete from "react-autocomplete"; const cssStyles = { menuStyles: { diff --git a/app/javascript/src/common/Table/index.tsx b/app/javascript/src/common/Table/index.tsx index d1d11c72f7..a17e313a88 100644 --- a/app/javascript/src/common/Table/index.tsx +++ b/app/javascript/src/common/Table/index.tsx @@ -1,7 +1,8 @@ import React from "react"; -import { useTable, useRowSelect } from "react-table"; -import PropTypes from "prop-types"; + import { Pencil, Trash } from "phosphor-react"; +import PropTypes from "prop-types"; +import { useTable, useRowSelect } from "react-table"; const IndeterminateCheckbox = React.forwardRef( // eslint-disable-line react/display-name ({ indeterminate, ...rest }:any, ref) => { diff --git a/app/javascript/src/common/Toastr.tsx b/app/javascript/src/common/Toastr.tsx index f789055c3b..a67439d268 100644 --- a/app/javascript/src/common/Toastr.tsx +++ b/app/javascript/src/common/Toastr.tsx @@ -1,6 +1,7 @@ import React from "react"; import { toast, Slide } from "react-toastify"; + import { GetToasterIcon, getToasterCloseButton } from "../constants/index"; const customId = "custom-toaster-id"; diff --git a/app/javascript/src/components/App.tsx b/app/javascript/src/components/App.tsx index be4edd02a3..eb14474cde 100644 --- a/app/javascript/src/components/App.tsx +++ b/app/javascript/src/components/App.tsx @@ -1,9 +1,13 @@ import React, { Fragment, useEffect } from "react"; + +import { Roles, TOASTER_DURATION } from "constants/index"; + import { BrowserRouter } from "react-router-dom"; import { ToastContainer } from "react-toastify"; + import { setAuthHeaders, registerIntercepts } from "apis/axios"; import UserContext from "context/UserContext"; -import { Roles, TOASTER_DURATION } from "constants/index"; + import Main from "./Main"; import Navbar from "./Navbar"; diff --git a/app/javascript/src/components/Clients/Details/Header.tsx b/app/javascript/src/components/Clients/Details/Header.tsx index 2764c23f6e..7099fa33da 100644 --- a/app/javascript/src/components/Clients/Details/Header.tsx +++ b/app/javascript/src/components/Clients/Details/Header.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; -import { useNavigate } from "react-router-dom"; + import { ArrowLeft, DotsThreeVertical, Receipt, Pencil, CaretDown, Trash } from "phosphor-react"; +import { useNavigate } from "react-router-dom"; import AddProject from "../Modals/AddProject"; import DeleteClient from "../Modals/DeleteClient"; diff --git a/app/javascript/src/components/Clients/Details/index.tsx b/app/javascript/src/components/Clients/Details/index.tsx index e257368f01..7d4b46b0dc 100644 --- a/app/javascript/src/components/Clients/Details/index.tsx +++ b/app/javascript/src/components/Clients/Details/index.tsx @@ -1,9 +1,12 @@ import React, { useEffect, useState } from "react"; + +import { TOASTER_DURATION } from "constants/index"; + import { useParams } from "react-router-dom"; import { ToastContainer } from "react-toastify"; + import { setAuthHeaders, registerIntercepts } from "apis/axios"; import clientApi from "apis/clients"; - import AmountBoxContainer from "common/AmountBox"; import ChartBar from "common/ChartBar"; import Table from "common/Table"; @@ -13,8 +16,8 @@ import { cashFormatter } from "helpers/cashFormater"; import { currencySymbol } from "helpers/currencySymbol"; import { sendGAPageView } from "utils/googleAnalytics"; -import { TOASTER_DURATION } from "constants/index"; import Header from "./Header"; + import { unmapClientDetails } from "../../../mapper/client.mapper"; const getTableData = (clients) => { @@ -67,10 +70,7 @@ const ClientList = ({ isAdminUser }) => { }); }; - useEffect(() => { - sendGAPageView(); - setAuthHeaders(); - registerIntercepts(); + const fetchProjectList = () => { clientApi.show(params.clientId, "?time_frame=week") .then((res) => { const sanitized = unmapClientDetails(res); @@ -79,6 +79,13 @@ const ClientList = ({ isAdminUser }) => { setTotalMinutes(sanitized.totalMinutes); setOverDueOutstandingAmt(sanitized.overdueOutstandingAmount); }); + }; + + useEffect(() => { + sendGAPageView(); + setAuthHeaders(); + registerIntercepts(); + fetchProjectList(); }, []); const tableHeader = [ @@ -134,7 +141,7 @@ const ClientList = ({ isAdminUser }) => { THIS WEEK