Table updates for new appointments fields. Schedule modal baseline functionality. Refactor schedule components to be more reusable.

This commit is contained in:
Patrick Fic
2020-02-06 13:39:39 -08:00
parent 1a14fb8da6
commit fae1e8cdeb
35 changed files with 785 additions and 67 deletions

View File

@@ -18,6 +18,178 @@
<folder_node>
<name>translation</name>
<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>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>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>
</children>
</folder_node>
<folder_node>
<name>successes</name>
<children>
<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>
@@ -3012,6 +3184,27 @@
<folder_node>
<name>labels</name>
<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>
<name>available_new_jobs</name>
<definition_loaded>false</definition_loaded>

View File

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

View File

@@ -22,7 +22,7 @@ export default function JobsDetailHeader({
scheduleModalState
}) {
const { t } = useTranslation();
const [scheduleModalVisible, setscheduleModalVisible] = scheduleModalState;
const setscheduleModalVisible = scheduleModalState[1];
const tombstoneTitle = (
<div>

View File

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

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

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

View File

@@ -1,14 +1,17 @@
import React from "react";
import { useQuery } from "react-apollo";
import ScheduleCalendarComponent from "./schedule-calendar.component";
import { QUERY_ALL_APPOINTMENTS } from "../../graphql/appointments.queries";
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_APPOINTMENTS, {
fetchPolicy: "network-only"
});
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" />;

View File

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

@@ -5,7 +5,7 @@ import PhoneFormatter from "../../utils/PhoneFormatter";
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";
export default function Event({ event }) {
export default function ScheduleEventComponent({ event, handleCancel }) {
const { t } = useTranslation();
const popoverContent = (
<div>
@@ -23,19 +23,35 @@ export default function Event({ event }) {
<PhoneFormatter>{event.job.ownr_ph1}</PhoneFormatter>
</div>
<Link to={`/manage/jobs/${event.job.id}`}>
<Button>View Job</Button>
<Button>{t("appointments.actions.viewjob")}</Button>
</Link>
<Button>//TODO: Reschedule</Button>
<Button>//TODO: Intake</Button>
<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}>
<strong>{`${event.job.ownr_fn || ""} ${event.job.ownr_ln || ""}`}</strong>
<div>
{`${event.job.vehicle.v_model_yr || ""} ${event.job.vehicle
.v_make_desc || ""} ${event.job.vehicle.v_model_desc || ""}`}
<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,16 @@
import React from "react";
import { useMutation } from "react-apollo";
import { CANCEL_APPOINTMENT_BY_ID } from "../../graphql/appointments.queries";
import ScheduleEventComponent from "./schedule-event.component";
export default function ScheduleEventContainer({ event, refetch }) {
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
console.log("refetch", refetch);
const handleCancel = id => {
cancelAppointment({ variables: { appid: event.id } }).then(r => {
if (refetch) refetch();
});
};
return <ScheduleEventComponent event={event} handleCancel={handleCancel} />;
}

View File

@@ -1,35 +1,57 @@
import { Checkbox, DatePicker, Modal, Tabs, TimePicker, Col, Row } from "antd";
import React from "react";
import { Modal, Tabs, DatePicker, TimePicker } from "antd";
import moment from "moment";
import { useTranslation } from "react-i18next";
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
export default function ScheduleJobModalComponent({
appData,
setAppData,
formData,
setFormData,
...props
}) {
const { t } = useTranslation();
return (
<Modal {...props} maskClosable={false}>
<Modal {...props} width={"80%"} maskClosable={false}>
<Tabs defaultActiveKey="1">
<Tabs.TabPane tab="SMART Scheduling" key="auto">
Automatic Job Selection.
</Tabs.TabPane>
<Tabs.TabPane tab="Manual Scheduling" key="manual">
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>
<Col span={14}>
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 });
}}
/>
</Col>
<Col span={10}>
<ScheduleDayViewContainer day={appData.start} />
</Col>
</Row>
</Tabs.TabPane>
</Tabs>
{
//TODO: Build out notifications.
}
<Checkbox
defaultChecked={formData.notifyCustomer}
onChange={e =>
setFormData({ ...formData, notifyCustomer: e.target.checked })
}
>
{t("jobs.labels.appointmentconfirmation")}
</Checkbox>
</Modal>
);
}

View File

