Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/javascript/src/apis/reports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from "axios";

const path = "/reports";

const get = async () => axios.get(`${path}`);
const get = (queryParams) => axios.get(`${path}${queryParams}`);

const reports = { get };

Expand Down
4 changes: 2 additions & 2 deletions app/javascript/src/components/Invoices/Generate/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ const GenerateInvoices = () => {
invoiceNumber,
amount
}}
isSending={showSendInvoiceModal}
setIsSending={setShowSendInvoiceModal}
isSending={showSendInvoiceModal}
setIsSending={setShowSendInvoiceModal}
/>}

{showInvoiceSetting && (
Expand Down
68 changes: 68 additions & 0 deletions app/javascript/src/components/Reports/Filters/filterOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import dayjs from "dayjs";
import { month, getDayWithSuffix } from "../../../utils/dateUtil";

const getWeek = (isCurrentWeek) => {
const currentDate = new Date();

const first = currentDate.getDate() - currentDate.getDay();
const weekFirstDay = isCurrentWeek ? first : first - 7;
const last = weekFirstDay + 6;

const firstday = dayjs(new Date(currentDate.setDate(weekFirstDay)));
const lastday = dayjs(new Date(currentDate.setDate(last)));
const completeCurrentDay = `${getDayWithSuffix(firstday.date())} ${month[firstday.month()]}`;
const completeLastWeekDay = `${getDayWithSuffix(lastday.date())} ${month[lastday.month()]}`;
return isCurrentWeek ? `This Week (${completeCurrentDay} - ${completeLastWeekDay})` :
`Last Week (${completeCurrentDay} - ${completeLastWeekDay})`;
};

const getMonth = (isCurrentMonth) => {
const currentDate = new Date();

const monthCount = isCurrentMonth ? dayjs(currentDate) : dayjs(currentDate).subtract(1, "month");
const monthStr = month[monthCount.month()];
const totalDaysOfCurrentMonth = dayjs(monthCount).daysInMonth();
const lastdayOfMonth = totalDaysOfCurrentMonth === 30 ? `${totalDaysOfCurrentMonth}th` : `${totalDaysOfCurrentMonth}st`;

return isCurrentMonth ? `This Month (1st ${monthStr} - ${lastdayOfMonth} ${monthStr})` :
`Last Month (1st ${monthStr} - ${lastdayOfMonth} ${monthStr})`;
};

const getDateRangeOptions = () => {
const thisWeek = getWeek(true);
const thisMonth = getMonth(true);
const previousMonth = getMonth(false);
const previousweek = getWeek(false);

return [
{ value: "this_month", label: thisMonth },
{ value: "last_month", label: previousMonth },
{ value: "this_week", label: thisWeek },
{ value: "last_week", label: previousweek }
];
};

const dateRangeOptions = [
{ value: "", label: "All" },
...getDateRangeOptions()
];

const statusOption = [
{ value: "billed", label: "BILLED" },
{ value: "unbilled", label: "UNBILLED" },
{ value: "nonBilled", label: "NON BILLABLE" }
];

const groupBy = [
{ value: "", label: "None" },
{ value: "team_member", label: "Team member" },
{ value: "client", label: "Client" },
{ value: "project", label: "Project" },
{ value: "Week", label: "Week" }
];

export {
dateRangeOptions,
statusOption,
groupBy
};
105 changes: 54 additions & 51 deletions app/javascript/src/components/Reports/Filters/index.tsx
Original file line number Diff line number Diff line change
@@ -1,58 +1,47 @@
import React from "react";
import React, { useState } from "react";
import Select from "react-select";
import { X } from "phosphor-react";

import {
dateRangeOptions,
statusOption
} from "./filterOptions";
import { customStyles } from "./style";
import getStatusCssClass from "../../../utils/getStatusTag";
import { useEntry } from "../context/EntryContext";

const FilterSideBar = ({ setFilterVisibilty }) => {
const FilterSideBar = ({
setFilterVisibilty,
resetFilter,
handleApplyFilter
}) => {

const dateRangeOptions = [
{ value: "", label: "All" },
{ value: "cwa", label: "This month (1st Dec - 31st Dec)" },
{ value: "alexa", label: "Last Month (1st Nov - 30th Nov)" },
{ value: "abc", label: "This Week (27th Dec - 2nd Jan)" },
{ value: "dwss", label: "Last Week (20th Dec - 26th Dec)" }
];
const projectOption = [
{ value: "onedrive", label: "One Drive" },
{ value: "cwa", label: "Outlook" },
{ value: "alexa", label: "Alexa" },
{ value: "abc", label: "One Drive" },
{ value: "dwss", label: "Outlook" },
{ value: "rrr", label: "Alexa" },
{ value: "xyz", label: "One Drive" },
{ value: "outlook", label: "Outlook" },
{ value: "pppp", label: "Alexa" }
];
const { filterOptions, selectedFilter } = useEntry();
const [filters, setFilters] = useState(selectedFilter);

const statusOption = [
{ value: "overdue", label: "OVERDUE" },
{ value: "sent", label: "SENT" },
{ value: "paid", label: "PAID" }
];
const handleSelectFilter = (selectedValue, field) => {
if (Array.isArray(selectedValue)) {
setFilters({
...filters,
[field.name]: selectedValue
});
}
else {
setFilters({
...filters,
[field.name]: selectedValue
});
}
};

const customStyles = {
control: (provided) => ({
...provided,
marginTop: "8px",
backgroundColor: "#F5F7F9",
color: "#1D1A31",
minHeight: 32,
padding: "0"
}),
menu: (provided) => ({
...provided,
fontSize: "12px",
letterSpacing: "2px"
})
const submitApplyFilter = () => {
handleApplyFilter(filters);
};

const CustomOption = (props) => {
const { innerProps, innerRef } = props;

return (
<div ref={innerRef} {...innerProps} className="py-1 px-2 cursor-pointer hover:bg-miru-gray-100">
<span className={`${getStatusCssClass(props.data.label)} text-xs tracking-widest`} >
<span className={`${getStatusCssClass(props.data.value)} text-xs tracking-widest`} >
{props.data.label}
</span>
</div>
Expand All @@ -74,30 +63,44 @@ const FilterSideBar = ({ setFilterVisibilty }) => {
<ul>
<li className="px-5 pb-5">
<h5 className="text-xs font-normal">Date Range</h5>
<Select isMulti={true} classNamePrefix="react-select-filter" styles={customStyles} options={dateRangeOptions} />
<Select
classNamePrefix="react-select-filter"
value={filters.dateRange}
onChange={handleSelectFilter}
name="dateRange"
styles={customStyles}
options={dateRangeOptions}
/>
</li>
<li className="px-5 pb-5">
<h5 className="text-xs font-normal">Clients</h5>
<Select isMulti={true} classNamePrefix="react-select-filter" styles={customStyles} />
<Select isMulti={true} value={filters.clients} classNamePrefix="react-select-filter" name="clients" onChange={handleSelectFilter} styles={customStyles} options={filterOptions.clients} />
</li>
<li className="px-5 pb-5">
<h5 className="text-xs font-normal">Team Members</h5>
<Select isMulti={true} classNamePrefix="react-select-filter" styles={customStyles} options={projectOption} />
<Select isMulti={true} value={filters.teamMember} classNamePrefix="react-select-filter" name="teamMember" onChange={handleSelectFilter} styles={customStyles} options={filterOptions.teamMembers} />
</li>
<li className="px-5 pb-5">
<h5 className="text-xs font-normal">Status</h5>
<Select isMulti={true} classNamePrefix="react-select-filter" styles={customStyles} options={statusOption} components={{ Option: CustomOption }} />
<Select isMulti={true} value={filters.status} classNamePrefix="react-select-filter" name="status" onChange={handleSelectFilter} styles={customStyles} options={statusOption} components={{ Option: CustomOption }} />
</li>
<li className="px-5 pb-5">
{/* <li className="px-5 pb-5">
<h5 className="text-xs font-normal">Group By</h5>
<Select isMulti={true} classNamePrefix="react-select-filter" styles={customStyles} options={statusOption} components={{ Option: CustomOption }} />
</li>
<Select
classNamePrefix="react-select-filter"
value={filters.groupBy}
styles={customStyles}
name="groupBy"
onChange={handleSelectFilter}
options={groupBy}
/>
</li> */}
</ul>
</div>
</div>
<div className="sidebar__footer">
<button className="sidebar__reset">RESET</button>
<button className="sidebar__apply">APPLY</button>
<button onClick={resetFilter} className="sidebar__reset">RESET</button>
<button onClick={submitApplyFilter} className="sidebar__apply">APPLY</button>
</div>
</div>
);
Expand Down
15 changes: 15 additions & 0 deletions app/javascript/src/components/Reports/Filters/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const customStyles = {
control: (provided) => ({
...provided,
marginTop: "8px",
backgroundColor: "#F5F7F9",
color: "#1D1A31",
minHeight: 32,
padding: "0"
}),
menu: (provided) => ({
...provided,
fontSize: "12px",
letterSpacing: "2px"
})
};
37 changes: 37 additions & 0 deletions app/javascript/src/components/Reports/Header/NavigationFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { Fragment } from "react";
import { X } from "phosphor-react";
import { useEntry } from "../context/EntryContext";

const NavigationFilter = () => {
const { selectedFilter, handleRemoveSingleFilter } = useEntry();

const filterHtml = (value, key, filterKey) => (
<li key={key} className="flex px-2 mr-4 py-1 rounded-xl tracking-widest font-semibold px-1 text-xs tracking-widest bg-miru-gray-400 text-miru-dark-purple-1000">
<span>{value}</span>
<button onClick={() => handleRemoveSingleFilter(filterKey, value)} className="inline-block ml-1">
<X size={11} color="#1D1A31" className="inline-block" weight="bold" />
</button>
</li>
);

const getFilterValues = () => {
let filterOptions = [];
for (const filterKey in selectedFilter) {
const filterValue = selectedFilter[filterKey];
if (Array.isArray(filterValue)) {
filterOptions = [...filterOptions, filterValue.map((item, index) => filterHtml(item.label, `${item}-${index}`, filterKey))];
}
else if (filterValue.value !== "") {
filterOptions = [...filterOptions, filterHtml(filterValue.label, filterKey, filterKey)];
}
}
return filterOptions;
};

return (
<Fragment>
{getFilterValues()}
</Fragment>
);
};
export default NavigationFilter;
56 changes: 42 additions & 14 deletions app/javascript/src/components/Reports/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,49 @@
import React from "react";
import {
Funnel
Funnel,
X
} from "phosphor-react";
import NavigationFilter from "./NavigationFilter";
import { useEntry } from "../context/EntryContext";

const Header = ({ setFilterVisibilty, isFilterVisible }) => (
<div className="sm:flex sm:items-center sm:justify-between mt-6 mb-3">
<div className="flex items-center">
<h2 className="text-3xl font-extrabold text-gray-900 sm:text-4xl sm:truncate py-1">
Time entry report
</h2>
<button className="ml-7 p-3 rounded hover:bg-miru-gray-1000 relative" onClick={() => { setFilterVisibilty(!isFilterVisible); }}>
<Funnel size={16} color="#7C5DEE" />
{/* Need to work on below code when integrating the api values */}
{/* <sup className="filter__counter">3</sup> */}
</button>
const Header = ({
setFilterVisibilty,
isFilterVisible,
showNavFilters,
resetFilter
}) => {
const { filterCounter } = useEntry();
return (
<div>
<div className="sm:flex sm:items-center sm:justify-between mt-6 mb-3">
<div className="flex items-center">
<h2 className="text-3xl font-extrabold text-gray-900 sm:text-4xl sm:truncate py-1">
Time entry report
</h2>
<button className="ml-7 p-3 rounded hover:bg-miru-gray-1000 relative" onClick={() => { setFilterVisibilty(!isFilterVisible); }}>
<Funnel size={16} color="#7C5DEE" />
{filterCounter > 0 && <sup className="filter__counter">{filterCounter}</sup>}
</button>
</div>
</div>
<div>
{
showNavFilters &&
<ul className="flex">
<NavigationFilter />
{
filterCounter > 0 && <li key={"clear_all"} className="flex px-2 mr-4 py-1 px-1 ">
<button onClick={resetFilter} className="inline-block ml-1 flex items-center">
<X size={12} color="#5B34EA" className="inline-block" weight="bold" />
<span className="text-miru-han-purple-1000 ml-1 text-xs tracking-widest font-bold">CLEAR ALL</span>
</button>
</li>
}
</ul>
}
</div>
</div>
</div>
);
);
};

export default Header;
40 changes: 40 additions & 0 deletions app/javascript/src/components/Reports/api/applyFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import reports from "apis/reports";

const isValuePresent = (filterValue) => filterValue.value && filterValue.value !== "";
const isNotEmptyArray = (value) => value && value.length > 0;

const apiKeys = {
clients: "client",
dateRange: "date_range",
groupBy: "groupBy",
status: "status",
teamMember: "team_member"
};

const getQueryParams = (selectedFilter) => {
let params = "";
for (const filterKey in selectedFilter) {
const filterValue = selectedFilter[filterKey];
if (Array.isArray(filterValue) && isNotEmptyArray(filterValue)) {
filterValue.forEach(item => {
params += `&${apiKeys[filterKey]}[]=${item.value}`;
});
}
if (!Array.isArray(filterValue) && isValuePresent(filterValue)) {
params += `&${apiKeys[filterKey]}=${filterValue.value}`;
}
}
return params;
};

const applyFilter = async (selectedFilter, setTimeEntries, setNavFilters, setFilterVisibilty) => {
const queryParams = getQueryParams(selectedFilter);
const sanitizedParam = queryParams.substring(1);
const sanitizedQuery = `?${sanitizedParam}`;
const res = await reports.get(sanitizedQuery);
setTimeEntries(res.data.entries);
setNavFilters(true);
setFilterVisibilty(false);
};

export default applyFilter;
Loading