Skip to content

Commit

Permalink
Merge pull request #423 from NBISweden/feature/#346_updatereporturlto…
Browse files Browse the repository at this point in the history
…showyearandweek

Update report url to show year and week
  • Loading branch information
jonandernovella authored May 11, 2022
2 parents ef135ef + f349664 commit 08e24f8
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 20 deletions.
39 changes: 27 additions & 12 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import React from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { Login } from "./pages/Login";
import { Report } from "./pages/Report";
import { AuthProvider } from "./components/AuthProvider";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { getISOWeek } from "date-fns";

// Route calls
// Order of routes is critical.
// First check for /login, the for the expected /report/year/week.
// If none of these apply, redirect to /report/year/week using
// current year and week and replace the history element.
export const App = () => {
const currentYear: number = new Date().getFullYear();
const currentWeek: number = getISOWeek(new Date());

return (
<>
<React.StrictMode>
<BrowserRouter>
<AuthProvider>
<Routes>
{["/report", "/"].map((path) => (
<Route
key={path}
path={path}
element={
<ProtectedRoute>
<Report />
</ProtectedRoute>
}
/>
))}
<Route path="/login" element={<Login />} />
<Route
path="/report/:year/:week"
element={
<ProtectedRoute>
<Report />
</ProtectedRoute>
}
/>
<Route
path="/*"
element={
<Navigate
replace
to={`/report/${currentYear}/${currentWeek}`}
/>
}
/>
</Routes>
</AuthProvider>
</BrowserRouter>
Expand Down
41 changes: 38 additions & 3 deletions frontend/src/components/TimeTravel.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import "../index.css";
import React, { useState, forwardRef } from "react";
import { getISOWeek } from "date-fns";
import { getISOWeek, getISOWeekYear } from "date-fns";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import sv from "date-fns/locale/sv";
import left from "../icons/caret-left-fill.svg";
import right from "../icons/caret-right-fill.svg";
import { useNavigate } from "react-router-dom";

/*
TimeTravel
Provide a date picker with navigation buttons on each side for switching weeks.
Parameters & properties:
- weekTravelDay = the last day of the displayed week
- onWeekTravel = any day in the week to be displayed?
- currentWeekArray = a list of the dates in the displayed week
- currentWeek = the number of the current/displayed week
Rendering is triggered by changing currentWeek (using setCurrentWeek()).
*/
export const TimeTravel = ({
weekTravelDay,
onWeekTravel,
Expand All @@ -16,30 +29,50 @@ export const TimeTravel = ({
onWeekTravel: (newDay: Date) => void;
currentWeekArray: Date[];
}) => {
// Week number is calculated from weekTravelDay
const [currentWeek, setCurrentWeek] = useState<number>(
getISOWeek(weekTravelDay)
);
// Change current week if weekTravelDay has changed.
// This is needed to make the datepicker respond to browser back- and forward-button navigation.
React.useEffect(() => {
setCurrentWeek(getISOWeek(weekTravelDay));
}, [weekTravelDay]);

// For navigation according to week number
const navigate = useNavigate();

// Date changed using the date picker
// Year for the url must be calculated w getISOWeekYear to handle year transitions.
const handleDateChange = (dates: Date[]) => {
setCurrentWeek(getISOWeek(dates[0]));
onWeekTravel(dates[0]);
navigate(`/report/${getISOWeekYear(dates[0])}/${getISOWeek(dates[0])}`);
};

// Click on previous week button.
// Calculate new date for end-of-week and set new week number based on that.
// Year for the url must be calculated w getISOWeekYear to handle year transitions.
const previousWeeksClickHandle = () => {
const nextDate = new Date(
weekTravelDay.setDate(weekTravelDay.getDate() - 7)
);
onWeekTravel(nextDate);
setCurrentWeek(getISOWeek(nextDate));
navigate(`/report/${getISOWeekYear(nextDate)}/${getISOWeek(nextDate)}`);
};

// Click on next week button
const nextWeeksClickHandle = () => {
const nextDate = new Date(
weekTravelDay.setDate(weekTravelDay.getDate() + 7)
);
onWeekTravel(nextDate);
setCurrentWeek(getISOWeek(nextDate));
navigate(`/report/${getISOWeekYear(nextDate)}/${getISOWeek(nextDate)}`);
};

// Date picker
const CustomDatePickerInput = forwardRef(({ onClick }, ref) => (
<button onClick={onClick} className="week-button" ref={ref}>
<svg
Expand All @@ -56,15 +89,17 @@ export const TimeTravel = ({
</button>
));

// Filter for weekdays. Return only Monday through Friday.
const isWeekday = (dt: Date) => {
const day = dt.getDay();
return day !== 0 && day !== 6;
};

// The calculated time-travel section
return (
<div className="time-travel">
<button onClick={previousWeeksClickHandle} className="week-arrow-button">
<img src={left} alt="left arrow" className="week-arrow" />
<img src={left} alt="switch to previous week" className="week-arrow" />
</button>
<DatePicker
onChange={(date) => handleDateChange(date)}
Expand All @@ -79,7 +114,7 @@ export const TimeTravel = ({
todayButton="Idag"
/>
<button onClick={nextWeeksClickHandle} className="week-arrow-button">
<img src={right} alt="right arrow" className="week-arrow" />
<img src={right} alt="switch to next week" className="week-arrow" />
</button>
</div>
);
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ input[type="number"] {
width: 100vw;
}

.header-warning {
text-align: center;
color: hsl(0deg 0% 15%);
background-color: #a7c947;
margin-top: 0%;
}

/* The following classes overwrite styling of the datepicker library */
/* stylelint-disable selector-class-pattern */
.react-datepicker__week .react-datepicker__day--selected {
Expand Down
81 changes: 76 additions & 5 deletions frontend/src/pages/Report.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import React, { useState } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { format as formatDate } from "date-fns";
import {
format as formatDate,
getISOWeek,
getISOWeekYear,
setISOWeek,
} from "date-fns";
import { Row } from "../components/Row";
import { HeaderRow } from "../components/HeaderRow";
import { QuickAdd } from "../components/QuickAdd";
Expand All @@ -16,6 +21,7 @@ import {
} from "../utils";
import { TimeTravel } from "../components/TimeTravel";
import { AuthContext } from "../components/AuthProvider";
import { useParams } from "react-router-dom";
import ClimbingBoxLoader from "react-spinners/ClimbingBoxLoader";
import LoadingOverlay from "react-loading-overlay-ts";

Expand All @@ -24,7 +30,10 @@ const beforeUnloadHandler = (event) => {
event.returnValue = "";
};

// The report page
export const Report = () => {
const urlparams = useParams();
let yearweekWarningMessage = "";
const [recentIssues, setRecentIssues] = useState<IssueActivityPair[]>([]);
const [filteredRecents, setFilteredRecents] = useState<IssueActivityPair[]>(
[]
Expand All @@ -33,7 +42,56 @@ export const Report = () => {
const [hidden, setHidden] = useState<IssueActivityPair[]>([]);
const [timeEntries, setTimeEntries] = useState<FetchedTimeEntry[]>([]);
const [newTimeEntries, setNewTimeEntries] = useState<TimeEntry[]>([]);
const today = new Date();

// Get year/week either from URL parameters or current time.
// Use today as date if nor year or week are valid numbers.
// Use flag "yearweekWarning" for indication of error in week/year parameters.
// When flag is true, display a warning message below the header with year/week.
let yearweekWarning: boolean = false;
const thisYear: number = new Date().getFullYear();
let yearnum: number = Number(urlparams.year);
if (isNaN(yearnum)) {
yearnum = thisYear;
yearweekWarning = true;
}

// Assume current week/date
let today = new Date();
const thisWeek: number = getISOWeek(new Date());
let weeknum = thisWeek;

// If week parameter is a valid number between 1-53, change the "today" value based on year/week.
// If the year parameter was wrong, ignore the week parameter and use thisWeek.
weeknum = Number(urlparams.week);
if (isNaN(weeknum) || yearweekWarning) {
weeknum = thisWeek;
yearweekWarning = true;
} else if (weeknum >= 1 && weeknum <= 53) {
today = setISOWeek(new Date(yearnum, 7, 7), weeknum);
} else {
yearweekWarning = true;
}
// Make sure that displayed year matches the year of the "today" variable
let ycheck = getISOWeekYear(today);
if (ycheck != yearnum) {
yearnum = ycheck;
yearweekWarning = true;
}

if (yearweekWarning) {
yearweekWarningMessage =
"Invalid year/week in url. Reverting to current year/week.";
} else {
yearweekWarningMessage = "";
}

// Set weeks for timetravel when weeknum has changed
React.useEffect(() => {
setWeekTravelDay(today);
setCurrentWeekArray(getFullWeek(today));
}, [weeknum]);

// Change displayed "Timetravel content" based on found year/week
const [weekTravelDay, setWeekTravelDay] = useState<Date>(today);
const [currentWeekArray, setCurrentWeekArray] = useState(getFullWeek(today));
const [showToast, setShowToast] = useState(false);
Expand All @@ -45,21 +103,23 @@ export const Report = () => {
setIsLoading(state);
};

// Retrieve time entries via api
const getTimeEntries = async (rowTopic: IssueActivityPair, days: Date[]) => {
let params = new URLSearchParams({
let queryparams = new URLSearchParams({
issue_id: `${rowTopic.issue.id}`,
activity_id: `${rowTopic.activity.id}`,
from: formatDate(days[0], dateFormat),
to: formatDate(days[4], dateFormat),
});
let entries: { time_entries: FetchedTimeEntry[] } = await getApiEndpoint(
`/api/time_entries?${params}`,
`/api/time_entries?${queryparams}`,
context
);
if (entries) return entries.time_entries;
return null;
};

// Retrieve time entries and recent entries
const getAllEntries = async (
favs: IssueActivityPair[],
recents: IssueActivityPair[]
Expand All @@ -76,6 +136,7 @@ export const Report = () => {
setTimeEntries(allEntries);
};

// If weekTravelDay changes, do this...
React.useEffect(() => {
let didCancel = false;
const setRecentIssuesWithinRange = async () => {
Expand All @@ -94,6 +155,7 @@ export const Report = () => {
};
}, [weekTravelDay]);

// If recentIssues has changed, do this...
React.useEffect(() => {
let didCancel = false;

Expand Down Expand Up @@ -134,6 +196,7 @@ export const Report = () => {
};
}, [recentIssues]);

// If the newTimeEntries have changed...
React.useEffect(() => {
window.removeEventListener("beforeunload", beforeUnloadHandler, true);
if (newTimeEntries.length > 0) {
Expand Down Expand Up @@ -183,6 +246,7 @@ export const Report = () => {
}
};

// Save which issues that have favorite status
const saveFavorites = async (newFavs: IssueActivityPair[]) => {
let logout = false;
const saved = await fetch(
Expand Down Expand Up @@ -212,6 +276,7 @@ export const Report = () => {
return saved;
};

// Toggle favorite status for an issue-activity pair
const handleToggleFav = async (topic: IssueActivityPair) => {
const existingFav = favorites.find(
(fav) =>
Expand Down Expand Up @@ -242,6 +307,7 @@ export const Report = () => {
}
};

// Enable hiding an issue-activity pair from the list of recent issues
const handleHide = async (topic: IssueActivityPair) => {
topic.is_hidden = true;
topic.custom_name = `${topic.issue.subject} - ${topic.activity.name}`;
Expand All @@ -256,6 +322,7 @@ export const Report = () => {
}
};

// Try to ...
const reportTime = async (timeEntry: TimeEntry) => {
let logout = false;
const saved = await fetch(`${SNOWPACK_PUBLIC_API_URL}/api/time_entries`, {
Expand Down Expand Up @@ -285,6 +352,7 @@ export const Report = () => {
return saved;
};

// Check for ...
const handleSave = async () => {
setShowToast(false);
setShowUnsavedWarning(false);
Expand Down Expand Up @@ -480,6 +548,8 @@ export const Report = () => {
};

if (context.user === null) return <></>;

// Main content
return (
<>
<LoadingOverlay
Expand All @@ -499,7 +569,7 @@ export const Report = () => {
>
<header>
<div className="report-header">
<h1 className="header-year">{weekTravelDay.getFullYear()}</h1>
<h1 className="header-year">{yearnum.toString()}</h1>
<TimeTravel
weekTravelDay={weekTravelDay}
onWeekTravel={handleWeekTravel}
Expand All @@ -511,6 +581,7 @@ export const Report = () => {
<div className="wrapper">
<div className="main">
<main>
<p className="header-warning">{yearweekWarningMessage}</p>
{favorites && favorites.length > 0 && (
<DragDropContext onDragEnd={onDragEnd}>
<section className="favorites-container">
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const addDays = (date: Date, days: number) => {
result.setDate(result.getDate() + days);
return result;
};

// Return array of all days in the same week as supplied date
export const getFullWeek = (today: Date): Date[] => {
let fullWeek = [];
const todayDay = today.getDay(); // Sunday - Saturday : 0 - 6
Expand Down

0 comments on commit 08e24f8

Please sign in to comment.