@@ -3,7 +3,8 @@ import ScheduleJobModalComponent from "./schedule-job-modal.component";
import { useMutation } from "react-apollo";
import { INSERT_APPOINTMENT } from "../../graphql/appointments.queries";
import moment from "moment";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
export default function ScheduleJobModalContainer({
scheduleModalState,
jobId
@@ -11,11 +12,15 @@ export default function ScheduleJobModalContainer({
const [scheduleModalVisible, setscheduleModalVisible] = scheduleModalState;
const [appData, setAppData] = useState({ jobid: jobId, start: null });
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
const [formData, setFormData] = useState({ notifyCustomer: false });
const { t } = useTranslation();
return (
<ScheduleJobModalComponent
appData={appData}
setAppData={setAppData}
formData={formData}
setFormData={setFormData}
//Spreadable Modal Props
visible={scheduleModalVisible}
onCancel={() => setscheduleModalVisible(false)}
@@ -25,9 +30,25 @@ export default function ScheduleJobModalContainer({
variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") }
}
}).then(r => {
setscheduleModalVisible(false);
});
})
.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);
})
.catch(error => {
notification["error"]({
message: t("appointments.errors.saving", {
message: error.message
})
});
});
}}
/>
);

View File

@@ -1,8 +1,8 @@
import { gql } from "apollo-boost";
export const QUERY_ALL_APPOINTMENTS = gql`
query QUERY_ALL_APPOINTMENTS {
appointments {
export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
query QUERY_ALL_ACTIVE_APPOINTMENTS {
appointments(where: { canceled: { _eq: false } }) {
start
id
end
@@ -35,3 +35,42 @@ export const INSERT_APPOINTMENT = gql`
}
}
`;
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
}
}
}
`;

View File

@@ -26,6 +26,9 @@ import UserActionTypes from "./user.types";
export function* signInWithEmail({ payload: { email, password } }) {
try {
const { user } = yield auth.signInWithEmailAndPassword(email, password);
let token = yield user.getIdToken();
localStorage.setItem("token", token);
window.sessionStorage.setItem(`lastTokenRefreshTime`, new Date());
yield put(
signInSuccess({
uid: user.uid,

View File

@@ -1,5 +1,22 @@
{
"translation": {
"appointments": {
"actions": {
"cancel": "Cancel",
"intake": "Intake",
"reschedule": "Reschedule",
"viewjob": "View Job"
},
"errors": {
"saving": "Error scheduling appointment. {{message}}"
},
"labels": {
"nodateselected": "No date has been selected."
},
"successes": {
"created": "Appointment scheduled successfully."
}
},
"associations": {
"actions": {
"activate": "Activate"
@@ -176,6 +193,7 @@
"vehicle": "Vehicle"
},
"labels": {
"appointmentconfirmation": "Send confirmation to customer?",
"available_new_jobs": "",
"cards": {
"appraiser": "Appraiser",

View File

@@ -1,5 +1,22 @@
{
"translation": {
"appointments": {
"actions": {
"cancel": "Cancelar",
"intake": "Consumo",
"reschedule": "Reprogramar",
"viewjob": "Ver trabajo"
},
"errors": {
"saving": "Error al programar la cita. {{message}}"
},
"labels": {
"nodateselected": "No se ha seleccionado ninguna fecha."
},
"successes": {
"created": "Cita programada con éxito."
}
},
"associations": {
"actions": {
"activate": "Activar"
@@ -176,6 +193,7 @@
"vehicle": "Vehículo"
},
"labels": {
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
"available_new_jobs": "",
"cards": {
"appraiser": "Tasador",

View File

@@ -1,5 +1,22 @@
{
"translation": {
"appointments": {
"actions": {
"cancel": "annuler",
"intake": "Admission",
"reschedule": "Replanifier",
"viewjob": "Voir le travail"
},
"errors": {
"saving": "Erreur lors de la planification du rendez-vous. {{message}}"
},
"labels": {
"nodateselected": "Aucune date n'a été sélectionnée."
},
"successes": {
"created": "Rendez-vous planifié avec succès."
}
},
"associations": {
"actions": {
"activate": "Activer"
@@ -176,6 +193,7 @@
"vehicle": "Véhicule"
},
"labels": {
"appointmentconfirmation": "Envoyer une confirmation au client?",
"available_new_jobs": "",
"cards": {
"appraiser": "Expert",

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

View File

@@ -0,0 +1,34 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_insert_permission
- args:
permission:
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,36 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_insert_permission
- args:
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- arrived
- canceled
- 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,32 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- created_at
- end
- start
- updated_at
- id
- jobid
computed_fields: []
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: appointments
schema: public
type: create_select_permission

View File

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

View File

@@ -0,0 +1,34 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_update_permission
- 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,36 @@
- args:
role: user
table:
name: appointments
schema: public
type: drop_update_permission
- args:
permission:
columns:
- arrived
- canceled
- 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