Table updates for appointments. Initial appointments screen + fetching.

This commit is contained in:
Patrick Fic
2020-02-05 11:44:07 -08:00
parent ba9e91d0c8
commit d216b9fa23
42 changed files with 2145 additions and 16 deletions

View File

@@ -40,7 +40,6 @@ export default connect(
return console.log("Error encountered when changing languages.", err);
});
console.log("currentUser", currentUser);
if (currentUser.authorized === null) {
//TODO: Translate this.
return <LoadingSpinner message="Waiting for Current Auth to persist." />;

View File

@@ -29,6 +29,12 @@ export default ({ landingHeader, selectedNavItem }) => {
</Link>
</Menu.Item>
<Menu.SubMenu title={t("menus.header.jobs")}>
<Menu.Item key="schedule">
<Link to="/manage/schedule">
<Icon type="calendar" />
{t("menus.header.schedule")}
</Link>
</Menu.Item>
<Menu.Item key="activejobs">
<Link to="/manage/jobs">
<Icon type="home" />

View File

@@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort } from "../../utils/sorters";
import { withRouter } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default withRouter(function JobsList({
loading,
@@ -154,13 +155,13 @@ export default withRouter(function JobsList({
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
width: "8%",
width: "10%",
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => {
return record.clm_total ? (
<span>{record.clm_total}</span>
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
) : (
t("general.labels.unknown")
);

View File

@@ -12,9 +12,7 @@ export default function ProfileShopsContainer() {
const [updateAssocation] = useMutation(UPDATE_ASSOCIATION);
const updateActiveShop = activeShopId => {
console.log("activeShopId", activeShopId);
data.associations.map(record => {
data.associations.forEach(record => {
updateAssocation({
variables: {
assocId: record.id,

View File

@@ -0,0 +1,27 @@
import moment from "moment";
import React from "react";
import { Calendar, momentLocalizer } from "react-big-calendar";
import "react-big-calendar/lib/css/react-big-calendar.css";
import DateCellWrapper from "../schedule-datecellwrapper/schedule-datecellwrapper.component";
import Event from "../schedule-event/schedule-event.component";
const localizer = momentLocalizer(moment);
export default function ScheduleCalendarComponent({ data }) {
return (
<Calendar
events={data}
defaultView="week"
//views={allViews}
step={30}
showMultiDayTimes
localizer={localizer}
min={new Date("2020-01-01T06:00:00")} //TODO: Read from business settings.
max={new Date("2020-01-01T20:00:00")}
//onSelectEvent={event => console.log("event", event)}
components={{
event: Event,
dateCellWrapper: DateCellWrapper
}}
/>
);
}

View File

@@ -0,0 +1,31 @@
import React from "react";
import { useQuery } from "react-apollo";
import ScheduleCalendarComponent from "./schedule-calendar.component";
import { QUERY_ALL_APPOINTMENTS } from "../../graphql/appointments.queries";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
export default function ScheduleCalendarContainer() {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_APPOINTMENTS, {
fetchPolicy: "network-only"
});
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
let normalizedData = data.appointments.map(e => {
//Required becuase Hasura returns a string instead of a date object.
return Object.assign(
{},
e,
{ start: new Date(e.start) },
{ end: new Date(e.end) }
);
});
return (
<ScheduleCalendarComponent
refetch={refetch}
data={data ? normalizedData : null}
/>
);
}

View File

@@ -0,0 +1,24 @@
import React from "react";
export default function ScheduleDateCellWrapper(dateCellWrapperProps) {
// Show 'click me' text in arbitrary places by using the range prop
const hasAlert = dateCellWrapperProps.range
? dateCellWrapperProps.range.some(date => {
return date.getDate() % 12 === 0;
})
: false;
const style = {
display: "flex",
flex: 1,
borderLeft: "1px solid #DDD",
backgroundColor: hasAlert ? "#f5f5dc" : "#fff"
};
return (
<div style={style}>
DateCellWrapper
{hasAlert && <button onClick={e => alert(e)}>Click me</button>}
{dateCellWrapperProps.children}
</div>
);
}

View File

@@ -0,0 +1,25 @@
import React from "react";
import { Popover, Button } from "antd";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { Link } from "react-router-dom";
export default function Event({ event }) {
const popoverContent = (
<div>
Job Total: <CurrencyFormatter>{event.job.clm_total}</CurrencyFormatter>
<Link to={`/manage/jobs/${event.job.id}`}>
<Button>View Job</Button>
</Link>
</div>
);
return (
<Popover content={popoverContent}>
<strong>{`${event.job.ownr_fn || ""} ${event.job.ownr_ln || ""}`}</strong>
<div>
{`${event.job.vehicle.v_model_yr || ""} ${event.job.vehicle
.v_make_desc || ""} ${event.job.vehicle.v_model_desc || ""}`}
</div>
</Popover>
);
}

View File

@@ -0,0 +1,24 @@
import { gql } from "apollo-boost";
export const QUERY_ALL_APPOINTMENTS = gql`
query QUERY_ALL_APPOINTMENTS {
appointments {
start
id
end
job {
ro_number
ownr_ln
ownr_fn
clm_total
id
vehicle {
id
v_model_yr
v_make_desc
v_model_desc
}
}
}
}
`;

View File

@@ -47,7 +47,7 @@ export const QUERY_ALL_OPEN_JOBS = gql`
name
}
updated_at
claim_total
clm_total
ded_amt
vehicle {
id
@@ -269,7 +269,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
name
}
updated_at
claim_total
clm_total
ded_amt
documents(limit: 3, order_by: { created_at: desc }) {
id

View File

@@ -27,6 +27,9 @@ const JobsAvailablePage = lazy(() =>
const ChatWindowContainer = lazy(() =>
import("../../components/chat-window/chat-window.container")
);
const ScheduleContainer = lazy(() =>
import("../schedule/schedule.page.container")
);
const { Header, Content, Footer } = Layout;
//This page will handle all routing for the entire application.
@@ -72,6 +75,12 @@ export default function Manage({ match }) {
component={ProfilePage}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}

View File

@@ -0,0 +1,6 @@
import React from "react";
import ScheduleCalendarContainer from "../../components/schedule-calendar/schedule-calendar.container";
export default function SchedulePageComponent() {
return <ScheduleCalendarContainer />;
}

View File

@@ -0,0 +1,12 @@
import React, { useEffect } from "react";
import SchedulePageComponent from "./schedule.page.component";
import { useTranslation } from "react-i18next";
export default function SchedulePageContainer() {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.schedule");
}, [t]);
return <SchedulePageComponent />;
}

View File

@@ -216,7 +216,8 @@
"activejobs": "Active Jobs",
"availablejobs": "Available Jobs",
"home": "Home",
"jobs": "Jobs"
"jobs": "Jobs",
"schedule": "Schedule"
},
"jobsdetail": {
"claimdetail": "Claim Details",
@@ -279,7 +280,8 @@
"jobsavailable": "Available Jobs | $t(titles.app)",
"jobsdetail": "Job {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Job Documents {{ro_number}} | $t(titles.app)",
"profile": "My Profile | $t(titles.app)"
"profile": "My Profile | $t(titles.app)",
"schedule": "Schedule | $t(titles.app)"
},
"user": {
"actions": {

View File

@@ -216,7 +216,8 @@
"activejobs": "Empleos activos",
"availablejobs": "Trabajos disponibles",
"home": "Casa",
"jobs": "Trabajos"
"jobs": "Trabajos",
"schedule": "Programar"
},
"jobsdetail": {
"claimdetail": "Detalles de la reclamación",
@@ -279,7 +280,8 @@
"jobsavailable": "Empleos disponibles | $t(titles.app)",
"jobsdetail": "Trabajo {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.app)",
"profile": "Mi perfil | $t(titles.app)"
"profile": "Mi perfil | $t(titles.app)",
"schedule": "Horario | $t(titles.app)"
},
"user": {
"actions": {

View File

@@ -216,7 +216,8 @@
"activejobs": "Emplois actifs",
"availablejobs": "Emplois disponibles",
"home": "Accueil",
"jobs": "Emplois"
"jobs": "Emplois",
"schedule": "Programme"
},
"jobsdetail": {
"claimdetail": "Détails de la réclamation",
@@ -279,7 +280,8 @@
"jobsavailable": "Emplois disponibles | $t(titles.app)",
"jobsdetail": "Travail {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.app)",
"profile": "Mon profil | $t(titles.app)"
"profile": "Mon profil | $t(titles.app)",
"schedule": "Horaire | $t(titles.app)"
},
"user": {
"actions": {

170
client/src/utils/dates.js Normal file
View File

@@ -0,0 +1,170 @@
/* eslint no-fallthrough: off */
import * as dates from "date-arithmetic";
export {
milliseconds,
seconds,
minutes,
hours,
month,
startOf,
endOf,
add,
eq,
gte,
gt,
lte,
lt,
inRange,
min,
max
} from "date-arithmetic";
const MILLI = {
seconds: 1000,
minutes: 1000 * 60,
hours: 1000 * 60 * 60,
day: 1000 * 60 * 60 * 24
};
const MONTHS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
export function monthsInYear(year) {
let date = new Date(year, 0, 1);
return MONTHS.map(i => dates.month(date, i));
}
export function firstVisibleDay(date, localizer) {
let firstOfMonth = dates.startOf(date, "month");
return dates.startOf(firstOfMonth, "week", localizer.startOfWeek());
}
export function lastVisibleDay(date, localizer) {
let endOfMonth = dates.endOf(date, "month");
return dates.endOf(endOfMonth, "week", localizer.startOfWeek());
}
export function visibleDays(date, localizer) {
let current = firstVisibleDay(date, localizer),
last = lastVisibleDay(date, localizer),
days = [];
while (dates.lte(current, last, "day")) {
days.push(current);
current = dates.add(current, 1, "day");
}
return days;
}
export function ceil(date, unit) {
let floor = dates.startOf(date, unit);
return dates.eq(floor, date) ? floor : dates.add(floor, 1, unit);
}
export function range(start, end, unit = "day") {
let current = start,
days = [];
while (dates.lte(current, end, unit)) {
days.push(current);
current = dates.add(current, 1, unit);
}
return days;
}
export function merge(date, time) {
if (time == null && date == null) return null;
if (time == null) time = new Date();
if (date == null) date = new Date();
date = dates.startOf(date, "day");
date = dates.hours(date, dates.hours(time));
date = dates.minutes(date, dates.minutes(time));
date = dates.seconds(date, dates.seconds(time));
return dates.milliseconds(date, dates.milliseconds(time));
}
export function eqTime(dateA, dateB) {
return (
dates.hours(dateA) === dates.hours(dateB) &&
dates.minutes(dateA) === dates.minutes(dateB) &&
dates.seconds(dateA) === dates.seconds(dateB)
);
}
export function isJustDate(date) {
return (
dates.hours(date) === 0 &&
dates.minutes(date) === 0 &&
dates.seconds(date) === 0 &&
dates.milliseconds(date) === 0
);
}
export function duration(start, end, unit, firstOfWeek) {
if (unit === "day") unit = "date";
return Math.abs(
dates[unit](start, undefined, firstOfWeek) -
dates[unit](end, undefined, firstOfWeek)
);
}
export function diff(dateA, dateB, unit) {
if (!unit || unit === "milliseconds") return Math.abs(+dateA - +dateB);
// the .round() handles an edge case
// with DST where the total won't be exact
// since one day in the range may be shorter/longer by an hour
return Math.round(
Math.abs(
+dates.startOf(dateA, unit) / MILLI[unit] -
+dates.startOf(dateB, unit) / MILLI[unit]
)
);
}
export function total(date, unit) {
let ms = date.getTime(),
div = 1;
switch (unit) {
case "week":
div *= 7;
case "day":
div *= 24;
case "hours":
div *= 60;
case "minutes":
div *= 60;
case "seconds":
div *= 1000;
}
return ms / div;
}
export function week(date) {
var d = new Date(date);
d.setHours(0, 0, 0);
d.setDate(d.getDate() + 4 - (d.getDay() || 7));
return Math.ceil(((d - new Date(d.getFullYear(), 0, 1)) / 8.64e7 + 1) / 7);
}
export function today() {
return dates.startOf(new Date(), "day");
}
export function yesterday() {
return dates.add(dates.startOf(new Date(), "day"), -1, "day");
}
export function tomorrow() {
return dates.add(dates.startOf(new Date(), "day"), 1, "day");
}