IO-924 Rest of phonebook implementation

This commit is contained in:
Patrick Fic
2021-04-23 09:11:48 -07:00
parent 084c7d9c74
commit ddbb1df9d9
18 changed files with 1331 additions and 1 deletions

View File

@@ -4531,6 +4531,53 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>phonebook</name>
<children>
<concept_node>
<name>edit</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>view</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>production</name>
<children>
@@ -23265,6 +23312,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>phonebook</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>productionboard</name>
<definition_loaded>false</definition_loaded>
@@ -26862,6 +26930,320 @@
</folder_node>
</children>
</folder_node>
<folder_node>
<name>phonebook</name>
<children>
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>new</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>city</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>company</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>country</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>email</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>fax</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>firstname</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>lastname</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>phone1</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>phone2</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>state</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>street1</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>street2</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>saved</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>printcenter</name>
<children>
@@ -28375,6 +28757,32 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>payments</name>
<children>
<concept_node>
<name>ca_bc_etf_table</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>vendors</name>
<children>
@@ -31961,6 +32369,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>phonebook</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>productionboard</name>
<definition_loaded>false</definition_loaded>
@@ -32740,6 +33169,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>phonebook</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>productionboard</name>
<definition_loaded>false</definition_loaded>

View File

@@ -276,6 +276,9 @@ function Header({
{t("menus.header.temporarydocs")}
</Link>
</Menu.Item>
<Menu.Item key="phonebook">
<Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
</Menu.Item>
{
// <Menu.Item key="shop-templates">
// <Link to="/manage/shop/templates">

View File

@@ -0,0 +1,122 @@
import { Button, Form, Input, PageHeader, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, {
PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function PhonebookFormComponent({
form,
formLoading,
handleDelete,
}) {
const { t } = useTranslation();
const { getFieldValue } = form;
return (
<div>
<PageHeader
title={`${form.getFieldValue("firstname") || ""} ${
form.getFieldValue("lastname") || ""
}${
form.getFieldValue("company")
? ` - ${form.getFieldValue("company")}`
: ""
}`}
extra={
<Space>
<Button
onClick={() => form.submit()}
type="primary"
loading={formLoading}
>
{t("general.actions.save")}
</Button>
<Button type="danger" onClick={handleDelete} loading={formLoading}>
{t("general.actions.delete")}
</Button>
</Space>
}
/>
<FormFieldsChanged form={form} />
<LayoutFormRow grow>
<Form.Item label={t("phonebook.fields.firstname")} name="firstname">
<Input />
</Form.Item>
<Form.Item label={t("phonebook.fields.lastname")} name="lastname">
<Input />
</Form.Item>
<Form.Item label={t("phonebook.fields.company")} name="company">
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("phonebook.fields.street1")} name="street1">
<Input />
</Form.Item>
<Form.Item label={t("phonebook.fields.street2")} name="street2">
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item label={t("phonebook.fields.city")} name="city">
<Input />
</Form.Item>
<Form.Item label={t("phonebook.fields.state")} name="state">
<Input />
</Form.Item>
<Form.Item label={t("phonebook.fields.country")} name="country">
<Input />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item
label={t("phonebook.fields.email")}
rules={[
{
type: "email",
message: t("general.validation.invalidemail"),
},
]}
name="email"
>
<FormItemEmail email={getFieldValue("email")} />
</Form.Item>
<Form.Item
label={t("phonebook.fields.phone1")}
name="phone1"
rules={[
({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "phone1"),
]}
>
<PhoneFormItem />
</Form.Item>
<Form.Item
label={t("phonebook.fields.phone2")}
name="phone2"
rules={[
({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "phone2"),
]}
>
<PhoneFormItem />
</Form.Item>
<Form.Item
label={t("phonebook.fields.fax")}
name="fax"
rules={[
({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "fax"),
]}
>
<PhoneFormItem />
</Form.Item>
</LayoutFormRow>
</div>
);
}

View File

@@ -0,0 +1,151 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
DELETE_PHONEBOOK,
INSERT_NEW_PHONEBOOK,
QUERY_PHONEBOOK_BY_ID,
UPDATE_PHONEBOOK,
} from "../../graphql/phonebook.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PhonebookFormComponent from "./phonebook-form.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
function PhonebookFormContainer({ refetch, bodyshop }) {
const history = useHistory();
const search = queryString.parse(useLocation().search);
const { phonebookentry } = search;
const [formLoading, setFormLoading] = useState(false);
const [form] = Form.useForm();
const { t } = useTranslation();
const { loading, error, data } = useQuery(QUERY_PHONEBOOK_BY_ID, {
variables: { id: phonebookentry },
fetchPolicy: "network-only",
skip: !!!phonebookentry || phonebookentry === "new",
});
const [updatePhonebook] = useMutation(UPDATE_PHONEBOOK);
const [insertPhonebook] = useMutation(INSERT_NEW_PHONEBOOK);
const [deletePhonebook] = useMutation(DELETE_PHONEBOOK);
const handleDelete = async () => {
setFormLoading(true);
const result = await deletePhonebook({
variables: { id: phonebookentry },
refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"],
});
if (!result.errors) {
notification["success"]({
message: t("phonebook.successes.deleted"),
});
delete search.phonebookentry;
history.push({ search: queryString.stringify(search) });
if (refetch)
refetch().then((r) => {
form.resetFields();
});
} else {
notification["error"]({
message: t("phonebook.errors.deleting", {
message: JSON.stringify(result.errors),
}),
});
}
setFormLoading(false);
};
const handleFinish = async (values) => {
setFormLoading(true);
if (phonebookentry && phonebookentry !== "new") {
//It's a phonebook to update.
const result = await updatePhonebook({
variables: { id: phonebookentry, phonebook: values },
refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"],
});
if (!result.errors) {
notification["success"]({
message: t("Phonebook.successes.saved"),
});
if (refetch) await refetch();
form.setFieldsValue(data.phonebook_by_pk);
form.resetFields();
setFormLoading(false);
} else {
notification["error"]({
message: t("Phonebook.errors.saving"),
});
setFormLoading(false);
}
} else {
//It's a new phonebook to insert.
const result = await insertPhonebook({
variables: {
phonebook_entry: [{ ...values, bodyshopid: bodyshop.id }],
},
refetchQueries: ["QUERY_PHONEBOOK_PAGINATED"],
});
if (!result.errors) {
notification["success"]({
message: t("phonebook.successes.saved"),
});
if (refetch) await refetch();
form.resetFields();
form.resetFields();
delete search.phonebookentry;
history.push({ search: queryString.stringify(search) });
setFormLoading(false);
} else {
notification["error"]({
message: t("phonebook.errors.saving"),
});
setFormLoading(false);
}
}
};
useEffect(() => {
if (data || phonebookentry === "new") form.resetFields();
}, [data, form, phonebookentry]);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Form
onFinish={handleFinish}
form={form}
layout="vertical"
autoComplete="new-password"
initialValues={data ? data.phonebook_by_pk : null}
>
{phonebookentry ? (
<PhonebookFormComponent
form={form}
formLoading={formLoading}
handleDelete={handleDelete}
/>
) : (
t("phonebook.labels.noneselected")
)}
</Form>
);
}
export default connect(mapStateToProps, null)(PhonebookFormContainer);

View File

@@ -39,6 +39,9 @@ const ret = {
"payments:enter": 3,
"payments:list": 3,
"phonebook:view": 1,
"phonebook:edit": 2,
"production:board": 1,
"production:list": 1,

View File

@@ -561,6 +561,30 @@ export default function ShopInfoRbacComponent({ form }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.view")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.edit")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:edit"]}
>
<InputNumber />
</Form.Item>
</LayoutFormRow>
</RbacWrapper>
);

View File

@@ -84,6 +84,44 @@ export const INSERT_EXPORT_LOG = gql`
}
`;
export const QUERY_PHONEBOOK_PAGINATED = gql`
query QUERY_PHONEBOOK_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [phonebook_order_by!]
) {
search_phonebook(
offset: $offset
limit: $limit
order_by: $order
args: { search: $search }
) {
id
created_at
firstname
lastname
phone1
phone2
state
zip
fax
email
country
company
city
category
address2
address1
}
search_phonebook_aggregate(args: { search: $search }) {
aggregate {
count(distinct: true)
}
}
}
`;
export const QUERY_EXPORT_LOG_PAGINATED = gql`
query QUERY_ALL_EXPORTLOG_PAGINATED(
$search: String

View File

@@ -0,0 +1,92 @@
import { gql } from "@apollo/client";
export const QUERY_PHONEBOOK_PAGINATED = gql`
query QUERY_PHONEBOOK_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [phonebook_order_by!]
) {
search_phonebook(
offset: $offset
limit: $limit
order_by: $order
args: { search: $search }
) {
id
created_at
firstname
lastname
phone1
phone2
state
zip
fax
email
country
company
city
category
address2
address1
}
search_phonebook_aggregate(args: { search: $search }) {
aggregate {
count(distinct: true)
}
}
}
`;
export const QUERY_PHONEBOOK_BY_ID = gql`
query QUERY_PHONEBOOK_BY_ID($id: uuid!) {
phonebook_by_pk(id: $id) {
id
created_at
firstname
lastname
phone1
phone2
state
zip
fax
email
country
company
city
category
address2
address1
}
}
`;
export const UPDATE_PHONEBOOK = gql`
mutation UPDATE_VENDOR($id: uuid!, $phonebook: phonebook_set_input!) {
update_phonebook(where: { id: { _eq: $id } }, _set: $phonebook) {
returning {
id
}
}
}
`;
export const INSERT_NEW_PHONEBOOK = gql`
mutation INSERT_NEW_PHONEBOOK($phonebook_entry: [phonebook_insert_input!]!) {
insert_phonebook(objects: $phonebook_entry) {
returning {
id
}
}
}
`;
export const DELETE_PHONEBOOK = gql`
mutation DELETE_PHONEBOOK($id: uuid!) {
delete_phonebook(where: { id: { _eq: $id } }) {
returning {
id
}
}
}
`;

View File

@@ -153,6 +153,8 @@ const PartsQueue = lazy(() =>
const ExportLogs = lazy(() =>
import("../export-logs/export-logs.page.container")
);
const Phonebook = lazy(() => import("../phonebook/phonebook.page.container"));
const EmailTest = lazy(() =>
import("../../components/email-test/email-test-component")
);
@@ -348,6 +350,7 @@ export function Manage({ match, conflict, bodyshop }) {
component={ExportLogs}
/>
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
<Route exact path={`${match.path}/phonebook`} component={Phonebook} />
<Route exact path={`${match.path}/payments`} component={PaymentsAll} />
<Route exact path={`${match.path}/shiftclock`} component={ShiftClock} />

View File

@@ -0,0 +1,190 @@
import { SyncOutlined } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import _ from "lodash";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_PHONEBOOK_PAGINATED } from "../../graphql/phonebook.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function PhonebookPageComponent({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search);
const { page, sortcolumn, sortorder, search, phonebookentry } = searchParams;
const history = useHistory();
const { loading, error, data, refetch } = useQuery(
QUERY_PHONEBOOK_PAGINATED,
{
variables: {
search: search || "",
offset: page ? (page - 1) * 25 : 0,
limit: 25,
order: [
{
[sortcolumn || "lastname"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
],
},
}
);
const { t } = useTranslation();
if (error) return <AlertComponent message={error.message} type="error" />;
const handleTableChange = (pagination, filters, sorter) => {
searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order;
if (filters.status) {
searchParams.statusFilters = JSON.stringify(
_.flattenDeep(filters.status)
);
} else {
delete searchParams.statusFilters;
}
history.push({ search: queryString.stringify(searchParams) });
};
const columns = [
{
title: t("phonebook.fields.firstname"),
dataIndex: "firstname",
key: "firstname",
},
{
title: t("phonebook.fields.lastname"),
dataIndex: "lastname",
key: "lastname",
},
{
title: t("phonebook.fields.company"),
dataIndex: "company",
key: "company",
},
{
title: t("phonebook.fields.email"),
dataIndex: "email",
key: "email",
},
{
title: t("phonebook.fields.phone1"),
dataIndex: "phone1",
key: "phone1",
render: (text, record) => <ChatOpenButton phone={text} />,
},
{
title: t("phonebook.fields.phone2"),
dataIndex: "phone2",
key: "phone2",
render: (text, record) => <ChatOpenButton phone={text} />,
},
{
title: t("phonebook.fields.street1"),
dataIndex: "street1",
key: "street1",
},
{
title: t("phonebook.fields.city"),
dataIndex: "city",
key: "city",
},
];
const handleNewPhonebook = () => {
searchParams.phonebookentry = "new";
history.push({ search: queryString.stringify(searchParams) });
};
const handleOnRowClick = (record) => {
if (record) {
searchParams.phonebookentry = record.id;
history.push({ search: queryString.stringify(searchParams) });
} else {
delete searchParams.phonebookentry;
history.push({ search: queryString.stringify(searchParams) });
}
};
return (
<Card
extra={
<Space wrap>
{searchParams.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {
search: searchParams.search,
})}
</Typography.Title>
<Button
onClick={() => {
delete searchParams.search;
history.push({ search: queryString.stringify(searchParams) });
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={handleNewPhonebook}>
{t("phonebook.actions.new")}
</Button>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={searchParams.search || t("general.labels.search")}
onSearch={(value) => {
searchParams.search = value;
history.push({ search: queryString.stringify(searchParams) });
}}
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: 25,
current: parseInt(page || 1),
total: data && data.search_phonebook_aggregate.aggregate.count,
}}
columns={columns}
rowKey="id"
dataSource={data && data.search_phonebook}
//scroll={{ x: true }}
onChange={handleTableChange}
rowSelection={{
onSelect: handleOnRowClick,
type: "radio",
selectedRowKeys: [phonebookentry],
}}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
},
};
}}
/>
</Card>
);
}
export default connect(mapStateToProps, null)(PhonebookPageComponent);

View File

@@ -0,0 +1,68 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import PhonebookPage from "./phonebook.page.component";
import { Drawer, Grid } from "antd";
import queryString from "query-string";
import { useHistory, useLocation } from "react-router-dom";
import PhonebookFormContainer from "../../components/phonebook-form/phonebook-form.container";
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function PhonebookContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.phonebook");
setSelectedHeader("phonebook");
setBreadcrumbs([
{
link: "/manage/phonebook",
label: t("titles.bc.phonebook"),
},
]);
}, [setBreadcrumbs, t, setSelectedHeader]);
const search = queryString.parse(useLocation().search);
const { phonebookentry } = search;
const history = useHistory();
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "50%",
xl: "50%",
xxl: "45%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
return (
<RbacWrapper action="phonebook:view">
<PhonebookPage />
<Drawer
width={drawerPercentage}
onClose={() => {
delete search.phonebookentry;
history.push({ search: queryString.stringify(search) });
}}
visible={phonebookentry}
>
<PhonebookFormContainer />
</Drawer>
</RbacWrapper>
);
}
export default connect(null, mapDispatchToProps)(PhonebookContainer);

View File

@@ -66,7 +66,10 @@ export function* openChatByPhone({ payload }) {
yield put(setSelectedConversation(conversations[0].id));
//Check to see if this job ID is already a child of it. If not add the tag.
if (!conversations[0].job_conversations.find((jc) => jc.jobid === jobid))
if (
jobid &&
!conversations[0].job_conversations.find((jc) => jc.jobid === jobid)
)
yield client.mutate({
mutation: INSERT_CONVERSATION_TAG,
variables: {

View File

@@ -295,6 +295,10 @@
"enter": "Payments -> Enter",
"list": "Payments -> List"
},
"phonebook": {
"edit": "Phonebook -> Edit",
"view": "Phonebook -> View"
},
"production": {
"board": "Production -> Board",
"list": "Production -> List"
@@ -1373,6 +1377,7 @@
"jobs": "Jobs",
"owners": "Owners",
"parts-queue": "Parts Queue",
"phonebook": "Phonebook",
"productionboard": "Production Board - Visual",
"productionlist": "Production Board - List",
"recent": "Recent Items",
@@ -1606,6 +1611,28 @@
"stripe": "Credit card transaction charged successfully."
}
},
"phonebook": {
"actions": {
"new": "New Phonebook Entry"
},
"fields": {
"city": "City",
"company": "Company",
"country": "Country",
"email": "Email",
"fax": "Fax",
"firstname": "First Name",
"lastname": "Last Name",
"phone1": "Phone 1",
"phone2": "Phone 2",
"state": "state",
"street1": "Street 1",
"street2": "Street 2"
},
"successes": {
"saved": ""
}
},
"printcenter": {
"appointments": {
"appointment_confirmation": "Appointment Confirmation"
@@ -1693,6 +1720,9 @@
"speedprint": "Speed Print",
"title": "Print Center"
},
"payments": {
"ca_bc_etf_table": "ICBC ETF Table"
},
"vendors": {
"purchases_by_vendor_detailed": "Purchases by Vendor - Detailed",
"purchases_by_vendor_summary": "Purchases by Vendor - Summary"
@@ -1921,6 +1951,7 @@
"owners": "Owners",
"parts-queue": "Parts Queue",
"payments-all": "All Payments",
"phonebook": "Phonebook",
"productionboard": "Production Board - Visual",
"productionlist": "Production Board - List",
"profile": "My Profile",
@@ -1959,6 +1990,7 @@
"owners-detail": "{{name}} | $t(titles.app)",
"parts-queue": "Parts Queue | $t(titles.app)",
"payments-all": "Payments | $t(titles.app)",
"phonebook": "Phonebook | $t(titles.app)",
"productionboard": "Production - Board",
"productionlist": "Production Board - List | $t(titles.app)",
"profile": "My Profile | $t(titles.app)",

View File

@@ -295,6 +295,10 @@
"enter": "",
"list": ""
},
"phonebook": {
"edit": "",
"view": ""
},
"production": {
"board": "",
"list": ""
@@ -1373,6 +1377,7 @@
"jobs": "Trabajos",
"owners": "propietarios",
"parts-queue": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
"recent": "",
@@ -1606,6 +1611,28 @@
"stripe": ""
}
},
"phonebook": {
"actions": {
"new": ""
},
"fields": {
"city": "",
"company": "",
"country": "",
"email": "",
"fax": "",
"firstname": "",
"lastname": "",
"phone1": "",
"phone2": "",
"state": "",
"street1": "",
"street2": ""
},
"successes": {
"saved": ""
}
},
"printcenter": {
"appointments": {
"appointment_confirmation": ""
@@ -1693,6 +1720,9 @@
"speedprint": "",
"title": ""
},
"payments": {
"ca_bc_etf_table": ""
},
"vendors": {
"purchases_by_vendor_detailed": "",
"purchases_by_vendor_summary": ""
@@ -1921,6 +1951,7 @@
"owners": "",
"parts-queue": "",
"payments-all": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
"profile": "",
@@ -1959,6 +1990,7 @@
"owners-detail": "",
"parts-queue": "",
"payments-all": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
"profile": "Mi perfil | $t(titles.app)",

View File

@@ -295,6 +295,10 @@
"enter": "",
"list": ""
},
"phonebook": {
"edit": "",
"view": ""
},
"production": {
"board": "",
"list": ""
@@ -1373,6 +1377,7 @@
"jobs": "Emplois",
"owners": "Propriétaires",
"parts-queue": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
"recent": "",
@@ -1606,6 +1611,28 @@
"stripe": ""
}
},
"phonebook": {
"actions": {
"new": ""
},
"fields": {
"city": "",
"company": "",
"country": "",
"email": "",
"fax": "",
"firstname": "",
"lastname": "",
"phone1": "",
"phone2": "",
"state": "",
"street1": "",
"street2": ""
},
"successes": {
"saved": ""
}
},
"printcenter": {
"appointments": {
"appointment_confirmation": ""
@@ -1693,6 +1720,9 @@
"speedprint": "",
"title": ""
},
"payments": {
"ca_bc_etf_table": ""
},
"vendors": {
"purchases_by_vendor_detailed": "",
"purchases_by_vendor_summary": ""
@@ -1921,6 +1951,7 @@
"owners": "",
"parts-queue": "",
"payments-all": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
"profile": "",
@@ -1959,6 +1990,7 @@
"owners-detail": "",
"parts-queue": "",
"payments-all": "",
"phonebook": "",
"productionboard": "",
"productionlist": "",
"profile": "Mon profil | $t(titles.app)",

View File

@@ -0,0 +1,43 @@
- args:
role: user
table:
name: phonebook
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- address1
- address2
- category
- city
- company
- country
- email
- fax
- firstname
- lastname
- phone1
- phone2
- state
- zip
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: phonebook
schema: public
type: create_select_permission

View File

@@ -0,0 +1,43 @@
- args:
role: user
table:
name: phonebook
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: true
columns:
- address1
- address2
- category
- city
- company
- country
- email
- fax
- firstname
- lastname
- phone1
- phone2
- state
- zip
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: phonebook
schema: public
type: create_select_permission

View File

@@ -3854,6 +3854,7 @@ tables:
_eq: X-Hasura-User-Id
- active:
_eq: true
allow_aggregations: true
update_permissions:
- role: user
permission: