diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel index d0530a7a7..dc9e49dcd 100644 --- a/bodyshop_translations.babel +++ b/bodyshop_translations.babel @@ -4089,6 +4089,32 @@ + + users + + + editaccess + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + @@ -26240,9 +26266,56 @@ + + errors + + + updating + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + fields + + authlevel + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + displayname false @@ -26264,6 +26337,27 @@ + + email + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + photourl false @@ -26287,6 +26381,32 @@ + + labels + + + actions + false + + + + + + en-US + false + + + es-MX + false + + + fr-CA + false + + + + + diff --git a/client/src/components/rbac-wrapper/rbac-defaults.js b/client/src/components/rbac-wrapper/rbac-defaults.js index 0a34260fe..4c9a41f22 100644 --- a/client/src/components/rbac-wrapper/rbac-defaults.js +++ b/client/src/components/rbac-wrapper/rbac-defaults.js @@ -21,6 +21,7 @@ const ret = { "jobs:intake": 1, "jobs:close": 5, "jobs:detail": 1, + "jobs:partsqueue": 4, "bills:enter": 2, "bills:view": 2, @@ -49,5 +50,8 @@ const ret = { "timetickets:enter": 3, "timetickets:list": 3, + "timetickets:edit": 4, + + "users:editaccess": 4, }; export default ret; diff --git a/client/src/components/shop-info/shop-info.rbac.component.jsx b/client/src/components/shop-info/shop-info.rbac.component.jsx index aeaa2970f..70bd5be24 100644 --- a/client/src/components/shop-info/shop-info.rbac.component.jsx +++ b/client/src/components/shop-info/shop-info.rbac.component.jsx @@ -476,6 +476,18 @@ export default function ShopInfoRbacComponent({ form }) { > + + + ); diff --git a/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx b/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx new file mode 100644 index 000000000..2fc17b06e --- /dev/null +++ b/client/src/components/shop-users-auth-edit/shop-users-auth-edit.component.jsx @@ -0,0 +1,51 @@ +import { InputNumber, notification } from "antd"; +import React, { useState } from "react"; +import { useMutation } from "react-apollo"; +import { useTranslation } from "react-i18next"; +import { UPDATE_ASSOCIATION } from "../../graphql/user.queries"; + +export default function ShopUsersAuthEdit({ association }) { + const { t } = useTranslation(); + const [updateAssociation] = useMutation(UPDATE_ASSOCIATION); + const [visible, setVisible] = useState(false); + const [value, setValue] = useState(association.authlevel); + + const handleSave = async () => { + setVisible(false); + const result = await updateAssociation({ + variables: { + assocId: association.id, + assoc: { authlevel: value }, + }, + }); + + if (!!result.errors) { + notification["error"]({ + message: t("user.errors.updating", { + message: JSON.stringify(result.errors), + }), + }); + } + }; + + return ( +
+ {visible && ( +
+ setValue(val)} + defaultValue={association.authlevel} + onBlur={handleSave} + /> +
+ )} + {!visible && ( +
setVisible(true)}> + {association.authlevel || t("general.labels.na")} +
+ )} +
+ ); +} diff --git a/client/src/components/shop-users/shop-users.component.jsx b/client/src/components/shop-users/shop-users.component.jsx new file mode 100644 index 000000000..b1ab5512f --- /dev/null +++ b/client/src/components/shop-users/shop-users.component.jsx @@ -0,0 +1,59 @@ +import { Button, Table } from "antd"; +import React from "react"; +import { useQuery } from "react-apollo"; +import { useTranslation } from "react-i18next"; +import { QUERY_SHOP_ASSOCIATIONS } from "../../graphql/user.queries"; +import AlertComponent from "../alert/alert.component"; +import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component"; +import ShopUsersAuthEdit from "../shop-users-auth-edit/shop-users-auth-edit.component"; + +export default function ShopInfoUsersComponent() { + const { t } = useTranslation(); + const { loading, error, data } = useQuery(QUERY_SHOP_ASSOCIATIONS); + const columns = [ + { + title: t("user.fields.email"), + dataIndex: "email", + key: "email", + render: (text, record) => record.user.email, + }, + { + title: t("user.fields.authlevel"), + dataIndex: "authlevel", + key: "authlevel", + render: (text, record) => ( + + + + ), + }, + { + title: t("user.labels.actions"), + dataIndex: "actions", + key: "actions", + render: (text, record) => ( +
+ +
+ ), + }, + ]; + + if (error) { + return ; + } + return ( +
+ ({ ...item }))} + rowKey="id" + dataSource={data && data.associations} + /> + + ); +} diff --git a/client/src/graphql/user.queries.js b/client/src/graphql/user.queries.js index 3b86be6a4..484f3cd80 100644 --- a/client/src/graphql/user.queries.js +++ b/client/src/graphql/user.queries.js @@ -1,5 +1,36 @@ import gql from "graphql-tag"; +export const QUERY_SHOP_ASSOCIATIONS = gql` + query QUERY_SHOP_ASSOCIATIONS { + associations { + id + authlevel + shopid + user { + email + } + } + } +`; + +export const UPDATE_ASSOCIATION = gql` + mutation UPDATE_ASSOCIATION( + $assocId: uuid! + $assoc: associations_set_input! + ) { + update_associations(where: { id: { _eq: $assocId } }, _set: $assoc) { + returning { + id + authlevel + shopid + user { + email + } + } + } + } +`; + export const UPSERT_USER = gql` mutation UPSERT_USER($authEmail: String!, $authToken: String!) { insert_users( diff --git a/client/src/pages/shop/shop.page.component.jsx b/client/src/pages/shop/shop.page.component.jsx index 93f440404..bd8df39fa 100644 --- a/client/src/pages/shop/shop.page.component.jsx +++ b/client/src/pages/shop/shop.page.component.jsx @@ -12,6 +12,7 @@ import { setBreadcrumbs, } from "../../redux/application/application.actions"; import { selectBodyshop } from "../../redux/user/user.selectors"; +import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component"; const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop, }); @@ -42,7 +43,7 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) { - Licensing + diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json index 977e896ac..08fe5e98a 100644 --- a/client/src/translations/en_us/common.json +++ b/client/src/translations/en_us/common.json @@ -280,6 +280,9 @@ "edit": "Time Tickets -> Edit", "enter": "Time Tickets -> Enter", "list": "Time Tickets -> List" + }, + "users": { + "editaccess": "Users -> Edit access" } }, "responsibilitycenter": "Responsibility Center", @@ -1616,9 +1619,17 @@ "signout": "Sign Out", "updateprofile": "Update Profile" }, + "errors": { + "updating": "Error updating user or association {{message}}" + }, "fields": { + "authlevel": "Authorization Level", "displayname": "Display Name", + "email": "Email", "photourl": "Avatar URL" + }, + "labels": { + "actions": "Actions" } }, "vehicles": { diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json index 046f6f27d..93e923a34 100644 --- a/client/src/translations/es/common.json +++ b/client/src/translations/es/common.json @@ -280,6 +280,9 @@ "edit": "", "enter": "", "list": "" + }, + "users": { + "editaccess": "" } }, "responsibilitycenter": "", @@ -1616,9 +1619,17 @@ "signout": "desconectar", "updateprofile": "Actualización del perfil" }, + "errors": { + "updating": "" + }, "fields": { + "authlevel": "", "displayname": "Nombre para mostrar", + "email": "", "photourl": "URL de avatar" + }, + "labels": { + "actions": "" } }, "vehicles": { diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json index 5fa46b539..41872bfb9 100644 --- a/client/src/translations/fr/common.json +++ b/client/src/translations/fr/common.json @@ -280,6 +280,9 @@ "edit": "", "enter": "", "list": "" + }, + "users": { + "editaccess": "" } }, "responsibilitycenter": "", @@ -1616,9 +1619,17 @@ "signout": "Déconnexion", "updateprofile": "Mettre à jour le profil" }, + "errors": { + "updating": "" + }, "fields": { + "authlevel": "", "displayname": "Afficher un nom", + "email": "", "photourl": "URL de l'avatar" + }, + "labels": { + "actions": "" } }, "vehicles": { diff --git a/hasura/migrations/1610062707742_update_permission_user_public_table_associations/down.yaml b/hasura/migrations/1610062707742_update_permission_user_public_table_associations/down.yaml new file mode 100644 index 000000000..015c4e179 --- /dev/null +++ b/hasura/migrations/1610062707742_update_permission_user_public_table_associations/down.yaml @@ -0,0 +1,25 @@ +- args: + role: user + table: + name: associations + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - active + - authlevel + - id + - shopid + - useremail + computed_fields: [] + filter: + user: + authid: + _eq: X-Hasura-User-Id + role: user + table: + name: associations + schema: public + type: create_select_permission diff --git a/hasura/migrations/1610062707742_update_permission_user_public_table_associations/up.yaml b/hasura/migrations/1610062707742_update_permission_user_public_table_associations/up.yaml new file mode 100644 index 000000000..fcae8dd99 --- /dev/null +++ b/hasura/migrations/1610062707742_update_permission_user_public_table_associations/up.yaml @@ -0,0 +1,30 @@ +- args: + role: user + table: + name: associations + schema: public + type: drop_select_permission +- args: + permission: + allow_aggregations: false + columns: + - active + - authlevel + - id + - shopid + - useremail + computed_fields: [] + filter: + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true + role: user + table: + name: associations + schema: public + type: create_select_permission diff --git a/hasura/migrations/1610065492233_update_permission_user_public_table_associations/down.yaml b/hasura/migrations/1610065492233_update_permission_user_public_table_associations/down.yaml new file mode 100644 index 000000000..9c02886e0 --- /dev/null +++ b/hasura/migrations/1610065492233_update_permission_user_public_table_associations/down.yaml @@ -0,0 +1,20 @@ +- args: + role: user + table: + name: associations + schema: public + type: drop_update_permission +- args: + permission: + columns: + - active + filter: + user: + authid: + _eq: X-Hasura-User-Id + set: {} + role: user + table: + name: associations + schema: public + type: create_update_permission diff --git a/hasura/migrations/1610065492233_update_permission_user_public_table_associations/up.yaml b/hasura/migrations/1610065492233_update_permission_user_public_table_associations/up.yaml new file mode 100644 index 000000000..0175b1813 --- /dev/null +++ b/hasura/migrations/1610065492233_update_permission_user_public_table_associations/up.yaml @@ -0,0 +1,21 @@ +- args: + role: user + table: + name: associations + schema: public + type: drop_update_permission +- args: + permission: + columns: + - active + - authlevel + filter: + user: + authid: + _eq: X-Hasura-User-Id + set: {} + role: user + table: + name: associations + schema: public + type: create_update_permission diff --git a/hasura/migrations/metadata.yaml b/hasura/migrations/metadata.yaml index 8edd60d39..1c36f0593 100644 --- a/hasura/migrations/metadata.yaml +++ b/hasura/migrations/metadata.yaml @@ -204,14 +204,20 @@ tables: - shopid - useremail filter: - user: - authid: - _eq: X-Hasura-User-Id + bodyshop: + associations: + _and: + - user: + authid: + _eq: X-Hasura-User-Id + - active: + _eq: true update_permissions: - role: user permission: columns: - active + - authlevel filter: user: authid: