Merge pull request #8 from snaptsoft/dev-patrick

Dev patrick
This commit is contained in:
2020-02-06 16:41:18 -08:00
committed by GitHub
138 changed files with 6090 additions and 128 deletions

View File

@@ -1,5 +1,35 @@
**Required items** **Required items**
-Bodyshop Record -Bodyshop Record
-Counter Record - type: ronum ..\*Include the statuses file in the format of:
```json
"statuses": [
"Open",
"Scheduled",
"Arrived",
"Repair Plan",
"Parts",
"Body",
"Prep",
"Paint",
"Reassembly",
"Sublet",
"Detail",
"Completed",
"Delivered",
"Invoiced",
"Exported"
],
"default_imported": "Open",
"default_scheduled": "Scheduled",
"default_arrived": "Arrived",
"default_completed": "Completed",
"default_delivered": "Delivered",
"default_invoiced": "Invoiced",
"default_exported": "Exported"
}
```
--\* Set the region for the shop.
-Counter Record - type: ronum

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.6.1"> <babeledit_project be_version="2.6.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -18,6 +18,439 @@
<folder_node> <folder_node>
<name>translation</name> <name>translation</name>
<children> <children>
<folder_node>
<name>appointments</name>
<children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>cancel</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>intake</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>reschedule</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>viewjob</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>canceling</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>saving</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>arrivedon</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>cancelledappointment</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>nodateselected</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>priorappointments</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>scheduledfor</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>successes</name>
<children>
<concept_node>
<name>canceled</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>created</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>associations</name>
<children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>activate</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>fields</name>
<children>
<concept_node>
<name>active</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>shopname</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>actions</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>bodyshop</name>
<children>
<folder_node>
<name>errors</name>
<children>
<concept_node>
<name>loading</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>documents</name> <name>documents</name>
<children> <children>
@@ -667,6 +1100,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>changestatus</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>convert</name> <name>convert</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -730,6 +1184,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>schedule</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -1875,7 +2350,7 @@
</translations> </translations>
</concept_node> </concept_node>
<concept_node> <concept_node>
<name>pay_date</name> <name>ownr_ph1</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
<description></description> <description></description>
<comment></comment> <comment></comment>
@@ -1896,7 +2371,7 @@
</translations> </translations>
</concept_node> </concept_node>
<concept_node> <concept_node>
<name>phone1</name> <name>pay_date</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
<description></description> <description></description>
<comment></comment> <comment></comment>
@@ -2887,6 +3362,27 @@
<folder_node> <folder_node>
<name>labels</name> <name>labels</name>
<children> <children>
<concept_node>
<name>appointmentconfirmation</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>available_new_jobs</name> <name>available_new_jobs</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3584,6 +4080,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>schedule</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -4407,6 +4924,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>schedule</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -4436,6 +4974,53 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>updateprofile</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
<name>fields</name>
<children>
<concept_node>
<name>displayname</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
</children> </children>

View File

@@ -19,6 +19,7 @@
"node-sass": "^4.13.0", "node-sass": "^4.13.0",
"react": "^16.12.0", "react": "^16.12.0",
"react-apollo": "^3.1.3", "react-apollo": "^3.1.3",
"react-big-calendar": "^0.23.0",
"react-chartjs-2": "^2.8.0", "react-chartjs-2": "^2.8.0",
"react-dom": "^16.12.0", "react-dom": "^16.12.0",
"react-i18next": "^11.2.7", "react-i18next": "^11.2.7",

View File

@@ -14,8 +14,6 @@ import SpinnerComponent from "../components/loading-spinner/loading-spinner.comp
import errorLink from "../graphql/apollo-error-handling"; import errorLink from "../graphql/apollo-error-handling";
import App from "./App"; import App from "./App";
class AppContainer extends Component { class AppContainer extends Component {
state = { state = {
client: null, client: null,

View File

@@ -28,19 +28,19 @@ const mapDispatchToProps = dispatch => ({
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(({ checkUserSession, currentUser }) => { )(({ checkUserSession, currentUser, setBodyshop }) => {
useEffect(() => { useEffect(() => {
checkUserSession(); checkUserSession();
return () => {}; return () => {};
}, [checkUserSession]); }, [checkUserSession]);
if (false) if (currentUser && currentUser.language)
i18next.changeLanguage("en_US", (err, t) => { i18next.changeLanguage(currentUser.language, (err, t) => {
if (err) if (err)
return console.log("Error encountered when changing languages.", err); return console.log("Error encountered when changing languages.", err);
}); });
console.log("currentUser", currentUser);
if (currentUser.authorized === null) { if (currentUser.authorized === null) {
//TODO: Translate this. //TODO: Translate this.
return <LoadingSpinner message="Waiting for Current Auth to persist." />; return <LoadingSpinner message="Waiting for Current Auth to persist." />;

View File

@@ -1,32 +1,34 @@
import React, { useState } from "react"; import { Layout } from "antd";
import ChatWindowComponent from "./chat-window.component"; import React from "react";
import { Button } from "antd";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors"; import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import { selectChatVisible } from "../../redux/messaging/messaging.selectors";
import ChatWindowComponent from "./chat-window.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser chatVisible: selectChatVisible
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
// signOutStart: () => dispatch(signOutStart()) toggleChatVisible: () => dispatch(toggleChatVisible())
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(function ChatWindowContainer() { )(function ChatWindowContainer({ chatVisible, toggleChatVisible }) {
const [visible, setVisible] = useState(false);
return ( return (
<div> <Layout.Sider
<Button onClick={() => setVisible(!visible)}>Drawer!</Button> collapsible
<ChatWindowComponent defaultCollapsed
mask={false} theme="light"
maskClosable={false} collapsedWidth={0}
visible={visible} width={300}
zIndex={0} collapsed={!chatVisible}
/> onCollapse={(collapsed, type) => toggleChatVisible()}
</div> >
<ChatWindowComponent mask={false} maskClosable={false} zIndex={0} />
</Layout.Sider>
); );
}); });

View File

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

View File

@@ -18,8 +18,6 @@ import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
import "./job-detail-cards.styles.scss"; import "./job-detail-cards.styles.scss";
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component"; import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
export default function JobDetailCards({ selectedJob }) { export default function JobDetailCards({ selectedJob }) {
const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, { const { loading, error, data, refetch } = useQuery(QUERY_JOB_CARD_DETAILS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -33,10 +31,10 @@ export default function JobDetailCards({ selectedJob }) {
return <div>{t("jobs.errors.nojobselected")}</div>; return <div>{t("jobs.errors.nojobselected")}</div>;
} }
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type='error' />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<div className='job-cards-container'> <div className="job-cards-container">
<NoteUpsertModal <NoteUpsertModal
jobId={data.jobs_by_pk.id} jobId={data.jobs_by_pk.id}
visible={noteModalVisible} visible={noteModalVisible}
@@ -47,9 +45,9 @@ export default function JobDetailCards({ selectedJob }) {
ghost={false} ghost={false}
onBack={() => window.history.back()} onBack={() => window.history.back()}
tags={ tags={
<span key='job-status'> <span key="job-status">
{data.jobs_by_pk.job_status ? ( {data.jobs_by_pk.status ? (
<Tag color='blue'>{data.jobs_by_pk.job_status.name}</Tag> <Tag color="blue">{data.jobs_by_pk.status}</Tag>
) : null} ) : null}
</span> </span>
} }
@@ -68,31 +66,34 @@ export default function JobDetailCards({ selectedJob }) {
} }
extra={[ extra={[
<Link <Link
key='documents' key="documents"
to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}> to={`/manage/jobs/${data.jobs_by_pk.id}#documents`}
>
<Button> <Button>
<Icon type='file-image' /> <Icon type="file-image" />
{t("jobs.actions.addDocuments")} {t("jobs.actions.addDocuments")}
</Button> </Button>
</Link>, </Link>,
<Button key='printing'> <Button key="printing">
<Icon type='printer' /> <Icon type="printer" />
{t("jobs.actions.printCenter")} {t("jobs.actions.printCenter")}
</Button>, </Button>,
<Button <Button
key='notes' key="notes"
actiontype='addNote' actiontype="addNote"
onClick={() => { onClick={() => {
setNoteModalVisible(!noteModalVisible); setNoteModalVisible(!noteModalVisible);
}}> }}
<Icon type='edit' /> >
<Icon type="edit" />
{t("jobs.actions.addNote")} {t("jobs.actions.addNote")}
</Button>, </Button>,
<Button key='postinvoices'> <Button key="postinvoices">
<Icon type='shopping-cart' /> <Icon type="shopping-cart" />
{t("jobs.actions.postInvoices")} {t("jobs.actions.postInvoices")}
</Button> </Button>
]}> ]}
>
{ {
// loading ? ( // loading ? (
// <LoadingSkeleton /> // <LoadingSkeleton />
@@ -113,7 +114,7 @@ export default function JobDetailCards({ selectedJob }) {
// ) // )
} }
<section className='job-cards'> <section className="job-cards">
<JobDetailCardsCustomerComponent <JobDetailCardsCustomerComponent
loading={loading} loading={loading}
data={data ? data.jobs_by_pk : null} data={data ? data.jobs_by_pk : null}

View File

@@ -1,8 +1,12 @@
import { import {
Avatar, Avatar,
Badge,
Button, Button,
Checkbox, Checkbox,
Descriptions, Descriptions,
Dropdown,
Icon,
Menu,
notification, notification,
PageHeader, PageHeader,
Tag Tag
@@ -10,17 +14,31 @@ import {
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Moment from "react-moment"; import Moment from "react-moment";
import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import CarImage from "../../assets/car.svg"; import CarImage from "../../assets/car.svg";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default function JobsDetailHeader({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export default connect(
mapStateToProps,
null
)(function JobsDetailHeader({
job, job,
mutationConvertJob, mutationConvertJob,
refetch, refetch,
handleSubmit handleSubmit,
scheduleModalState,
bodyshop,
updateJobStatus
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const setscheduleModalVisible = scheduleModalState[1];
const tombstoneTitle = ( const tombstoneTitle = (
<div> <div>
@@ -57,7 +75,34 @@ export default function JobsDetailHeader({
</div> </div>
); );
const statusmenu = (
<Menu
onClick={e => {
updateJobStatus(e.key);
}}
>
{bodyshop.md_ro_statuses.statuses.map(item => (
<Menu.Item key={item}>{item}</Menu.Item>
))}
</Menu>
);
const menuExtra = [ const menuExtra = [
<Dropdown overlay={statusmenu} key="changestatus">
<Button>
{t("jobs.actions.changestatus")} <Icon type="down" />
</Button>
</Dropdown>,
<Badge key="schedule" count={job.appointments_aggregate.aggregate.count}>
<Button
//TODO: Enabled logic based on status.
onClick={() => {
setscheduleModalVisible(true);
}}
>
{t("jobs.actions.schedule")}
</Button>
</Badge>,
<Button <Button
key="convert" key="convert"
type="dashed" type="dashed"
@@ -95,9 +140,7 @@ export default function JobsDetailHeader({
subTitle={tombstoneSubtitle} subTitle={tombstoneSubtitle}
tags={ tags={
<span key="job-status"> <span key="job-status">
{job.job_status ? ( {job.status ? <Tag color="blue">{job.status}</Tag> : null}
<Tag color="blue">{job.job_status.name}</Tag>
) : null}
</span> </span>
} }
extra={menuExtra} extra={menuExtra}
@@ -127,4 +170,4 @@ export default function JobsDetailHeader({
</Descriptions> </Descriptions>
</PageHeader> </PageHeader>
); );
} });

View File

@@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
export default withRouter(function JobsList({ export default withRouter(function JobsList({
loading, loading,
@@ -56,14 +57,12 @@ export default withRouter(function JobsList({
</Link> </Link>
) : ( ) : (
// t("jobs.errors.noowner") // t("jobs.errors.noowner")
<span> <span>{`${record.ownr_fn} ${record.ownr_ln}`}</span>
{record.ownr_fn} {record.ownr_ln}
</span>
); );
} }
}, },
{ {
title: t("jobs.fields.phone1"), title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1", dataIndex: "ownr_ph1",
key: "ownr_ph1", key: "ownr_ph1",
width: "12%", width: "12%",
@@ -95,7 +94,7 @@ export default withRouter(function JobsList({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.job_status?.name || t("general.labels.na"); return record.status || t("general.labels.na");
} }
}, },
@@ -154,13 +153,13 @@ export default withRouter(function JobsList({
title: t("jobs.fields.clm_total"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",
key: "clm_total", key: "clm_total",
width: "8%", width: "10%",
sorter: (a, b) => a.clm_total - b.clm_total, sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.clm_total ? ( return record.clm_total ? (
<span>{record.clm_total}</span> <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
) : ( ) : (
t("general.labels.unknown") t("general.labels.unknown")
); );

View File

@@ -4,5 +4,5 @@ import "./loading-skeleton.styles.scss";
import { Skeleton } from "antd"; import { Skeleton } from "antd";
export default function LoadingSkeleton(props) { export default function LoadingSkeleton(props) {
return <Skeleton {...props} className='loading-skeleton' active />; return <Skeleton {...props} className="loading-skeleton" active />;
} }

View File

@@ -1,15 +1,17 @@
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ProfileMyComponent from "../profile-my/profile-my.component";
import ProfileShopsContainer from "../profile-shops/profile-shops.container";
export default function ProfileContent({ sidebarSelection }) { export default function ProfileContent({ sidebarSelection }) {
const { t } = useTranslation(); const { t } = useTranslation();
switch (sidebarSelection.key) { switch (sidebarSelection.key) {
case "profile": case "profile":
return <div>Profile stuff</div>; return <ProfileMyComponent />;
case "shop": case "shops":
return <div>Shop stuff</div>; return <ProfileShopsContainer />;
default: default:
return ( return (
<AlertComponent message={t("profile.errors.state")} type="error" /> <AlertComponent message={t("profile.errors.state")} type="error" />

View File

@@ -0,0 +1,74 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import { Form, Input, notification, Button } from "antd";
import { updateUserDetails } from "../../redux/user/user.actions";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser
});
const mapDispatchToProps = dispatch => ({
updateUserDetails: userDetails => dispatch(updateUserDetails(userDetails))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(
Form.create({ name: "ProfileMyComponentForm" })(function ProfileMyComponent({
currentUser,
form,
updateUserDetails
}) {
const { isFieldsTouched, resetFields, getFieldDecorator } = form;
const { t } = useTranslation();
const handleSubmit = e => {
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
notification["error"]({
message: t("jobs.errors.validationtitle"),
description: t("jobs.errors.validation")
});
}
if (!err) {
console.log("values", values);
updateUserDetails({ displayName: values.displayname });
}
});
};
return (
<div>
{isFieldsTouched() ? (
//TODO: Appropriate Error
<AlertComponent
message={t("jobs.errors.validation")}
onClick={() => resetFields()}
/>
) : null}
<Form onSubmit={handleSubmit} autoComplete={"no"}>
<Form.Item label={t("user.fields.displayname")}>
{getFieldDecorator("displayname", {
initialValue: currentUser.displayName,
rules: [{ required: true }]
})(<Input name="displayname" />)}
</Form.Item>
<Button
type="primary"
key="submit"
htmlType="submit"
onClick={handleSubmit}
>
{t("user.actions.updateprofile")}
</Button>
</Form>
</div>
);
})
);

View File

@@ -0,0 +1,52 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { Table, Button } from "antd";
export default function ProfileShopsComponent({
loading,
data,
updateActiveShop
}) {
const { t } = useTranslation();
const columns = [
{
title: t("associations.fields.shopname"),
dataIndex: "shopname",
key: "shopname",
width: "25%",
render: (text, record) => <span>{record.bodyshop.shopname}</span>
},
{
title: t("associations.fields.active"),
dataIndex: "active",
key: "active",
width: "25%",
render: (text, record) => <span>{record.active ? "Yes" : "No"}</span>
},
{
title: t("associations.labels.actions"),
dataIndex: "actions",
key: "actions",
width: "25%",
render: (text, record) => (
<span>
{record.active ? null : (
<Button onClick={() => updateActiveShop(record.id)}>
Activate
</Button>
)}
</span>
)
}
];
return (
<Table
loading={loading}
size="small"
columns={columns.map(item => ({ ...item }))}
rowKey="id"
dataSource={data}
/>
);
}

View File

@@ -0,0 +1,34 @@
import React from "react";
import { useQuery, useMutation } from "react-apollo";
import {
QUERY_ALL_ASSOCIATIONS,
UPDATE_ASSOCIATION
} from "../../graphql/associations.queries";
import AlertComponent from "../alert/alert.component";
import ProfileShopsComponent from "./profile-shops.component";
export default function ProfileShopsContainer() {
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ASSOCIATIONS);
const [updateAssocation] = useMutation(UPDATE_ASSOCIATION);
const updateActiveShop = activeShopId => {
data.associations.forEach(record => {
updateAssocation({
variables: {
assocId: record.id,
assocActive: record.id === activeShopId ? true : false
}
});
});
refetch();
};
if (error) return <AlertComponent type="error" message={error.message} />;
return (
<ProfileShopsComponent
loading={loading}
data={data ? data.associations : null}
updateActiveShop={updateActiveShop}
/>
);
}

View File

@@ -0,0 +1,33 @@
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.container";
const localizer = momentLocalizer(moment);
export default function ScheduleCalendarWrapperComponent({
data,
refetch,
defaultView,
...otherProps
}) {
return (
<Calendar
events={data}
defaultView={defaultView}
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")}
components={{
event: e => {
return Event({ event: e.event, refetch: refetch });
},
dateCellWrapper: DateCellWrapper
}}
{...otherProps}
/>
);
}

View File

@@ -0,0 +1,13 @@
import React from "react";
//import "react-big-calendar/lib/css/react-big-calendar.css";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
export default function ScheduleCalendarComponent({ data, refetch }) {
return (
<ScheduleCalendarWrapperComponent
data={data}
defaultView="week"
refetch={refetch}
/>
);
}

View File

@@ -0,0 +1,34 @@
import React from "react";
import { useQuery } from "react-apollo";
import ScheduleCalendarComponent from "./schedule-calendar.component";
import { QUERY_ALL_ACTIVE_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_ACTIVE_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,18 @@
import React from "react";
export default function ScheduleDateCellWrapper(dateCellWrapperProps) {
// Show 'click me' text in arbitrary places by using the range prop
const style = {
display: "flex",
flex: 1,
borderLeft: "1px solid #DDD",
backgroundColor: "#fff"
};
return (
<div style={style}>
PLACEHOLDER:DATA
{dateCellWrapperProps.children}
</div>
);
}

View File

@@ -0,0 +1,20 @@
import React from "react";
import "react-big-calendar/lib/css/react-big-calendar.css";
import { useTranslation } from "react-i18next";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
export default function ScheduleDayViewComponent({ data, day }) {
const { t } = useTranslation();
if (data)
//TODO Remove addtional calendar elements from day view.
return (
<ScheduleCalendarWrapperComponent
events={data}
defaultView="day"
style={{ height: "40vh" }}
defaultDate={new Date(day)}
//onNavigate={e => console.log("e", e)}
/>
);
else return <div>{t("appointments.labels.nodateselected")}</div>;
}

View File

@@ -0,0 +1,36 @@
import React from "react";
import ScheduleDayViewComponent from "./schedule-day-view.component";
import { useQuery } from "react-apollo";
import { QUERY_APPOINTMENT_BY_DATE } from "../../graphql/appointments.queries";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import moment from "moment";
export default function ScheduleDayViewContainer({ day }) {
const { loading, error, data } = useQuery(QUERY_APPOINTMENT_BY_DATE, {
variables: {
start: moment(day).startOf("day"),
end: moment(day).endOf("day")
},
skip: !day,
fetchPolicy: "network-only"
});
if (loading) return <LoadingSkeleton paragraph={{ rows: 4 }} />;
if (error) return <div>{error.message}</div>;
let normalizedData;
if (data) {
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 (
<ScheduleDayViewComponent data={data ? normalizedData : null} day={day} />
);
}

View File

@@ -0,0 +1,58 @@
import React from "react";
import { Popover, Button } from "antd";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function ScheduleEventComponent({ event, handleCancel }) {
const { t } = useTranslation();
const popoverContent = (
<div>
<div>{`${t("jobs.fields.ro_number")}: ${event.job.ro_number}`}</div>
<div>
{t("jobs.fields.clm_total")}:
<CurrencyFormatter>{event.job.clm_total}</CurrencyFormatter>
</div>
<div>{`${t("jobs.fields.clm_no")}: ${event.job.clm_no}`}</div>
<div>
{t("jobs.fields.ownr_ea")}:{event.job.ownr_ea}
</div>
<div>
{t("jobs.fields.ownr_ph1")}:
<PhoneFormatter>{event.job.ownr_ph1}</PhoneFormatter>
</div>
<Link to={`/manage/jobs/${event.job.id}`}>
<Button>{t("appointments.actions.viewjob")}</Button>
</Link>
<Button onClick={() => handleCancel(event.id)}>
{t("appointments.actions.cancel")}
</Button>
<Button>
{
//TODO: Add reschedule Func.
}
{t("appointments.actions.reschedule")}
</Button>
<Button>
{
//TODO: Add intake func.
}
{t("appointments.actions.intake")}
</Button>
</div>
);
return (
<Popover content={popoverContent}>
<div>
<strong>{`${event.job.ownr_fn || ""} ${event.job.ownr_ln ||
""}`}</strong>
<span style={{ margin: 4 }}>
{`${event.job.vehicle.v_model_yr || ""} ${event.job.vehicle
.v_make_desc || ""} ${event.job.vehicle.v_model_desc || ""}`}
</span>
</div>
</Popover>
);
}

View File

@@ -0,0 +1,24 @@
import React from "react";
import { useMutation } from "react-apollo";
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
import ScheduleEventComponent from "./schedule-event.component";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
export default function ScheduleEventContainer({ event, refetch }) {
const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const handleCancel = id => {
cancelAppointment({ variables: { appid: event.id } })
.then(r => {
notification["success"]({
message: t("appointments.successes.canceled")
});
if (refetch) refetch();
})
.catch(error => {
notification["error"]({ message: t("appointments.errors.canceling") });
});
};
return <ScheduleEventComponent event={event} handleCancel={handleCancel} />;
}

View File

@@ -0,0 +1,44 @@
import React from "react";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component";
import { Timeline } from "antd";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
export default function ScheduleExistingAppointmentsList({
existingAppointments
}) {
const { t } = useTranslation();
if (existingAppointments.loading) return <LoadingSpinner />;
if (existingAppointments.error)
return (
<AlertComponent
message={existingAppointments.error.message}
type="error"
/>
);
return (
<div>
{t("appointments.labels.priorappointments")}
<Timeline>
{existingAppointments.data.appointments.map(item => {
return (
<Timeline.Item
key={item.id}
color={item.canceled ? "red" : item.arrived ? "green" : "blue"}
>
{item.canceled
? t("appointments.labels.cancelledappointment")
: item.arrived
? t("appointments.labels.arrivedon")
: t("appointments.labels.scheduledfor")}
<DateTimeFormatter>{item.start}</DateTimeFormatter>
</Timeline.Item>
);
})}
</Timeline>
</div>
);
}

View File

@@ -0,0 +1,72 @@
import { Checkbox, Col, DatePicker, Modal, Row, Tabs, TimePicker } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component";
export default function ScheduleJobModalComponent({
existingAppointments,
appData,
setAppData,
formData,
setFormData,
...props
}) {
const { t } = useTranslation();
//TODO: Existing appointments list only refreshes sometimes after modal close. May have to do with the container class.
return (
<Modal
{...props}
width={"80%"}
maskClosable={false}
destroyOnClose={true}
okButtonProps={{ disabled: appData.start ? false : true }}
>
<Row>
<Col span={14}>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="SMART Scheduling" key="auto">
Automatic Job Selection.
</Tabs.TabPane>
<Tabs.TabPane tab="Manual Scheduling" key="manual">
<Row>
Manual Job Selection Scheduled Time
<DatePicker
value={appData.start}
onChange={e => {
setAppData({ ...appData, start: e });
}}
/>
<TimePicker
value={appData.start}
format={"HH:mm"}
minuteStep={15}
onChange={e => {
setAppData({ ...appData, start: e });
}}
/>
</Row>
</Tabs.TabPane>
</Tabs>
<ScheduleExistingAppointmentsList
existingAppointments={existingAppointments}
/>
{
//TODO: Build out notifications.
}
<Checkbox
defaultChecked={formData.notifyCustomer}
onChange={e =>
setFormData({ ...formData, notifyCustomer: e.target.checked })
}
>
{t("jobs.labels.appointmentconfirmation")}
</Checkbox>
</Col>
<Col span={10}>
<ScheduleDayViewContainer day={appData.start} />
</Col>
</Row>
</Modal>
);
}

View File

@@ -0,0 +1,88 @@
import React, { useState } from "react";
import ScheduleJobModalComponent from "./schedule-job-modal.component";
import { useMutation, useQuery } from "react-apollo";
import {
INSERT_APPOINTMENT,
QUERY_APPOINTMENTS_BY_JOBID
} from "../../graphql/appointments.queries";
import moment from "moment";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
export default connect(
mapStateToProps,
null
)(function ScheduleJobModalContainer({
scheduleModalState,
jobId,
bodyshop,
refetch
}) {
const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, {
variables: { jobid: jobId },
fetchPolicy: "network-only",
skip: !jobId
});
const [scheduleModalVisible, setscheduleModalVisible] = scheduleModalState;
const [appData, setAppData] = useState({ jobid: jobId, start: null });
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS, {
variables: {
jobId: jobId,
status: bodyshop.md_ro_statuses.default_scheduled
}
});
const [formData, setFormData] = useState({ notifyCustomer: false });
const { t } = useTranslation();
return (
<ScheduleJobModalComponent
existingAppointments={existingAppointments}
appData={appData}
setAppData={setAppData}
formData={formData}
setFormData={setFormData}
//Spreadable Modal Props
visible={scheduleModalVisible}
onCancel={() => setscheduleModalVisible(false)}
onOk={() => {
//TODO: Customize the amount of minutes it will add.
insertAppointment({
variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") }
}
})
.then(r => {
updateJobStatus().then(r => {
notification["success"]({
message: t("appointments.successes.created")
});
if (formData.notifyCustomer) {
//TODO: Implement customer reminder on scheduling.
alert("Chosed to notify the customer somehow!");
}
setscheduleModalVisible(false);
if (refetch) refetch();
});
})
.catch(error => {
notification["error"]({
message: t("appointments.errors.saving", {
message: error.message
})
});
});
}}
/>
);
});

View File

@@ -34,6 +34,7 @@ export const createUserProfileDocument = async (userAuth, additionalData) => {
}; };
export const auth = firebase.auth(); export const auth = firebase.auth();
export const firestore = firebase.firestore(); export const firestore = firebase.firestore();
const provider = new firebase.auth.GoogleAuthProvider(); const provider = new firebase.auth.GoogleAuthProvider();
@@ -49,3 +50,15 @@ export const getCurrentUser = () => {
}, reject); }, reject);
}); });
}; };
export const updateCurrentUser = userDetails => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
console.log("userDetails", userDetails);
userAuth.updateProfile(userDetails).then(r => {
unsubscribe();
resolve(userAuth);
});
}, reject);
});
};

View File

@@ -42,8 +42,9 @@ const errorLink = onError(
authorization: token ? `Bearer ${token}` : "" authorization: token ? `Bearer ${token}` : ""
} }
}); });
console.log("forward", forward);
return forward(operation); console.log("operation", operation);
return forward(operation).subscribe();
// return new Observable(observer => { // return new Observable(observer => {
// const subscriber = { // const subscriber = {

View File

@@ -0,0 +1,89 @@
import { gql } from "apollo-boost";
export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
query QUERY_ALL_ACTIVE_APPOINTMENTS {
appointments(where: { canceled: { _eq: false } }) {
start
id
end
job {
ro_number
ownr_ln
ownr_fn
ownr_ph1
ownr_ea
clm_total
id
clm_no
vehicle {
id
v_model_yr
v_make_desc
v_model_desc
}
}
}
}
`;
export const INSERT_APPOINTMENT = gql`
mutation INSERT_APPOINTMENT($app: [appointments_insert_input!]!) {
insert_appointments(objects: $app) {
returning {
id
}
}
}
`;
export const QUERY_APPOINTMENT_BY_DATE = gql`
query QUERY_APPOINTMENT_BY_DATE($start: timestamptz, $end: timestamptz) {
appointments(where: { start: { _lte: $end, _gte: $start } }) {
start
id
end
job {
ro_number
ownr_ln
ownr_fn
ownr_ph1
ownr_ea
clm_total
id
clm_no
vehicle {
id
v_model_yr
v_make_desc
v_model_desc
}
}
}
}
`;
export const CANCEL_APPOINTMENT_BY_ID = gql`
mutation CANCEL_APPOINTMENT_BY_ID($appid: uuid!) {
update_appointments(
where: { id: { _eq: $appid } }
_set: { canceled: true }
) {
returning {
id
}
}
}
`;
export const QUERY_APPOINTMENTS_BY_JOBID = gql`
query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) {
appointments(where: { jobid: { _eq: $jobid } }) {
start
id
end
arrived
canceled
created_at
}
}
`;

View File

@@ -0,0 +1,27 @@
import { gql } from "apollo-boost";
export const QUERY_ALL_ASSOCIATIONS = gql`
query QUERY_ALL_ASSOCIATIONS {
associations {
id
active
bodyshop {
shopname
}
}
}
`;
export const UPDATE_ASSOCIATION = gql`
mutation UPDATE_ASSOCIATION($assocId: uuid, $assocActive: Boolean) {
update_associations(
where: { id: { _eq: $assocId } }
_set: { active: $assocActive }
) {
returning {
id
active
}
}
}
`;

View File

@@ -19,6 +19,7 @@ export const QUERY_BODYSHOP = gql`
state_tax_id state_tax_id
updated_at updated_at
zip_post zip_post
region_config
} }
} }
`; `;

View File

@@ -42,12 +42,9 @@ export const QUERY_ALL_OPEN_JOBS = gql`
scheduled_completion scheduled_completion
scheduled_in scheduled_in
scheduled_delivery scheduled_delivery
job_status { status
id
name
}
updated_at updated_at
claim_total clm_total
ded_amt ded_amt
vehicle { vehicle {
id id
@@ -195,7 +192,7 @@ export const GET_JOB_BY_PK = gql`
date_invoiced date_invoiced
date_closed date_closed
date_exported date_exported
status
joblines { joblines {
id id
unq_seq unq_seq
@@ -213,6 +210,11 @@ export const GET_JOB_BY_PK = gql`
lbr_amt lbr_amt
op_code_desc op_code_desc
} }
appointments_aggregate {
aggregate {
count
}
}
} }
} }
`; `;
@@ -253,6 +255,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
est_ct_fn est_ct_fn
est_ct_ln est_ct_ln
clm_no clm_no
status
ro_number ro_number
scheduled_completion scheduled_completion
scheduled_in scheduled_in
@@ -264,12 +267,9 @@ export const QUERY_JOB_CARD_DETAILS = gql`
private private
created_at created_at
} }
job_status {
id
name
}
updated_at updated_at
claim_total clm_total
ded_amt ded_amt
documents(limit: 3, order_by: { created_at: desc }) { documents(limit: 3, order_by: { created_at: desc }) {
id id
@@ -321,3 +321,13 @@ export const INSERT_NEW_JOB = gql`
} }
} }
`; `;
export const UPDATE_JOB_STATUS = gql`
mutation UPDATE_JOB_STATUS($jobId: uuid!, $status: String!) {
update_jobs(where: { id: { _eq: $jobId } }, _set: { status: $status }) {
returning {
id
}
}
}
`;

View File

@@ -16,13 +16,16 @@ import JobsDocumentsContainer from "../../components/jobs-documents/jobs-documen
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
import JobDetailFormContext from "./jobs-detail.page.context"; import JobDetailFormContext from "./jobs-detail.page.context";
import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component"; import JobsDetailDatesComponent from "../../components/jobs-detail-dates/jobs-detail-dates.component";
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
export default function JobsDetailPage({ export default function JobsDetailPage({
job, job,
mutationUpdateJob, mutationUpdateJob,
mutationConvertJob, mutationConvertJob,
handleSubmit, handleSubmit,
refetch refetch,
scheduleModalState,
updateJobStatus
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -41,12 +44,20 @@ export default function JobsDetailPage({
return ( return (
<div> <div>
<ScheduleJobModalContainer
scheduleModalState={scheduleModalState}
jobId={job.id}
refetch={refetch}
/>
<Form onSubmit={handleSubmit} {...formItemLayout} autoComplete={"off"}> <Form onSubmit={handleSubmit} {...formItemLayout} autoComplete={"off"}>
<JobsDetailHeader <JobsDetailHeader
job={job} job={job}
mutationConvertJob={mutationConvertJob} mutationConvertJob={mutationConvertJob}
refetch={refetch} refetch={refetch}
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
scheduleModalState={scheduleModalState}
updateJobStatus={updateJobStatus}
/> />
{isFieldsTouched() ? ( {isFieldsTouched() ? (

View File

@@ -1,5 +1,5 @@
import { Form, notification } from "antd"; import { Form, notification } from "antd";
import React, { useEffect } from "react"; import React, { useEffect, useState } from "react";
import { useMutation, useQuery } from "react-apollo"; import { useMutation, useQuery } from "react-apollo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
@@ -7,7 +7,8 @@ import SpinComponent from "../../components/loading-spinner/loading-spinner.comp
import { import {
CONVERT_JOB_TO_RO, CONVERT_JOB_TO_RO,
GET_JOB_BY_PK, GET_JOB_BY_PK,
UPDATE_JOB UPDATE_JOB,
UPDATE_JOB_STATUS
} from "../../graphql/jobs.queries"; } from "../../graphql/jobs.queries";
import JobsDetailPage from "./jobs-detail.page.component"; import JobsDetailPage from "./jobs-detail.page.component";
import JobDetailFormContext from "./jobs-detail.page.context"; import JobDetailFormContext from "./jobs-detail.page.context";
@@ -16,12 +17,29 @@ function JobsDetailPageContainer({ match, form }) {
const { jobId } = match.params; const { jobId } = match.params;
const { t } = useTranslation(); const { t } = useTranslation();
const scheduleModalState = useState(false);
const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, { const { loading, error, data, refetch } = useQuery(GET_JOB_BY_PK, {
variables: { id: jobId }, variables: { id: jobId },
fetchPolicy: "network-only" fetchPolicy: "network-only"
}); });
const [mutationUpdateJob] = useMutation(UPDATE_JOB); const [mutationUpdateJob] = useMutation(UPDATE_JOB);
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO); const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
const updateJobStatus = status => {
mutationUpdateJobstatus({
variables: { jobId: jobId, status: status }
})
.then(r => {
notification["success"]({ message: t("jobs.successes.save") });
refetch();
})
.catch(error => {
//TODO Error handling.
console.log("error", error);
});
};
useEffect(() => { useEffect(() => {
document.title = loading document.title = loading
@@ -71,6 +89,8 @@ function JobsDetailPageContainer({ match, form }) {
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
getFieldDecorator={form.getFieldDecorator} getFieldDecorator={form.getFieldDecorator}
refetch={refetch} refetch={refetch}
scheduleModalState={scheduleModalState}
updateJobStatus={updateJobStatus}
/> />
</JobDetailFormContext.Provider> </JobDetailFormContext.Provider>
) : ( ) : (

View File

@@ -1,16 +1,21 @@
import { BackTop, Layout } from "antd"; import { BackTop, Layout, notification } from "antd";
import React, { lazy, Suspense, useEffect } from "react"; import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
//This page will handle all routing for the entire application.
import { connect } from "react-redux";
import { Route } from "react-router"; import { Route } from "react-router";
import ChatWindowContainer from "../../components/chat-window/chat-window.container"; import { createStructuredSelector } from "reselect";
import { useQuery } from "react-apollo";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FooterComponent from "../../components/footer/footer.component"; import FooterComponent from "../../components/footer/footer.component";
//Component Imports //Component Imports
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
//const WhiteBoardPage = lazy(() => import("../white-board/white-board.page"));
import { setBodyshop } from "../../redux/user/user.actions";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
//const WhiteBoardPage = lazy(() => import("../white-board/white-board.page"));
const ManageRootPage = lazy(() => const ManageRootPage = lazy(() =>
import("../manage-root/manage-root.page.container") import("../manage-root/manage-root.page.container")
); );
@@ -25,12 +30,35 @@ const JobsDocumentsPage = lazy(() =>
const JobsAvailablePage = lazy(() => const JobsAvailablePage = lazy(() =>
import("../jobs-available/jobs-available.page.container") import("../jobs-available/jobs-available.page.container")
); );
const ChatWindowContainer = lazy(() =>
import("../../components/chat-window/chat-window.container")
);
const ScheduleContainer = lazy(() =>
import("../schedule/schedule.page.container")
);
const { Header, Content, Footer, Sider } = Layout; const { Header, Content, Footer } = Layout;
//This page will handle all routing for the entire application.
export default function Manage({ match }) { const mapDispatchToProps = dispatch => ({
setBodyshop: bs => dispatch(setBodyshop(bs))
});
export default connect(
null,
mapDispatchToProps
)(function Manage({ match, setBodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { error, data } = useQuery(QUERY_BODYSHOP, {
fetchPolicy: "network-only"
});
if (error) {
notification["error"]({ message: t("bodyshop.errors.loading") });
}
if (data) {
setBodyshop(data.bodyshops[0]);
}
useEffect(() => { useEffect(() => {
document.title = t("titles.app"); document.title = t("titles.app");
}, [t]); }, [t]);
@@ -41,17 +69,9 @@ export default function Manage({ match }) {
<HeaderContainer /> <HeaderContainer />
</Header> </Header>
<Layout> <Layout>
<Sider <ChatWindowContainer />
collapsible
defaultCollapsed
theme="light"
collapsedWidth={0}
width={300}
>
<chatWindowContainer />
</Sider>
<Content className="content-container" style={{ margin: "50px" }}> <Content className="content-container" style={{ margin: "0px" }}>
<ErrorBoundary> <ErrorBoundary>
<Suspense <Suspense
fallback={ fallback={
@@ -78,6 +98,12 @@ export default function Manage({ match }) {
component={ProfilePage} component={ProfilePage}
/> />
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route <Route
exact exact
path={`${match.path}/available`} path={`${match.path}/available`}
@@ -93,4 +119,4 @@ export default function Manage({ match }) {
<BackTop /> <BackTop />
</Layout> </Layout>
); );
} });

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

@@ -0,0 +1,7 @@
import MessagingActionTypes from './messaging.types'
export const toggleChatVisible = () => ({
type: MessagingActionTypes.TOGGLE_CHAT_VISIBLE,
//payload: user
});

View File

@@ -0,0 +1,24 @@
import MessagingActionTypes from "./messaging.types";
const INITIAL_STATE = {
visible: false
};
const messagingReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case MessagingActionTypes.TOGGLE_CHAT_VISIBLE:
return {
...state,
visible: !state.visible
};
case MessagingActionTypes.SET_CHAT_VISIBLE:
return {
...state,
visible: true
};
default:
return state;
}
};
export default messagingReducer;

View File

@@ -0,0 +1,90 @@
import {
all
// call, put, takeLatest
} from "redux-saga/effects";
//import { auth, getCurrentUser } from "../../firebase/firebase.utils";
// import { toggleChatVisible } from "./messaging.actions";
// import MessagingActionTypes from "./messaging.types";
// export function* getSnapshotFromUserAuth(userAuth) {
// try {
// const userRef = yield call(createUserProfileDocument, userAuth);
// //const userSnapshot = yield userRef.get();
// } catch (error) {
// yield put(signInFailure(error));
// }
// }
// export function* signInWithEmail({ payload: { email, password } }) {
// try {
// const { user } = yield auth.signInWithEmailAndPassword(email, password);
// yield put(
// signInSuccess({
// uid: user.uid,
// email: user.email,
// displayName: user.displayName,
// authorized: true
// })
// );
// } catch (error) {
// yield put(signInFailure(error));
// }
// }
// //This is the listener fo rthe call, and when it finds it, it triggers somethign else.
// export function* onEmailSignInStart() {
// yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
// }
// export function* isUserAuthenticated() {
// try {
// const user = yield getCurrentUser();
// if (!user) {
// yield put(unauthorizedUser());
// return;
// }
// let token = yield user.getIdToken();
// localStorage.setItem("token", token);
// window.sessionStorage.setItem(`lastTokenRefreshTime`, new Date());
// yield put(
// signInSuccess({
// uid: user.uid,
// email: user.email,
// displayName: user.displayName,
// authorized: true
// })
// );
// } catch (error) {
// yield put(signInFailure(error));
// }
// }
// export function* onCheckUserSession() {
// yield takeLatest(UserActionTypes.CHECK_USER_SESSION, isUserAuthenticated);
// }
// export function* signOutStart() {
// try {
// yield auth.signOut();
// yield put(signOutSuccess());
// localStorage.removeItem("token");
// } catch (error) {
// yield put(signOutFailure(error.message));
// }
// }
// export function* onSignOutStart() {
// yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
// }
export function* messagingSagas() {
yield all([
// call(onGoogleSignInStart),
// call(onEmailSignInStart),
// call(onCheckUserSession),
// call(onSignOutStart)
// call(onEmailSignUpStart),
// call(onEmailSignUpSuccess)
]);
}

View File

@@ -0,0 +1,8 @@
import { createSelector } from "reselect";
const selectMessaging = state => state.messaging;
export const selectChatVisible = createSelector(
[selectMessaging],
messaging => messaging.visible
);

View File

@@ -0,0 +1,5 @@
const MessagingActionTypes = {
TOGGLE_CHAT_VISIBLE: "TOGGLE_CHAT_VISIBLE",
SET_CHAT_VISIBLE: "SET_CHAT_VISIBLE"
};
export default MessagingActionTypes;

View File

@@ -3,6 +3,7 @@ import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage"; import storage from "redux-persist/lib/storage";
import userReducer from "./user/user.reducer"; import userReducer from "./user/user.reducer";
import messagingReducer from "./messaging/messaging.reducer";
// import cartReducer from './cart/cart.reducer'; // import cartReducer from './cart/cart.reducer';
// import directoryReducer from './directory/directory.reducer'; // import directoryReducer from './directory/directory.reducer';
// import shopReducer from './shop/shop.reducer'; // import shopReducer from './shop/shop.reducer';
@@ -15,7 +16,8 @@ const persistConfig = {
}; };
const rootReducer = combineReducers({ const rootReducer = combineReducers({
user: userReducer user: userReducer,
messaging: messagingReducer
// cart: cartReducer, // cart: cartReducer,
// directory: directoryReducer, // directory: directoryReducer,
// shop: shopReducer // shop: shopReducer

View File

@@ -3,9 +3,10 @@ import { all, call } from "redux-saga/effects";
//List of all Sagas //List of all Sagas
// import { shopSagas } from "./shop/shop.sagas"; // import { shopSagas } from "./shop/shop.sagas";
import { userSagas } from "./user/user.sagas"; import { userSagas } from "./user/user.sagas";
import { messagingSagas } from "./messaging/messaging.sagas";
//import { cartSagas } from "./cart/cart.sagas"; //import { cartSagas } from "./cart/cart.sagas";
export default function* rootSaga() { export default function* rootSaga() {
//All starts all the Sagas concurrently. //All starts all the Sagas concurrently.
yield all([call(userSagas)]); yield all([call(userSagas), call(messagingSagas)]);
} }

View File

@@ -38,3 +38,18 @@ export const setUserLanguage = language => ({
type: UserActionTypes.SET_USER_LANGUAGE, type: UserActionTypes.SET_USER_LANGUAGE,
payload: language payload: language
}); });
export const updateUserDetails = userDetails => ({
type: UserActionTypes.UPDATE_USER_DETAILS,
payload: userDetails
});
export const updateUserDetailsSuccess = userDetails => ({
type: UserActionTypes.UPDATE_USER_DETAILS_SUCCESS,
payload: userDetails
});
export const setBodyshop = bodyshop => ({
type: UserActionTypes.SET_SHOP_DETAILS,
payload: bodyshop
});

View File

@@ -3,8 +3,9 @@ import UserActionTypes from "./user.types";
const INITIAL_STATE = { const INITIAL_STATE = {
currentUser: { currentUser: {
authorized: null, authorized: null,
language: "en_US" language: "en_us"
}, },
bodyshop: null,
error: null error: null
}; };
@@ -33,10 +34,20 @@ const userReducer = (state = INITIAL_STATE, action) => {
...state, ...state,
language: action.payload language: action.payload
}; };
case UserActionTypes.UPDATE_USER_DETAILS_SUCCESS:
return {
...state,
currentUser: {
...state.currentUser,
...action.payload //Spread current user details in.
}
};
case UserActionTypes.SET_SHOP_DETAILS:
return { ...state, bodyshop: action.payload };
case UserActionTypes.SIGN_IN_FAILURE: case UserActionTypes.SIGN_IN_FAILURE:
case UserActionTypes.SIGN_OUT_FAILURE: case UserActionTypes.SIGN_OUT_FAILURE:
case UserActionTypes.EMAIL_SIGN_UP_FAILURE: case UserActionTypes.EMAIL_SIGN_UP_FAILURE:
console.log("Reduced getting called.");
return { return {
...state, ...state,
error: action.payload error: action.payload

View File

@@ -1,6 +1,17 @@
import { all, call, put, takeLatest } from "redux-saga/effects"; import { all, call, put, takeLatest } from "redux-saga/effects";
import { auth, getCurrentUser } from "../../firebase/firebase.utils"; import {
import { signInFailure, signInSuccess, signOutFailure, signOutSuccess, unauthorizedUser } from "./user.actions"; auth,
getCurrentUser,
updateCurrentUser
} from "../../firebase/firebase.utils";
import {
signInFailure,
signInSuccess,
signOutFailure,
signOutSuccess,
unauthorizedUser,
updateUserDetailsSuccess
} from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
// export function* getSnapshotFromUserAuth(userAuth) { // export function* getSnapshotFromUserAuth(userAuth) {
@@ -15,6 +26,9 @@ import UserActionTypes from "./user.types";
export function* signInWithEmail({ payload: { email, password } }) { export function* signInWithEmail({ payload: { email, password } }) {
try { try {
const { user } = yield auth.signInWithEmailAndPassword(email, password); const { user } = yield auth.signInWithEmailAndPassword(email, password);
let token = yield user.getIdToken();
localStorage.setItem("token", token);
window.sessionStorage.setItem(`lastTokenRefreshTime`, new Date());
yield put( yield put(
signInSuccess({ signInSuccess({
uid: user.uid, uid: user.uid,
@@ -74,14 +88,24 @@ export function* onSignOutStart() {
yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart); yield takeLatest(UserActionTypes.SIGN_OUT_START, signOutStart);
} }
export function* onUpdateUserDetails() {
yield takeLatest(UserActionTypes.UPDATE_USER_DETAILS, updateUserDetails);
}
export function* updateUserDetails(userDetails) {
try {
yield updateCurrentUser(userDetails.payload);
yield put(updateUserDetailsSuccess(userDetails.payload));
} catch (error) {
//yield put(signOutFailure(error.message));
//TODO: error handling
}
}
export function* userSagas() { export function* userSagas() {
yield all([ yield all([
// call(onGoogleSignInStart),
call(onEmailSignInStart), call(onEmailSignInStart),
call(onCheckUserSession), call(onCheckUserSession),
call(onSignOutStart), call(onSignOutStart),
// call(onEmailSignUpStart), call(onUpdateUserDetails)
// call(onEmailSignUpSuccess)
]); ]);
} }

View File

@@ -11,3 +11,8 @@ export const selectSignInError = createSelector(
[selectUser], [selectUser],
user => user.error user => user.error
); );
export const selectBodyshop = createSelector(
[selectUser],
user => user.bodyshop
);

View File

@@ -12,6 +12,9 @@ const UserActionTypes = {
EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS", EMAIL_SIGN_UP_SUCCESS: "EMAIL_SIGN_UP_SUCCESS",
EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE", EMAIL_SIGN_UP_FAILURE: "EMAIL_SIGN_UP_FAILURE",
UNAUTHORIZED_USER: "UNAUTHORIZED_USER", UNAUTHORIZED_USER: "UNAUTHORIZED_USER",
SET_USER_LANGUAGE: "SET_USER_LANGUAGE" SET_USER_LANGUAGE: "SET_USER_LANGUAGE",
UPDATE_USER_DETAILS: "UPDATE_USER_DETAILS",
UPDATE_USER_DETAILS_SUCCESS: "UPDATE_USER_DETAILS_SUCCESS",
SET_SHOP_DETAILS: "SET_SHOP_DETAILS"
}; };
export default UserActionTypes; export default UserActionTypes;

View File

@@ -1,5 +1,45 @@
{ {
"translation": { "translation": {
"appointments": {
"actions": {
"cancel": "Cancel",
"intake": "Intake",
"reschedule": "Reschedule",
"viewjob": "View Job"
},
"errors": {
"canceling": "Error canceling appointment.",
"saving": "Error scheduling appointment. {{message}}"
},
"labels": {
"arrivedon": "Arrived on: ",
"cancelledappointment": "Canceled appointment for: ",
"nodateselected": "No date has been selected.",
"priorappointments": "Previous Appointments",
"scheduledfor": "Scheduled appointment for: "
},
"successes": {
"canceled": "Appointment canceled successfully.",
"created": "Appointment scheduled successfully."
}
},
"associations": {
"actions": {
"activate": "Activate"
},
"fields": {
"active": "Active?",
"shopname": "Shop Name"
},
"labels": {
"actions": "Actions"
}
},
"bodyshop": {
"errors": {
"loading": "Unable to load shop details. Please call technical support."
}
},
"documents": { "documents": {
"errors": { "errors": {
"deletes3": "Error deleting document from storage. ", "deletes3": "Error deleting document from storage. ",
@@ -52,9 +92,11 @@
"actions": { "actions": {
"addDocuments": "Add Job Documents", "addDocuments": "Add Job Documents",
"addNote": "Add Note", "addNote": "Add Note",
"changestatus": "Change Status",
"convert": "Convert", "convert": "Convert",
"postInvoices": "Post Invoices", "postInvoices": "Post Invoices",
"printCenter": "Print Center" "printCenter": "Print Center",
"schedule": "Schedule"
}, },
"errors": { "errors": {
"creating": "Error encountered while creating job. {{error}}", "creating": "Error encountered while creating job. {{error}}",
@@ -113,8 +155,8 @@
"owner": "Owner", "owner": "Owner",
"owner_owing": "Cust. Owes", "owner_owing": "Cust. Owes",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_ph1": "Phone 1",
"pay_date": "Inspection Date", "pay_date": "Inspection Date",
"phone1": "Phone 1",
"phoneshort": "PH", "phoneshort": "PH",
"policy_no": "Policy #", "policy_no": "Policy #",
"ponumber": "PO Number", "ponumber": "PO Number",
@@ -163,6 +205,7 @@
"vehicle": "Vehicle" "vehicle": "Vehicle"
}, },
"labels": { "labels": {
"appointmentconfirmation": "Send confirmation to customer?",
"available_new_jobs": "", "available_new_jobs": "",
"cards": { "cards": {
"appraiser": "Appraiser", "appraiser": "Appraiser",
@@ -191,7 +234,7 @@
"converted": "Job converted successfully.", "converted": "Job converted successfully.",
"created": "Job created successfully. Click to view.", "created": "Job created successfully. Click to view.",
"deleted": "Job deleted successfully.", "deleted": "Job deleted successfully.",
"save": "Record Saved", "save": "Job saved successfully.",
"savetitle": "Record saved successfully." "savetitle": "Record saved successfully."
} }
}, },
@@ -204,7 +247,8 @@
"activejobs": "Active Jobs", "activejobs": "Active Jobs",
"availablejobs": "Available Jobs", "availablejobs": "Available Jobs",
"home": "Home", "home": "Home",
"jobs": "Jobs" "jobs": "Jobs",
"schedule": "Schedule"
}, },
"jobsdetail": { "jobsdetail": {
"claimdetail": "Claim Details", "claimdetail": "Claim Details",
@@ -267,11 +311,16 @@
"jobsavailable": "Available Jobs | $t(titles.app)", "jobsavailable": "Available Jobs | $t(titles.app)",
"jobsdetail": "Job {{ro_number}} | $t(titles.app)", "jobsdetail": "Job {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Job Documents {{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": { "user": {
"actions": { "actions": {
"signout": "Sign Out" "signout": "Sign Out",
"updateprofile": "Update Profile"
},
"fields": {
"displayname": "Display Name"
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -1,5 +1,45 @@
{ {
"translation": { "translation": {
"appointments": {
"actions": {
"cancel": "Cancelar",
"intake": "Consumo",
"reschedule": "Reprogramar",
"viewjob": "Ver trabajo"
},
"errors": {
"canceling": "Error al cancelar la cita.",
"saving": "Error al programar la cita. {{message}}"
},
"labels": {
"arrivedon": "Llegado el:",
"cancelledappointment": "Cita cancelada para:",
"nodateselected": "No se ha seleccionado ninguna fecha.",
"priorappointments": "Nombramientos previos",
"scheduledfor": "Cita programada para:"
},
"successes": {
"canceled": "Cita cancelada con éxito.",
"created": "Cita programada con éxito."
}
},
"associations": {
"actions": {
"activate": "Activar"
},
"fields": {
"active": "¿Activo?",
"shopname": "Nombre de tienda"
},
"labels": {
"actions": "Comportamiento"
}
},
"bodyshop": {
"errors": {
"loading": "No se pueden cargar los detalles de la tienda. Por favor llame al soporte técnico."
}
},
"documents": { "documents": {
"errors": { "errors": {
"deletes3": "Error al eliminar el documento del almacenamiento.", "deletes3": "Error al eliminar el documento del almacenamiento.",
@@ -52,9 +92,11 @@
"actions": { "actions": {
"addDocuments": "Agregar documentos de trabajo", "addDocuments": "Agregar documentos de trabajo",
"addNote": "Añadir la nota", "addNote": "Añadir la nota",
"changestatus": "Cambiar Estado",
"convert": "Convertir", "convert": "Convertir",
"postInvoices": "Contabilizar facturas", "postInvoices": "Contabilizar facturas",
"printCenter": "Centro de impresión" "printCenter": "Centro de impresión",
"schedule": "Programar"
}, },
"errors": { "errors": {
"creating": "", "creating": "",
@@ -113,8 +155,8 @@
"owner": "Propietario", "owner": "Propietario",
"owner_owing": "Cust. Debe", "owner_owing": "Cust. Debe",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_ph1": "Teléfono 1",
"pay_date": "Fecha de inspección", "pay_date": "Fecha de inspección",
"phone1": "Teléfono 1",
"phoneshort": "PH", "phoneshort": "PH",
"policy_no": "Política #", "policy_no": "Política #",
"ponumber": "numero postal", "ponumber": "numero postal",
@@ -163,6 +205,7 @@
"vehicle": "Vehículo" "vehicle": "Vehículo"
}, },
"labels": { "labels": {
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
"available_new_jobs": "", "available_new_jobs": "",
"cards": { "cards": {
"appraiser": "Tasador", "appraiser": "Tasador",
@@ -191,7 +234,7 @@
"converted": "Trabajo convertido con éxito.", "converted": "Trabajo convertido con éxito.",
"created": "Trabajo creado con éxito. Click para ver.", "created": "Trabajo creado con éxito. Click para ver.",
"deleted": "Trabajo eliminado con éxito.", "deleted": "Trabajo eliminado con éxito.",
"save": "Registro guardado", "save": "Trabajo guardado con éxito.",
"savetitle": "Registro guardado con éxito." "savetitle": "Registro guardado con éxito."
} }
}, },
@@ -204,7 +247,8 @@
"activejobs": "Empleos activos", "activejobs": "Empleos activos",
"availablejobs": "Trabajos disponibles", "availablejobs": "Trabajos disponibles",
"home": "Casa", "home": "Casa",
"jobs": "Trabajos" "jobs": "Trabajos",
"schedule": "Programar"
}, },
"jobsdetail": { "jobsdetail": {
"claimdetail": "Detalles de la reclamación", "claimdetail": "Detalles de la reclamación",
@@ -267,11 +311,16 @@
"jobsavailable": "Empleos disponibles | $t(titles.app)", "jobsavailable": "Empleos disponibles | $t(titles.app)",
"jobsdetail": "Trabajo {{ro_number}} | $t(titles.app)", "jobsdetail": "Trabajo {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Documentos de trabajo {{ro_number}} | $ t (títulos.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": { "user": {
"actions": { "actions": {
"signout": "desconectar" "signout": "desconectar",
"updateprofile": "Actualización del perfil"
},
"fields": {
"displayname": "Nombre para mostrar"
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -1,5 +1,45 @@
{ {
"translation": { "translation": {
"appointments": {
"actions": {
"cancel": "annuler",
"intake": "Admission",
"reschedule": "Replanifier",
"viewjob": "Voir le travail"
},
"errors": {
"canceling": "Erreur lors de l'annulation du rendez-vous.",
"saving": "Erreur lors de la planification du rendez-vous. {{message}}"
},
"labels": {
"arrivedon": "Arrivé le:",
"cancelledappointment": "Rendez-vous annulé pour:",
"nodateselected": "Aucune date n'a été sélectionnée.",
"priorappointments": "Rendez-vous précédents",
"scheduledfor": "Rendez-vous prévu pour:"
},
"successes": {
"canceled": "Rendez-vous annulé avec succès.",
"created": "Rendez-vous planifié avec succès."
}
},
"associations": {
"actions": {
"activate": "Activer"
},
"fields": {
"active": "Actif?",
"shopname": "nom de la boutique"
},
"labels": {
"actions": "actes"
}
},
"bodyshop": {
"errors": {
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique."
}
},
"documents": { "documents": {
"errors": { "errors": {
"deletes3": "Erreur lors de la suppression du document du stockage.", "deletes3": "Erreur lors de la suppression du document du stockage.",
@@ -52,9 +92,11 @@
"actions": { "actions": {
"addDocuments": "Ajouter des documents de travail", "addDocuments": "Ajouter des documents de travail",
"addNote": "Ajouter une note", "addNote": "Ajouter une note",
"changestatus": "Changer le statut",
"convert": "Convertir", "convert": "Convertir",
"postInvoices": "Poster des factures", "postInvoices": "Poster des factures",
"printCenter": "Centre d'impression" "printCenter": "Centre d'impression",
"schedule": "Programme"
}, },
"errors": { "errors": {
"creating": "", "creating": "",
@@ -113,8 +155,8 @@
"owner": "Propriétaire", "owner": "Propriétaire",
"owner_owing": "Cust. Owes", "owner_owing": "Cust. Owes",
"ownr_ea": "Email", "ownr_ea": "Email",
"ownr_ph1": "Téléphone 1",
"pay_date": "Date d'inspection", "pay_date": "Date d'inspection",
"phone1": "Téléphone 1",
"phoneshort": "PH", "phoneshort": "PH",
"policy_no": "Politique #", "policy_no": "Politique #",
"ponumber": "Numéro de bon de commande", "ponumber": "Numéro de bon de commande",
@@ -163,6 +205,7 @@
"vehicle": "Véhicule" "vehicle": "Véhicule"
}, },
"labels": { "labels": {
"appointmentconfirmation": "Envoyer une confirmation au client?",
"available_new_jobs": "", "available_new_jobs": "",
"cards": { "cards": {
"appraiser": "Expert", "appraiser": "Expert",
@@ -191,7 +234,7 @@
"converted": "Travail converti avec succès.", "converted": "Travail converti avec succès.",
"created": "Le travail a été créé avec succès. Clique pour voir.", "created": "Le travail a été créé avec succès. Clique pour voir.",
"deleted": "Le travail a bien été supprimé.", "deleted": "Le travail a bien été supprimé.",
"save": "Enregistrement enregistré", "save": "Le travail a été enregistré avec succès.",
"savetitle": "Enregistrement enregistré avec succès." "savetitle": "Enregistrement enregistré avec succès."
} }
}, },
@@ -204,7 +247,8 @@
"activejobs": "Emplois actifs", "activejobs": "Emplois actifs",
"availablejobs": "Emplois disponibles", "availablejobs": "Emplois disponibles",
"home": "Accueil", "home": "Accueil",
"jobs": "Emplois" "jobs": "Emplois",
"schedule": "Programme"
}, },
"jobsdetail": { "jobsdetail": {
"claimdetail": "Détails de la réclamation", "claimdetail": "Détails de la réclamation",
@@ -267,11 +311,16 @@
"jobsavailable": "Emplois disponibles | $t(titles.app)", "jobsavailable": "Emplois disponibles | $t(titles.app)",
"jobsdetail": "Travail {{ro_number}} | $t(titles.app)", "jobsdetail": "Travail {{ro_number}} | $t(titles.app)",
"jobsdocuments": "Documents de travail {{ro_number}} | $ t (titres.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": { "user": {
"actions": { "actions": {
"signout": "Déconnexion" "signout": "Déconnexion",
"updateprofile": "Mettre à jour le profil"
},
"fields": {
"displayname": "Afficher un nom"
} }
}, },
"vehicles": { "vehicles": {

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");
}

View File

@@ -1026,7 +1026,7 @@
dependencies: dependencies:
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/runtime@^7.5.5": "@babel/runtime@^7.1.5", "@babel/runtime@^7.5.5":
version "7.8.4" version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308"
integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==
@@ -1620,6 +1620,11 @@
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
"@restart/hooks@^0.3.12":
version "0.3.20"
resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.3.20.tgz#e7179ab41e5f346b2feca091261fbdad77e8bc19"
integrity sha512-Q1eeEqcxHQ4oqty7C5Me8/hzWwdCRR643nR/6EHxv8BVxLVYHe4IoWAHg8MIGkE4VtSm3/JnNhkoLJhCkLx5aw==
"@svgr/babel-plugin-add-jsx-attribute@^4.2.0": "@svgr/babel-plugin-add-jsx-attribute@^4.2.0":
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1"
@@ -1839,6 +1844,14 @@
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^2.2.0" csstype "^2.2.0"
"@types/react@^16.9.11":
version "16.9.19"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.19.tgz#c842aa83ea490007d29938146ff2e4d9e4360c40"
integrity sha512-LJV97//H+zqKWMms0kvxaKYJDG05U2TtQB3chRLF8MPNs+MQh/H1aGlyDUxjaHvu08EAGerdX2z4LTBc7ns77A==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"
"@types/stack-utils@^1.0.1": "@types/stack-utils@^1.0.1":
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
@@ -3483,6 +3496,11 @@ clone-deep@^4.0.1:
kind-of "^6.0.2" kind-of "^6.0.2"
shallow-clone "^3.0.0" shallow-clone "^3.0.0"
clsx@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702"
integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA==
co@^4.6.0: co@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -4103,7 +4121,7 @@ cssstyle@^1.0.0, cssstyle@^1.1.1:
dependencies: dependencies:
cssom "0.3.x" cssom "0.3.x"
csstype@^2.2.0: csstype@^2.2.0, csstype@^2.6.7:
version "2.6.8" version "2.6.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431"
integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA== integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA==
@@ -4149,6 +4167,11 @@ data-urls@^1.0.0, data-urls@^1.1.0:
whatwg-mimetype "^2.2.0" whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0" whatwg-url "^7.0.0"
date-arithmetic@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/date-arithmetic/-/date-arithmetic-4.1.0.tgz#e5d6434e9deb71f79760a37b729e4a515e730ddf"
integrity sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -4409,6 +4432,14 @@ dom-converter@^0.2:
dependencies: dependencies:
utila "~0.4" utila "~0.4"
dom-helpers@^5.1.0:
version "5.1.3"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.1.3.tgz#7233248eb3a2d1f74aafca31e52c5299cc8ce821"
integrity sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==
dependencies:
"@babel/runtime" "^7.6.3"
csstype "^2.6.7"
dom-matches@>=1.0.1: dom-matches@>=1.0.1:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/dom-matches/-/dom-matches-2.0.0.tgz#d2728b416a87533980eb089b848d253cf23a758c" resolved "https://registry.yarnpkg.com/dom-matches/-/dom-matches-2.0.0.tgz#d2728b416a87533980eb089b848d253cf23a758c"
@@ -7487,6 +7518,11 @@ locate-path@^3.0.0:
p-locate "^3.0.0" p-locate "^3.0.0"
path-exists "^3.0.0" path-exists "^3.0.0"
lodash-es@^4.17.11:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78"
integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==
lodash._reinterpolate@^3.0.0: lodash._reinterpolate@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d"
@@ -7689,6 +7725,11 @@ mem@^4.0.0:
mimic-fn "^2.0.0" mimic-fn "^2.0.0"
p-is-promise "^2.0.0" p-is-promise "^2.0.0"
memoize-one@^4.0.3:
version "4.1.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906"
integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==
memoize-one@^5.0.0: memoize-one@^5.0.0:
version "5.1.1" version "5.1.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
@@ -8908,6 +8949,11 @@ pnp-webpack-plugin@1.5.0:
dependencies: dependencies:
ts-pnp "^1.1.2" ts-pnp "^1.1.2"
popper.js@^1.15.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
portfinder@^1.0.9: portfinder@^1.0.9:
version "1.0.25" version "1.0.25"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca"
@@ -10342,6 +10388,24 @@ react-app-polyfill@^1.0.4:
regenerator-runtime "^0.13.3" regenerator-runtime "^0.13.3"
whatwg-fetch "^3.0.0" whatwg-fetch "^3.0.0"
react-big-calendar@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-0.23.0.tgz#cc780481548fcefabb04cea05dd1a08456702a67"
integrity sha512-f/v1J/oqvEJqXcT5clJ5wzFIWKRNUeMCvLLzH6PYszcR15mUgqQb31HH+2Ev+YZHVW3YSStcZn2HWEhmvFr9ew==
dependencies:
"@babel/runtime" "^7.1.5"
clsx "^1.0.4"
date-arithmetic "^4.0.1"
dom-helpers "^5.1.0"
invariant "^2.2.4"
lodash "^4.17.11"
lodash-es "^4.17.11"
memoize-one "^4.0.3"
prop-types "^15.6.2"
react-overlays "^2.0.0-0"
uncontrollable "^7.0.0"
warning "^4.0.2"
react-chartjs-2@^2.8.0: react-chartjs-2@^2.8.0:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz#1c24de91fb3755f8c4302675de7d66fdda339759" resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.8.0.tgz#1c24de91fb3755f8c4302675de7d66fdda339759"
@@ -10452,6 +10516,19 @@ react-number-format@^4.3.1:
dependencies: dependencies:
prop-types "^15.7.2" prop-types "^15.7.2"
react-overlays@^2.0.0-0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-2.1.1.tgz#ffe2090c4a10da6b8947a1c7b1a67d0457648a0d"
integrity sha512-gaQJwmb8Ij2IGVt4D1HmLtl4A0mDVYxlsv/8i0dHWK7Mw0kNat6ORelbbEWzaXTK1TqMeQtJw/jraL3WOADz3w==
dependencies:
"@babel/runtime" "^7.4.5"
"@restart/hooks" "^0.3.12"
dom-helpers "^5.1.0"
popper.js "^1.15.0"
prop-types "^15.7.2"
uncontrollable "^7.0.0"
warning "^4.0.3"
react-popopo@^2.1.9: react-popopo@^2.1.9:
version "2.1.9" version "2.1.9"
resolved "https://registry.yarnpkg.com/react-popopo/-/react-popopo-2.1.9.tgz#d93f70a8fb68227907d00c0cea4d8f5d321053ea" resolved "https://registry.yarnpkg.com/react-popopo/-/react-popopo-2.1.9.tgz#d93f70a8fb68227907d00c0cea4d8f5d321053ea"
@@ -12389,6 +12466,16 @@ uglify-js@^3.1.4:
commander "~2.20.3" commander "~2.20.3"
source-map "~0.6.1" source-map "~0.6.1"
uncontrollable@^7.0.0:
version "7.1.1"
resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.1.1.tgz#f67fed3ef93637126571809746323a9db815d556"
integrity sha512-EcPYhot3uWTS3w00R32R2+vS8Vr53tttrvMj/yA1uYRhf8hbTG2GyugGqWDY0qIskxn0uTTojVd6wPYW9ZEf8Q==
dependencies:
"@babel/runtime" "^7.6.3"
"@types/react" "^16.9.11"
invariant "^2.2.4"
react-lifecycles-compat "^3.0.4"
unicode-canonical-property-names-ecmascript@^1.0.4: unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"

View File

@@ -0,0 +1,3 @@
- args:
sql: DROP TABLE "public"."employees"
type: run_sql

View File

@@ -0,0 +1,21 @@
- args:
sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
type: run_sql
- args:
sql: "CREATE TABLE \"public\".\"employees\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
\"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
NOT NULL DEFAULT now(), \"first_name\" text NOT NULL, \"last_name\" text NOT
NULL, \"employee_number\" text, \"shopid\" uuid NOT NULL, \"active\" boolean
NOT NULL DEFAULT true, PRIMARY KEY (\"id\") , FOREIGN KEY (\"shopid\") REFERENCES
\"public\".\"bodyshops\"(\"id\") ON UPDATE cascade ON DELETE cascade);\nCREATE
OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
= NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_employees_updated_at\"\nBEFORE
UPDATE ON \"public\".\"employees\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
ON TRIGGER \"set_public_employees_updated_at\" ON \"public\".\"employees\" \nIS
'trigger to set value of column \"updated_at\" to current timestamp on row update';\n"
type: run_sql
- args:
name: employees
schema: public
type: add_existing_table_or_view

View File

@@ -0,0 +1,12 @@
- args:
relationship: employees
table:
name: bodyshops
schema: public
type: drop_relationship
- args:
relationship: bodyshop
table:
name: employees
schema: public
type: drop_relationship

View File

@@ -0,0 +1,20 @@
- args:
name: employees
table:
name: bodyshops
schema: public
using:
foreign_key_constraint_on:
column: shopid
table:
name: employees
schema: public
type: create_array_relationship
- args:
name: bodyshop
table:
name: employees
schema: public
using:
foreign_key_constraint_on: shopid
type: create_object_relationship

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_insert_permission

View File

@@ -0,0 +1,30 @@
- args:
permission:
allow_upsert: true
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- active
- employee_number
- first_name
- last_name
- created_at
- updated_at
- id
- shopid
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: employees
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,27 @@
- args:
permission:
allow_aggregations: false
columns:
- active
- employee_number
- first_name
- last_name
- created_at
- updated_at
- id
- shopid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
limit: null
role: user
table:
name: employees
schema: public
type: create_select_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_update_permission

View File

@@ -0,0 +1,29 @@
- args:
permission:
columns:
- active
- employee_number
- first_name
- last_name
- created_at
- updated_at
- id
- shopid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: employees
schema: public
type: create_update_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: employees
schema: public
type: drop_delete_permission

View File

@@ -0,0 +1,16 @@
- args:
permission:
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: employees
schema: public
type: create_delete_permission

View File

@@ -0,0 +1,3 @@
- args:
sql: DROP TABLE "public"."appointments"
type: run_sql

View File

@@ -0,0 +1,21 @@
- args:
sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
type: run_sql
- args:
sql: "CREATE TABLE \"public\".\"appointments\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
\"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
NOT NULL DEFAULT now(), \"jobid\" uuid NOT NULL, \"start\" timestamptz NOT NULL,
\"end\" timestamptz NOT NULL, PRIMARY KEY (\"id\") , FOREIGN KEY (\"jobid\")
REFERENCES \"public\".\"jobs\"(\"id\") ON UPDATE cascade ON DELETE cascade);\nCREATE
OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
= NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_appointments_updated_at\"\nBEFORE
UPDATE ON \"public\".\"appointments\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
ON TRIGGER \"set_public_appointments_updated_at\" ON \"public\".\"appointments\"
\nIS 'trigger to set value of column \"updated_at\" to current timestamp on
row update';\n"
type: run_sql
- args:
name: appointments
schema: public
type: add_existing_table_or_view

View File

@@ -0,0 +1,12 @@
- args:
relationship: job
table:
name: appointments
schema: public
type: drop_relationship
- args:
relationship: appointments
table:
name: jobs
schema: public
type: drop_relationship

View File

@@ -0,0 +1,20 @@
- args:
name: job
table:
name: appointments
schema: public
using:
foreign_key_constraint_on: jobid
type: create_object_relationship
- args:
name: appointments
table:
name: jobs
schema: public
using:
foreign_key_constraint_on:
column: jobid
table:
name: appointments
schema: public
type: create_array_relationship

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_insert_permission

View File

@@ -0,0 +1,29 @@
- args:
permission:
allow_upsert: true
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- created_at
- end
- start
- updated_at
- id
- jobid
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: appointments
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,26 @@
- args:
permission:
allow_aggregations: false
columns:
- created_at
- end
- start
- updated_at
- id
- jobid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
limit: null
role: user
table:
name: appointments
schema: public
type: create_select_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_update_permission

View File

@@ -0,0 +1,28 @@
- args:
permission:
columns:
- created_at
- end
- start
- updated_at
- id
- jobid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: appointments
schema: public
type: create_update_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_delete_permission

View File

@@ -0,0 +1,17 @@
- args:
permission:
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: appointments
schema: public
type: create_delete_permission

View File

@@ -0,0 +1,244 @@
- args:
role: user
table:
name: jobs
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- shopid
- ro_number
- ownerid
- vehicleid
- labor_rate_id
- labor_rate_desc
- rate_lab
- rate_lad
- rate_lae
- rate_lar
- rate_las
- rate_laf
- rate_lam
- rate_lag
- rate_atp
- rate_lau
- rate_la1
- rate_la2
- rate_la3
- rate_la4
- rate_mapa
- rate_mash
- rate_mahw
- rate_ma2s
- rate_ma3s
- rate_ma2t
- rate_mabl
- rate_macs
- rate_matd
- federal_tax_rate
- state_tax_rate
- local_tax_rate
- est_co_nm
- est_addr1
- est_addr2
- est_city
- est_st
- est_zip
- est_ctry
- est_ph1
- est_ea
- est_ct_ln
- est_ct_fn
- scheduled_in
- actual_in
- scheduled_completion
- actual_completion
- scheduled_delivery
- actual_delivery
- regie_number
- invoice_date
- claim_total
- deductible
- inproduction
- statusid
- ins_co_id
- ins_co_nm
- ins_addr1
- ins_addr2
- ins_city
- ins_st
- ins_zip
- ins_ctry
- ins_ph1
- ins_ph1x
- ins_ph2
- ins_ph2x
- ins_fax
- ins_faxx
- ins_ct_ln
- ins_ct_fn
- ins_title
- ins_ct_ph
- ins_ct_phx
- ins_ea
- ins_memo
- policy_no
- ded_amt
- ded_status
- asgn_no
- asgn_date
- asgn_type
- clm_no
- clm_ofc_id
- date_estimated
- date_open
- date_scheduled
- date_invoiced
- date_closed
- date_exported
- clm_total
- owner_owing
- converted
- ciecaid
- loss_date
- clm_ofc_nm
- clm_addr1
- clm_addr2
- clm_city
- clm_st
- clm_zip
- clm_ctry
- clm_ph1
- clm_ph1x
- clm_ph2
- clm_ph2x
- clm_fax
- clm_faxx
- clm_ct_ln
- clm_ct_fn
- clm_title
- clm_ct_ph
- clm_ct_phx
- clm_ea
- payee_nms
- pay_type
- pay_date
- pay_chknm
- pay_amt
- agt_co_id
- agt_co_nm
- agt_addr1
- agt_addr2
- agt_city
- agt_st
- agt_zip
- agt_ctry
- agt_ph1
- agt_ph1x
- agt_ph2
- agt_ph2x
- agt_fax
- agt_faxx
- agt_ct_ln
- agt_ct_fn
- agt_ct_ph
- agt_ct_phx
- agt_ea
- agt_lic_no
- loss_type
- loss_desc
- theft_ind
- cat_no
- tlos_ind
- cust_pr
- insd_ln
- insd_fn
- insd_title
- insd_co_nm
- insd_addr1
- insd_addr2
- insd_city
- insd_st
- insd_zip
- insd_ctry
- insd_ph1
- insd_ph1x
- insd_ph2
- insd_ph2x
- insd_fax
- insd_faxx
- insd_ea
- ownr_ln
- ownr_fn
- ownr_title
- ownr_co_nm
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_st
- ownr_zip
- ownr_ctry
- ownr_ph1
- ownr_ph1x
- ownr_ph2
- ownr_ph2x
- ownr_fax
- ownr_faxx
- ownr_ea
- area_of_damage
- loss_cat
- est_number
- service_car
- special_coverage_policy
- csr
- po_number
- unit_number
- kmin
- kmout
- referral_source
- selling_dealer
- servicing_dealer
- servicing_dealer_contact
- selling_dealer_contact
- depreciation_taxes
- federal_tax_payable
- other_amount_payable
- towing_payable
- storage_payable
- adjustment_bottom_line
- tax_pstthr
- tax_tow_rt
- tax_sub_rt
- tax_paint_mat_rt
- tax_levies_rt
- tax_prethr
- tax_thramt
- tax_str_rt
- tax_lbr_rt
- adj_g_disc
- adj_towdis
- adj_strdis
- tax_predis
- rate_laa
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: jobs
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,243 @@
- args:
role: user
table:
name: jobs
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actual_completion
- actual_delivery
- actual_in
- adj_g_disc
- adj_strdis
- adj_towdis
- adjustment_bottom_line
- agt_addr1
- agt_addr2
- agt_city
- agt_co_id
- agt_co_nm
- agt_ct_fn
- agt_ct_ln
- agt_ct_ph
- agt_ct_phx
- agt_ctry
- agt_ea
- agt_fax
- agt_faxx
- agt_lic_no
- agt_ph1
- agt_ph1x
- agt_ph2
- agt_ph2x
- agt_st
- agt_zip
- area_of_damage
- asgn_date
- asgn_no
- asgn_type
- cat_no
- ciecaid
- clm_addr1
- clm_addr2
- clm_city
- clm_ct_fn
- clm_ct_ln
- clm_ct_ph
- clm_ct_phx
- clm_ctry
- clm_ea
- clm_fax
- clm_faxx
- clm_no
- clm_ofc_id
- clm_ofc_nm
- clm_ph1
- clm_ph1x
- clm_ph2
- clm_ph2x
- clm_st
- clm_title
- clm_total
- clm_zip
- converted
- created_at
- csr
- cust_pr
- date_closed
- date_estimated
- date_exported
- date_invoiced
- date_open
- date_scheduled
- ded_amt
- ded_status
- deductible
- depreciation_taxes
- est_addr1
- est_addr2
- est_city
- est_co_nm
- est_ct_fn
- est_ct_ln
- est_ctry
- est_ea
- est_number
- est_ph1
- est_st
- est_zip
- federal_tax_payable
- federal_tax_rate
- id
- inproduction
- ins_addr1
- ins_addr2
- ins_city
- ins_co_id
- ins_co_nm
- ins_ct_fn
- ins_ct_ln
- ins_ct_ph
- ins_ct_phx
- ins_ctry
- ins_ea
- ins_fax
- ins_faxx
- ins_memo
- ins_ph1
- ins_ph1x
- ins_ph2
- ins_ph2x
- ins_st
- ins_title
- ins_zip
- insd_addr1
- insd_addr2
- insd_city
- insd_co_nm
- insd_ctry
- insd_ea
- insd_fax
- insd_faxx
- insd_fn
- insd_ln
- insd_ph1
- insd_ph1x
- insd_ph2
- insd_ph2x
- insd_st
- insd_title
- insd_zip
- invoice_date
- kmin
- kmout
- labor_rate_desc
- labor_rate_id
- local_tax_rate
- loss_cat
- loss_date
- loss_desc
- loss_type
- other_amount_payable
- owner_owing
- ownerid
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_co_nm
- ownr_ctry
- ownr_ea
- ownr_fax
- ownr_faxx
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1x
- ownr_ph2
- ownr_ph2x
- ownr_st
- ownr_title
- ownr_zip
- pay_amt
- pay_chknm
- pay_date
- pay_type
- payee_nms
- po_number
- policy_no
- rate_atp
- rate_la1
- rate_la2
- rate_la3
- rate_la4
- rate_laa
- rate_lab
- rate_lad
- rate_lae
- rate_laf
- rate_lag
- rate_lam
- rate_lar
- rate_las
- rate_lau
- rate_ma2s
- rate_ma2t
- rate_ma3s
- rate_mabl
- rate_macs
- rate_mahw
- rate_mapa
- rate_mash
- rate_matd
- referral_source
- regie_number
- ro_number
- scheduled_completion
- scheduled_delivery
- scheduled_in
- selling_dealer
- selling_dealer_contact
- service_car
- servicing_dealer
- servicing_dealer_contact
- shopid
- special_coverage_policy
- state_tax_rate
- statusid
- storage_payable
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
- tax_predis
- tax_prethr
- tax_pstthr
- tax_str_rt
- tax_sub_rt
- tax_thramt
- tax_tow_rt
- theft_ind
- tlos_ind
- towing_payable
- unit_number
- updated_at
- vehicleid
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: jobs
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,242 @@
- args:
role: user
table:
name: jobs
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- converted
- inproduction
- special_coverage_policy
- theft_ind
- tlos_ind
- asgn_date
- invoice_date
- loss_date
- pay_date
- kmin
- kmout
- est_number
- area_of_damage
- adj_g_disc
- adj_strdis
- adj_towdis
- adjustment_bottom_line
- claim_total
- clm_total
- ded_amt
- deductible
- depreciation_taxes
- federal_tax_payable
- federal_tax_rate
- local_tax_rate
- other_amount_payable
- owner_owing
- pay_amt
- rate_atp
- rate_la1
- rate_la2
- rate_la3
- rate_la4
- rate_laa
- rate_lab
- rate_lad
- rate_lae
- rate_laf
- rate_lag
- rate_lam
- rate_lar
- rate_las
- rate_lau
- rate_ma2s
- rate_ma2t
- rate_ma3s
- rate_mabl
- rate_macs
- rate_mahw
- rate_mapa
- rate_mash
- rate_matd
- state_tax_rate
- storage_payable
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
- tax_predis
- tax_prethr
- tax_str_rt
- tax_thramt
- towing_payable
- agt_addr1
- agt_addr2
- agt_city
- agt_co_id
- agt_co_nm
- agt_ct_fn
- agt_ct_ln
- agt_ct_ph
- agt_ct_phx
- agt_ctry
- agt_ea
- agt_fax
- agt_faxx
- agt_lic_no
- agt_ph1
- agt_ph1x
- agt_ph2
- agt_ph2x
- agt_st
- agt_zip
- asgn_no
- asgn_type
- cat_no
- ciecaid
- clm_addr1
- clm_addr2
- clm_city
- clm_ct_fn
- clm_ct_ln
- clm_ct_ph
- clm_ct_phx
- clm_ctry
- clm_ea
- clm_fax
- clm_faxx
- clm_no
- clm_ofc_id
- clm_ofc_nm
- clm_ph1
- clm_ph1x
- clm_ph2
- clm_ph2x
- clm_st
- clm_title
- clm_zip
- csr
- cust_pr
- ded_status
- est_addr1
- est_addr2
- est_city
- est_co_nm
- est_ct_fn
- est_ct_ln
- est_ctry
- est_ea
- est_ph1
- est_st
- est_zip
- ins_addr1
- ins_addr2
- ins_city
- ins_co_id
- ins_co_nm
- ins_ct_fn
- ins_ct_ln
- ins_ct_ph
- ins_ct_phx
- ins_ctry
- insd_addr1
- insd_addr2
- insd_city
- insd_co_nm
- insd_ctry
- insd_ea
- insd_fax
- insd_faxx
- insd_fn
- insd_ln
- insd_ph1
- insd_ph1x
- insd_ph2
- insd_ph2x
- insd_st
- insd_title
- insd_zip
- ins_ea
- ins_fax
- ins_faxx
- ins_memo
- ins_ph1
- ins_ph1x
- ins_ph2
- ins_ph2x
- ins_st
- ins_title
- ins_zip
- labor_rate_desc
- labor_rate_id
- loss_cat
- loss_desc
- loss_type
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_co_nm
- ownr_ctry
- ownr_ea
- ownr_fax
- ownr_faxx
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1x
- ownr_ph2
- ownr_ph2x
- ownr_st
- ownr_title
- ownr_zip
- pay_chknm
- payee_nms
- pay_type
- policy_no
- po_number
- referral_source
- regie_number
- ro_number
- selling_dealer
- selling_dealer_contact
- service_car
- servicing_dealer
- servicing_dealer_contact
- unit_number
- actual_completion
- actual_delivery
- actual_in
- created_at
- date_closed
- date_estimated
- date_exported
- date_invoiced
- date_open
- date_scheduled
- scheduled_completion
- scheduled_delivery
- scheduled_in
- updated_at
- id
- ownerid
- shopid
- statusid
- vehicleid
- tax_pstthr
- tax_sub_rt
- tax_tow_rt
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: jobs
schema: public
type: create_select_permission

View File

@@ -0,0 +1,241 @@
- args:
role: user
table:
name: jobs
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actual_completion
- actual_delivery
- actual_in
- adj_g_disc
- adj_strdis
- adj_towdis
- adjustment_bottom_line
- agt_addr1
- agt_addr2
- agt_city
- agt_co_id
- agt_co_nm
- agt_ct_fn
- agt_ct_ln
- agt_ct_ph
- agt_ct_phx
- agt_ctry
- agt_ea
- agt_fax
- agt_faxx
- agt_lic_no
- agt_ph1
- agt_ph1x
- agt_ph2
- agt_ph2x
- agt_st
- agt_zip
- area_of_damage
- asgn_date
- asgn_no
- asgn_type
- cat_no
- ciecaid
- clm_addr1
- clm_addr2
- clm_city
- clm_ct_fn
- clm_ct_ln
- clm_ct_ph
- clm_ct_phx
- clm_ctry
- clm_ea
- clm_fax
- clm_faxx
- clm_no
- clm_ofc_id
- clm_ofc_nm
- clm_ph1
- clm_ph1x
- clm_ph2
- clm_ph2x
- clm_st
- clm_title
- clm_total
- clm_zip
- converted
- created_at
- csr
- cust_pr
- date_closed
- date_estimated
- date_exported
- date_invoiced
- date_open
- date_scheduled
- ded_amt
- ded_status
- deductible
- depreciation_taxes
- est_addr1
- est_addr2
- est_city
- est_co_nm
- est_ct_fn
- est_ct_ln
- est_ctry
- est_ea
- est_number
- est_ph1
- est_st
- est_zip
- federal_tax_payable
- federal_tax_rate
- id
- inproduction
- ins_addr1
- ins_addr2
- ins_city
- ins_co_id
- ins_co_nm
- ins_ct_fn
- ins_ct_ln
- ins_ct_ph
- ins_ct_phx
- ins_ctry
- ins_ea
- ins_fax
- ins_faxx
- ins_memo
- ins_ph1
- ins_ph1x
- ins_ph2
- ins_ph2x
- ins_st
- ins_title
- ins_zip
- insd_addr1
- insd_addr2
- insd_city
- insd_co_nm
- insd_ctry
- insd_ea
- insd_fax
- insd_faxx
- insd_fn
- insd_ln
- insd_ph1
- insd_ph1x
- insd_ph2
- insd_ph2x
- insd_st
- insd_title
- insd_zip
- invoice_date
- kmin
- kmout
- labor_rate_desc
- labor_rate_id
- local_tax_rate
- loss_cat
- loss_date
- loss_desc
- loss_type
- other_amount_payable
- owner_owing
- ownerid
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_co_nm
- ownr_ctry
- ownr_ea
- ownr_fax
- ownr_faxx
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1x
- ownr_ph2
- ownr_ph2x
- ownr_st
- ownr_title
- ownr_zip
- pay_amt
- pay_chknm
- pay_date
- pay_type
- payee_nms
- po_number
- policy_no
- rate_atp
- rate_la1
- rate_la2
- rate_la3
- rate_la4
- rate_laa
- rate_lab
- rate_lad
- rate_lae
- rate_laf
- rate_lag
- rate_lam
- rate_lar
- rate_las
- rate_lau
- rate_ma2s
- rate_ma2t
- rate_ma3s
- rate_mabl
- rate_macs
- rate_mahw
- rate_mapa
- rate_mash
- rate_matd
- referral_source
- regie_number
- ro_number
- scheduled_completion
- scheduled_delivery
- scheduled_in
- selling_dealer
- selling_dealer_contact
- service_car
- servicing_dealer
- servicing_dealer_contact
- shopid
- special_coverage_policy
- state_tax_rate
- statusid
- storage_payable
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
- tax_predis
- tax_prethr
- tax_pstthr
- tax_str_rt
- tax_sub_rt
- tax_thramt
- tax_tow_rt
- theft_ind
- tlos_ind
- towing_payable
- unit_number
- updated_at
- vehicleid
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: jobs
schema: public
type: create_select_permission

View File

@@ -0,0 +1,244 @@
- args:
role: user
table:
name: jobs
schema: public
type: drop_update_permission
- args:
permission:
columns:
- converted
- inproduction
- special_coverage_policy
- theft_ind
- tlos_ind
- asgn_date
- invoice_date
- loss_date
- pay_date
- kmin
- kmout
- est_number
- area_of_damage
- adj_g_disc
- adj_strdis
- adj_towdis
- adjustment_bottom_line
- claim_total
- clm_total
- ded_amt
- deductible
- depreciation_taxes
- federal_tax_payable
- federal_tax_rate
- local_tax_rate
- other_amount_payable
- owner_owing
- pay_amt
- rate_atp
- rate_la1
- rate_la2
- rate_la3
- rate_la4
- rate_laa
- rate_lab
- rate_lad
- rate_lae
- rate_laf
- rate_lag
- rate_lam
- rate_lar
- rate_las
- rate_lau
- rate_ma2s
- rate_ma2t
- rate_ma3s
- rate_mabl
- rate_macs
- rate_mahw
- rate_mapa
- rate_mash
- rate_matd
- state_tax_rate
- storage_payable
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
- tax_predis
- tax_prethr
- tax_str_rt
- tax_thramt
- towing_payable
- agt_addr1
- agt_addr2
- agt_city
- agt_co_id
- agt_co_nm
- agt_ct_fn
- agt_ct_ln
- agt_ct_ph
- agt_ct_phx
- agt_ctry
- agt_ea
- agt_fax
- agt_faxx
- agt_lic_no
- agt_ph1
- agt_ph1x
- agt_ph2
- agt_ph2x
- agt_st
- agt_zip
- asgn_no
- asgn_type
- cat_no
- ciecaid
- clm_addr1
- clm_addr2
- clm_city
- clm_ct_fn
- clm_ct_ln
- clm_ct_ph
- clm_ct_phx
- clm_ctry
- clm_ea
- clm_fax
- clm_faxx
- clm_no
- clm_ofc_id
- clm_ofc_nm
- clm_ph1
- clm_ph1x
- clm_ph2
- clm_ph2x
- clm_st
- clm_title
- clm_zip
- csr
- cust_pr
- ded_status
- est_addr1
- est_addr2
- est_city
- est_co_nm
- est_ct_fn
- est_ct_ln
- est_ctry
- est_ea
- est_ph1
- est_st
- est_zip
- ins_addr1
- ins_addr2
- ins_city
- ins_co_id
- ins_co_nm
- ins_ct_fn
- ins_ct_ln
- ins_ct_ph
- ins_ct_phx
- ins_ctry
- insd_addr1
- insd_addr2
- insd_city
- insd_co_nm
- insd_ctry
- insd_ea
- insd_fax
- insd_faxx
- insd_fn
- insd_ln
- insd_ph1
- insd_ph1x
- insd_ph2
- insd_ph2x
- insd_st
- insd_title
- insd_zip
- ins_ea
- ins_fax
- ins_faxx
- ins_memo
- ins_ph1
- ins_ph1x
- ins_ph2
- ins_ph2x
- ins_st
- ins_title
- ins_zip
- labor_rate_desc
- labor_rate_id
- loss_cat
- loss_desc
- loss_type
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_co_nm
- ownr_ctry
- ownr_ea
- ownr_fax
- ownr_faxx
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1x
- ownr_ph2
- ownr_ph2x
- ownr_st
- ownr_title
- ownr_zip
- pay_chknm
- payee_nms
- pay_type
- policy_no
- po_number
- referral_source
- regie_number
- ro_number
- selling_dealer
- selling_dealer_contact
- service_car
- servicing_dealer
- servicing_dealer_contact
- unit_number
- actual_completion
- actual_delivery
- actual_in
- created_at
- date_closed
- date_estimated
- date_exported
- date_invoiced
- date_open
- date_scheduled
- scheduled_completion
- scheduled_delivery
- scheduled_in
- updated_at
- id
- ownerid
- shopid
- statusid
- vehicleid
- tax_pstthr
- tax_sub_rt
- tax_tow_rt
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: jobs
schema: public
type: create_update_permission

View File

@@ -0,0 +1,243 @@
- args:
role: user
table:
name: jobs
schema: public
type: drop_update_permission
- args:
permission:
columns:
- actual_completion
- actual_delivery
- actual_in
- adj_g_disc
- adj_strdis
- adj_towdis
- adjustment_bottom_line
- agt_addr1
- agt_addr2
- agt_city
- agt_co_id
- agt_co_nm
- agt_ct_fn
- agt_ct_ln
- agt_ct_ph
- agt_ct_phx
- agt_ctry
- agt_ea
- agt_fax
- agt_faxx
- agt_lic_no
- agt_ph1
- agt_ph1x
- agt_ph2
- agt_ph2x
- agt_st
- agt_zip
- area_of_damage
- asgn_date
- asgn_no
- asgn_type
- cat_no
- ciecaid
- clm_addr1
- clm_addr2
- clm_city
- clm_ct_fn
- clm_ct_ln
- clm_ct_ph
- clm_ct_phx
- clm_ctry
- clm_ea
- clm_fax
- clm_faxx
- clm_no
- clm_ofc_id
- clm_ofc_nm
- clm_ph1
- clm_ph1x
- clm_ph2
- clm_ph2x
- clm_st
- clm_title
- clm_total
- clm_zip
- converted
- created_at
- csr
- cust_pr
- date_closed
- date_estimated
- date_exported
- date_invoiced
- date_open
- date_scheduled
- ded_amt
- ded_status
- deductible
- depreciation_taxes
- est_addr1
- est_addr2
- est_city
- est_co_nm
- est_ct_fn
- est_ct_ln
- est_ctry
- est_ea
- est_number
- est_ph1
- est_st
- est_zip
- federal_tax_payable
- federal_tax_rate
- id
- inproduction
- ins_addr1
- ins_addr2
- ins_city
- ins_co_id
- ins_co_nm
- ins_ct_fn
- ins_ct_ln
- ins_ct_ph
- ins_ct_phx
- ins_ctry
- ins_ea
- ins_fax
- ins_faxx
- ins_memo
- ins_ph1
- ins_ph1x
- ins_ph2
- ins_ph2x
- ins_st
- ins_title
- ins_zip
- insd_addr1
- insd_addr2
- insd_city
- insd_co_nm
- insd_ctry
- insd_ea
- insd_fax
- insd_faxx
- insd_fn
- insd_ln
- insd_ph1
- insd_ph1x
- insd_ph2
- insd_ph2x
- insd_st
- insd_title
- insd_zip
- invoice_date
- kmin
- kmout
- labor_rate_desc
- labor_rate_id
- local_tax_rate
- loss_cat
- loss_date
- loss_desc
- loss_type
- other_amount_payable
- owner_owing
- ownerid
- ownr_addr1
- ownr_addr2
- ownr_city
- ownr_co_nm
- ownr_ctry
- ownr_ea
- ownr_fax
- ownr_faxx
- ownr_fn
- ownr_ln
- ownr_ph1
- ownr_ph1x
- ownr_ph2
- ownr_ph2x
- ownr_st
- ownr_title
- ownr_zip
- pay_amt
- pay_chknm
- pay_date
- pay_type
- payee_nms
- po_number
- policy_no
- rate_atp
- rate_la1
- rate_la2
- rate_la3
- rate_la4
- rate_laa
- rate_lab
- rate_lad
- rate_lae
- rate_laf
- rate_lag
- rate_lam
- rate_lar
- rate_las
- rate_lau
- rate_ma2s
- rate_ma2t
- rate_ma3s
- rate_mabl
- rate_macs
- rate_mahw
- rate_mapa
- rate_mash
- rate_matd
- referral_source
- regie_number
- ro_number
- scheduled_completion
- scheduled_delivery
- scheduled_in
- selling_dealer
- selling_dealer_contact
- service_car
- servicing_dealer
- servicing_dealer_contact
- shopid
- special_coverage_policy
- state_tax_rate
- statusid
- storage_payable
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
- tax_predis
- tax_prethr
- tax_pstthr
- tax_str_rt
- tax_sub_rt
- tax_thramt
- tax_tow_rt
- theft_ind
- tlos_ind
- towing_payable
- unit_number
- updated_at
- vehicleid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: jobs
schema: public
type: create_update_permission

View File

@@ -0,0 +1,9 @@
- args:
sql: ALTER TABLE "public"."jobs" ADD COLUMN "claim_total" numeric
type: run_sql
- args:
sql: ALTER TABLE "public"."jobs" ALTER COLUMN "claim_total" DROP NOT NULL
type: run_sql
- args:
sql: ALTER TABLE "public"."jobs" ALTER COLUMN "claim_total" SET DEFAULT 0
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."jobs" DROP COLUMN "claim_total" CASCADE
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
sql: ALTER TABLE ONLY "public"."jobs" ALTER COLUMN "clm_total" SET DEFAULT 0;
type: run_sql
- args:
sql: COMMENT ON COLUMN "public"."jobs"."clm_total" IS E'null'
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
sql: ALTER TABLE ONLY "public"."jobs" ALTER COLUMN "clm_total" SET DEFAULT 0;
type: run_sql
- args:
sql: COMMENT ON COLUMN "public"."jobs"."clm_total" IS E''
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
role: anonymous
table:
name: users
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,12 @@
- args:
permission:
allow_aggregations: false
columns:
- authid
filter: {}
limit: null
role: anonymous
table:
name: users
schema: public
type: create_select_permission

View File

@@ -0,0 +1,17 @@
- args:
permission:
columns:
- authid
- email
- created_at
- updated_at
filter: {}
localPresets:
- key: ""
value: ""
set: {}
role: anonymous
table:
name: users
schema: public
type: create_update_permission

View File

@@ -0,0 +1,6 @@
- args:
role: anonymous
table:
name: users
schema: public
type: drop_update_permission

View File

@@ -0,0 +1,17 @@
- args:
permission:
check: {}
columns:
- authid
- created_at
- email
- updated_at
localPresets:
- key: ""
value: ""
set: {}
role: anonymous
table:
name: users
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,6 @@
- args:
role: anonymous
table:
name: users
schema: public
type: drop_insert_permission

View File

@@ -0,0 +1,12 @@
- args:
permission:
allow_aggregations: false
columns:
- authid
computed_fields: []
filter: {}
role: anonymous
table:
name: users
schema: public
type: create_select_permission

View File

@@ -0,0 +1,6 @@
- args:
role: anonymous
table:
name: users
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."appointments" DROP COLUMN "canceled";
type: run_sql

View File

@@ -0,0 +1,4 @@
- args:
sql: ALTER TABLE "public"."appointments" ADD COLUMN "canceled" boolean NOT NULL
DEFAULT false;
type: run_sql

View File

@@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "public"."appointments" DROP COLUMN "arrived";
type: run_sql

View File

@@ -0,0 +1,4 @@
- args:
sql: ALTER TABLE "public"."appointments" ADD COLUMN "arrived" boolean NOT NULL
DEFAULT false;
type: run_sql

Some files were not shown because too many files have changed in this diff Show More