From 2a49799063c5aee78ada932a99c0232c06381f4c Mon Sep 17 00:00:00 2001 From: yk-eukarya <81808708+yk-eukarya@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:42:43 +0100 Subject: [PATCH 01/28] fix(server): creating item with default group value fails (#1292) * fix(server): creating item with default group value fails * refactor --- server/internal/adapter/integration/convert.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/internal/adapter/integration/convert.go b/server/internal/adapter/integration/convert.go index 3042944518..7ee7667eb7 100644 --- a/server/internal/adapter/integration/convert.go +++ b/server/internal/adapter/integration/convert.go @@ -113,10 +113,9 @@ func appendGroupFieldsDefaultValue(sp *schema.Package, res []interfaces.ItemFiel continue } igID := id.NewItemGroupID() - var v any - v = []id.ItemGroupID{id.NewItemGroupID()} - if !gsf.Multiple() { - v = igID + var v any = igID + if gsf.Multiple() { + v = []any{igID} } res = append(res, interfaces.ItemFieldParam{ Field: gsf.ID().Ref(), From 1db1bff61aef3ea5c156c3ada0d53ac8930e3028 Mon Sep 17 00:00:00 2001 From: rot1024 Date: Fri, 1 Nov 2024 13:34:02 +0900 Subject: [PATCH 02/28] chore(web): rewrite title and favicon in docker image --- web/docker/40-envsubst-on-reearth-config.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web/docker/40-envsubst-on-reearth-config.sh b/web/docker/40-envsubst-on-reearth-config.sh index 1b116b4164..ce1f9d6c64 100755 --- a/web/docker/40-envsubst-on-reearth-config.sh +++ b/web/docker/40-envsubst-on-reearth-config.sh @@ -15,3 +15,17 @@ wrap_reearth_cms_variables() { wrap_reearth_cms_variables $@ envsubst < "$_REEARTH_CONFIG_TEMPLATE_FILE" > "$_REEARTH_CONFIG_OUTPUT_FILE" + +# rewrite index.html to change title and favicon +_REEARTH_HTML_FILE="/usr/share/nginx/html/index.html" + +# Rewrite title tag in index.html only if REEARTH_CMS_TITLE is set +if [ -n "$REEARTH_CMS_TITLE" ]; then + sed -i '' -e "s|.*|${REEARTH_CMS_TITLE}|g" "$_REEARTH_HTML_FILE" +fi + +# Rewrite favicon in index.html only if REEARTH_CMS_FAVICON is set +# +if [ -n "$REEARTH_CMS_FAVICON" ]; then + sed -i '' -e "s|||g" "$_REEARTH_HTML_FILE" +fi From da553315d34a30124d4cbbe34553da28d3bba1c2 Mon Sep 17 00:00:00 2001 From: KeisukeYamashita <19yamashita15@gmail.com> Date: Fri, 1 Nov 2024 06:05:10 +0100 Subject: [PATCH 03/28] ci: fix branch retrieval in build workflow for web and worker (#1294) Signed-off-by: KeisukeYamashita <19yamashita15@gmail.com> --- .github/workflows/build_web.yml | 2 +- .github/workflows/build_worker.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_web.yml b/.github/workflows/build_web.yml index fead094495..237b3e4919 100644 --- a/.github/workflows/build_web.yml +++ b/.github/workflows/build_web.yml @@ -26,7 +26,7 @@ jobs: id: info # The tag name should be retrieved lazily, as tagging may be delayed. env: - BRANCH: ${{ github.event.workflow_run.head_branch }} + BRANCH: ${{ github.head_ref || github.ref_name }} run: | echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" if [[ "$BRANCH" = "release" ]]; then diff --git a/.github/workflows/build_worker.yml b/.github/workflows/build_worker.yml index 4f877a49a5..29e320f020 100644 --- a/.github/workflows/build_worker.yml +++ b/.github/workflows/build_worker.yml @@ -26,7 +26,7 @@ jobs: id: info # The tag name should be retrieved lazily, as tagging may be delayed. env: - BRANCH: ${{ github.event.workflow_run.head_branch }} + BRANCH: ${{ github.head_ref || github.ref_name }} run: | echo "::set-output name=sha_short::$(git rev-parse --short HEAD)" if [[ "$BRANCH" = "release" ]]; then From cdc25a6a0f12d9b9109d33743ea738f76ba5be9c Mon Sep 17 00:00:00 2001 From: rot1024 Date: Fri, 1 Nov 2024 16:49:35 +0900 Subject: [PATCH 04/28] fix(web): fix errors on Docker startup --- web/Dockerfile | 16 +++++---- web/docker/40-envsubst-on-reearth-config.sh | 36 ++++++++++++--------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/web/Dockerfile b/web/Dockerfile index 7dd957c3f5..5438007a1e 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -21,10 +21,10 @@ RUN --mount=type=bind,source=package.json,target=package.json \ yarn build FROM nginx:1.27-alpine -WORKDIR /app +WORKDIR /usr/share/nginx/html # Quite the Nginx startup logs. -ENV NGINX_ENTRYPOINT_QUIET_LOGS=true +ENV NGINX_ENTRYPOINT_QUIET_LOGS=true # Default to Cloud Run port. # Ref: https://cloud.google.com/run/docs/reference/container-contract#port @@ -35,18 +35,20 @@ ENV PORT=8080 ENV REAL_IP_HEADER=X-Forwarded-For # Default values. -# Cesium Ion access token is not a secret. -ENV REEARTH_CMS_CESIUM_ION_ACCESS_TOKEN=null - -# All values in reearth_config.json must have a default value. +ENV REEARTH_CMS_TITLE= +ENV REEARTH_CMS_FAVICON_URL= ENV REEARTH_CMS_API=null +ENV REEARTH_CMS_AUTH0_AUDIENCE= +ENV REEARTH_CMS_AUTH0_CLIENT_ID= +ENV REEARTH_CMS_AUTH0_DOMAIN= +ENV REEARTH_CMS_CESIUM_ION_ACCESS_TOKEN=null ENV REEARTH_CMS_COVER_IMAGE_URL=null ENV REEARTH_CMS_EDITOR_URL=null ENV REEARTH_CMS_LOGO_URL=null ENV REEARTH_CMS_MULTI_TENANT=null COPY --from=builder --chown=nginx:nginx /app/dist /usr/share/nginx/html -COPY --chown=nginx:nginx docker/nginx.conf.template /etc/nginx/templates/nginx.conf.template +COPY --chown=nginx:nginx docker/nginx.conf.template /etc/nginx/templates/nginx.conf.template COPY --chown=nginx:nginx docker/40-envsubst-on-reearth-config.sh /docker-entrypoint.d COPY --chown=nginx:nginx docker/reearth_config.json.template /opt/reearth-cms/reearth_config.json.template diff --git a/web/docker/40-envsubst-on-reearth-config.sh b/web/docker/40-envsubst-on-reearth-config.sh index ce1f9d6c64..d60a911ac0 100755 --- a/web/docker/40-envsubst-on-reearth-config.sh +++ b/web/docker/40-envsubst-on-reearth-config.sh @@ -2,6 +2,20 @@ set -e +# rewrite index.html to change title and favicon +_REEARTH_HTML_FILE="/usr/share/nginx/html/index.html" + +# Rewrite title tag in index.html only if REEARTH_CMS_TITLE is set +if [ -n "$REEARTH_CMS_TITLE" ]; then + sed -i -e "s|.*|${REEARTH_CMS_TITLE}|g" "$_REEARTH_HTML_FILE" +fi + +# Rewrite favicon in index.html only if REEARTH_CMS_FAVICON_URL is set +if [ -n "$REEARTH_CMS_FAVICON_URL" ]; then + sed -i -e "s|||g" "$_REEARTH_HTML_FILE" +fi + +# generate reearth_config.json _REEARTH_CONFIG_TEMPLATE_FILE="/opt/reearth-cms/reearth_config.json.template" _REEARTH_CONFIG_OUTPUT_FILE="/usr/share/nginx/html/reearth_config.json" @@ -9,23 +23,13 @@ _REEARTH_CONFIG_OUTPUT_FILE="/usr/share/nginx/html/reearth_config.json" wrap_reearth_cms_variables() { for var in $(env | grep '^REEARTH_CMS_' | cut -d= -f1); do value=$(printenv "$var") - [ "$value" != "null" ] && ! echo "$value" | grep -qE '^\{.*\}$' && export "$var=\"${value}\"" + if [ -z "$value" ]; then + eval "export $var='\"\"'" + elif [ "$value" != "null" ] && ! echo "$value" | grep -qE '^\{.*\}$'; then + eval "export $var='${value}'" + fi done } -wrap_reearth_cms_variables $@ +wrap_reearth_cms_variables "$@" envsubst < "$_REEARTH_CONFIG_TEMPLATE_FILE" > "$_REEARTH_CONFIG_OUTPUT_FILE" - -# rewrite index.html to change title and favicon -_REEARTH_HTML_FILE="/usr/share/nginx/html/index.html" - -# Rewrite title tag in index.html only if REEARTH_CMS_TITLE is set -if [ -n "$REEARTH_CMS_TITLE" ]; then - sed -i '' -e "s|.*|${REEARTH_CMS_TITLE}|g" "$_REEARTH_HTML_FILE" -fi - -# Rewrite favicon in index.html only if REEARTH_CMS_FAVICON is set -# -if [ -n "$REEARTH_CMS_FAVICON" ]; then - sed -i '' -e "s|||g" "$_REEARTH_HTML_FILE" -fi From 7a13a608f6aee1ef1ad18504c5f6cb74e5a782c1 Mon Sep 17 00:00:00 2001 From: caichi <54824604+caichi-t@users.noreply.github.com> Date: Fri, 1 Nov 2024 17:11:32 +0900 Subject: [PATCH 05/28] feat(web): publish multiple items (#1295) * add: publish button * remove deselect button --- .../molecules/Asset/AssetListTable/index.tsx | 7 --- .../molecules/Content/List/index.tsx | 6 +++ .../molecules/Content/Table/index.tsx | 49 ++++++++++++++++--- .../Integration/IntegrationTable/index.tsx | 7 --- .../molecules/Request/Table/index.tsx | 7 --- .../Project/Content/ContentList/hooks.ts | 4 ++ .../Project/Content/ContentList/index.tsx | 4 ++ web/src/i18n/translations/en.yml | 7 +-- web/src/i18n/translations/ja.yml | 7 +-- 9 files changed, 63 insertions(+), 35 deletions(-) diff --git a/web/src/components/molecules/Asset/AssetListTable/index.tsx b/web/src/components/molecules/Asset/AssetListTable/index.tsx index 61b4f1fd75..1671d89815 100644 --- a/web/src/components/molecules/Asset/AssetListTable/index.tsx +++ b/web/src/components/molecules/Asset/AssetListTable/index.tsx @@ -284,13 +284,6 @@ const AssetListTable: React.FC = ({ (props: any) => { return ( - void; onFilterChange: (filter?: ConditionInput[]) => void; onContentTableChange: (page: number, pageSize: number, sorter?: ItemSort) => void; + onPublish: (itemIds: string[]) => Promise; onUnpublish: (itemIds: string[]) => Promise; onItemSelect: (itemId: string) => void; setSelectedItems: (input: { selectedRows: { itemId: string; version?: string }[] }) => void; @@ -72,6 +74,7 @@ const ContentListMolecule: React.FC = ({ modelsMenu, loading, deleteLoading, + publishLoading, unpublishLoading, selectedItem, selectedItems, @@ -88,6 +91,7 @@ const ContentListMolecule: React.FC = ({ requestModalTotalCount, requestModalPage, requestModalPageSize, + onPublish, onUnpublish, onAddItemToRequest, onAddItemToRequestModalClose, @@ -145,9 +149,11 @@ const ContentListMolecule: React.FC = ({ pageSize={pageSize} loading={loading} deleteLoading={deleteLoading} + publishLoading={publishLoading} unpublishLoading={unpublishLoading} selectedItem={selectedItem} selectedItems={selectedItems} + onPublish={onPublish} onUnpublish={onUnpublish} onSearchTerm={onSearchTerm} onFilterChange={onFilterChange} diff --git a/web/src/components/molecules/Content/Table/index.tsx b/web/src/components/molecules/Content/Table/index.tsx index 7be4e5e916..a278702724 100644 --- a/web/src/components/molecules/Content/Table/index.tsx +++ b/web/src/components/molecules/Content/Table/index.tsx @@ -16,6 +16,7 @@ import CustomTag from "@reearth-cms/components/atoms/CustomTag"; import Dropdown, { MenuProps } from "@reearth-cms/components/atoms/Dropdown"; import Icon from "@reearth-cms/components/atoms/Icon"; import Input from "@reearth-cms/components/atoms/Input"; +import Modal from "@reearth-cms/components/atoms/Modal"; import { TableRowSelection, ListToolBarProps, @@ -56,6 +57,7 @@ type Props = { contentTableColumns?: ExtendedColumns[]; loading: boolean; deleteLoading: boolean; + publishLoading: boolean; unpublishLoading: boolean; selectedItem?: Item; selectedItems: { selectedRows: { itemId: string; version?: string }[] }; @@ -77,6 +79,7 @@ type Props = { setSelectedItems: (input: { selectedRows: { itemId: string; version?: string }[] }) => void; onItemEdit: (itemId: string) => void; onItemDelete: (itemIds: string[]) => Promise; + onPublish: (itemIds: string[]) => Promise; onUnpublish: (itemIds: string[]) => Promise; onItemsReload: () => void; requests: Request[]; @@ -94,6 +97,7 @@ const ContentTable: React.FC = ({ contentTableColumns, loading, deleteLoading, + publishLoading, unpublishLoading, selectedItem, selectedItems, @@ -113,6 +117,7 @@ const ContentTable: React.FC = ({ onAddItemToRequest, onAddItemToRequestModalClose, onAddItemToRequestModalOpen, + onPublish, onUnpublish, onSearchTerm, onFilterChange, @@ -289,11 +294,37 @@ const ContentTable: React.FC = ({ [selectedItems, setSelectedItems], ); + const publishConfirm = useCallback( + (itemIds: string[]) => { + Modal.confirm({ + title: t("Publish items"), + content: t("All selected items will be published. You can unpublish them anytime."), + icon: , + cancelText: t("No"), + okText: t("Yes"), + async onOk() { + await onPublish(itemIds); + }, + }); + }, + [onPublish, t], + ); + const alertOptions = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (props: any) => { return ( + - ); }, - [deleteLoading, onAssetDelete, t], + [deleteLoading, hasDeleteRight, onAssetDelete, t], ); const handleChange = useCallback( diff --git a/web/src/components/molecules/Asset/UploadAsset/index.tsx b/web/src/components/molecules/Asset/UploadAsset/index.tsx index 917aa0811c..271bdc557d 100644 --- a/web/src/components/molecules/Asset/UploadAsset/index.tsx +++ b/web/src/components/molecules/Asset/UploadAsset/index.tsx @@ -14,6 +14,7 @@ type Props = { uploadModalVisibility?: boolean; uploadUrl: { url: string; autoUnzip: boolean }; uploadType?: UploadType; + hasCreateRight: boolean; setUploadUrl: (uploadUrl: { url: string; autoUnzip: boolean }) => void; setUploadType?: (type: UploadType) => void; onUploadModalClose?: () => void; @@ -28,11 +29,12 @@ const UploadAsset: React.FC = ({ fileList, uploading, uploadModalVisibility, + uploadUrl, + uploadType, + hasCreateRight, onUploadModalClose, displayUploadModal, onUploadModalCancel, - uploadUrl, - uploadType, setUploadUrl, setUploadType, onUpload, @@ -40,7 +42,11 @@ const UploadAsset: React.FC = ({ const t = useT(); return ( <> - Promise; onCommentDelete: (commentId: string) => Promise; }; -const CommentMolecule: React.FC = ({ me, comment, onCommentUpdate, onCommentDelete }) => { +const CommentMolecule: React.FC = ({ + me, + hasUpdateRight, + hasDeleteRight, + comment, + onCommentUpdate, + onCommentDelete, +}) => { const [showEditor, setShowEditor] = useState(false); const [value, setValue] = useState(comment.content); @@ -49,20 +58,36 @@ const CommentMolecule: React.FC = ({ me, comment, onCommentUpdate, onComm [comment.createdAt], ); + const actions = useMemo(() => { + const result = []; + const isMine = me?.id === comment.author.id; + if (hasDeleteRight || (hasDeleteRight === null && isMine)) { + result.push( onCommentDelete(comment.id)} />); + } + if (hasUpdateRight || (hasUpdateRight === null && isMine)) { + result.push( + setShowEditor(true)} + />, + ); + } + return result; + }, [ + comment.author.id, + comment.id, + handleSubmit, + hasDeleteRight, + hasUpdateRight, + me?.id, + onCommentDelete, + showEditor, + ]); + return ( onCommentDelete(comment.id)} />, - showEditor ? ( - - ) : ( - setShowEditor(true)} /> - ), - ] - : [] - } + actions={actions} author={comment.author.name} avatar={ comment.author.type === "Integration" ? ( diff --git a/web/src/components/molecules/Common/CommentsPanel/CommentList.tsx b/web/src/components/molecules/Common/CommentsPanel/CommentList.tsx index 8c8218e128..612281cc7c 100644 --- a/web/src/components/molecules/Common/CommentsPanel/CommentList.tsx +++ b/web/src/components/molecules/Common/CommentsPanel/CommentList.tsx @@ -6,6 +6,8 @@ import CommentMolecule from "./Comment"; type Props = { me?: User; + hasUpdateRight: boolean | null; + hasDeleteRight: boolean | null; comments: Comment[]; onCommentUpdate: (commentId: string, content: string) => Promise; onCommentDelete: (commentId: string) => Promise; @@ -13,6 +15,8 @@ type Props = { export const CommentList: React.FC = ({ me, + hasUpdateRight, + hasDeleteRight, comments, onCommentUpdate, onCommentDelete, @@ -22,8 +26,10 @@ export const CommentList: React.FC = ({ itemLayout="horizontal" renderItem={props => ( diff --git a/web/src/components/molecules/Common/CommentsPanel/Thread.tsx b/web/src/components/molecules/Common/CommentsPanel/Thread.tsx index 49cc7bfb0e..e584230d4e 100644 --- a/web/src/components/molecules/Common/CommentsPanel/Thread.tsx +++ b/web/src/components/molecules/Common/CommentsPanel/Thread.tsx @@ -5,17 +5,28 @@ import { CommentList } from "./CommentList"; type Props = { me?: User; + hasUpdateRight: boolean | null; + hasDeleteRight: boolean | null; comments?: Comment[]; onCommentUpdate: (commentId: string, content: string) => Promise; onCommentDelete: (commentId: string) => Promise; }; -export const Thread: React.FC = ({ me, comments, onCommentUpdate, onCommentDelete }) => { +export const Thread: React.FC = ({ + me, + hasUpdateRight, + hasDeleteRight, + comments, + onCommentUpdate, + onCommentDelete, +}) => { return ( <> {comments && comments?.length > 0 && ( = ({ me, + hasCreateRight, + hasUpdateRight, + hasDeleteRight, comments, emptyText, collapsed, @@ -50,6 +56,8 @@ const CommentsPanel: React.FC = ({ = ({ {emptyText} ) : null} - + )} diff --git a/web/src/components/molecules/Common/Form/AssetItem/hooks.ts b/web/src/components/molecules/Common/Form/AssetItem/hooks.ts index 9777e8e720..b0e69ff096 100644 --- a/web/src/components/molecules/Common/Form/AssetItem/hooks.ts +++ b/web/src/components/molecules/Common/Form/AssetItem/hooks.ts @@ -1,9 +1,9 @@ -import { useState, useCallback } from "react"; +import { useState, useCallback, useMemo } from "react"; import { UploadFile } from "@reearth-cms/components/atoms/Upload"; import { UploadType } from "@reearth-cms/components/molecules/Asset/AssetList"; import { Asset } from "@reearth-cms/components/molecules/Asset/types"; -import { useProject, useWorkspace } from "@reearth-cms/state"; +import { useProject, useWorkspace, useUserRights } from "@reearth-cms/state"; export default ( fileList?: UploadFile[], @@ -17,6 +17,9 @@ export default ( ) => { const [currentWorkspace] = useWorkspace(); const [currentProject] = useProject(); + const [userRights] = useUserRights(); + const hasCreateRight = useMemo(() => !!userRights?.asset.create, [userRights?.asset.create]); + const [visible, setVisible] = useState(false); const handleClick = useCallback(() => { setVisible(true); @@ -49,6 +52,7 @@ export default ( visible, workspaceId: currentWorkspace?.id, projectId: currentProject?.id, + hasCreateRight, handleClick, handleLinkAssetModalCancel, displayUploadModal, diff --git a/web/src/components/molecules/Common/Form/AssetItem/index.tsx b/web/src/components/molecules/Common/Form/AssetItem/index.tsx index 3e4fc8f7f8..c4ab09c5c0 100644 --- a/web/src/components/molecules/Common/Form/AssetItem/index.tsx +++ b/web/src/components/molecules/Common/Form/AssetItem/index.tsx @@ -76,6 +76,7 @@ const AssetItem: React.FC = ({ visible, workspaceId, projectId, + hasCreateRight, handleClick, handleLinkAssetModalCancel, displayUploadModal, @@ -203,6 +204,7 @@ const AssetItem: React.FC = ({ totalCount={totalCount} page={page} pageSize={pageSize} + hasCreateRight={hasCreateRight} onAssetTableChange={onAssetTableChange} setUploadUrl={setUploadUrl} setUploadType={setUploadType} diff --git a/web/src/components/molecules/Common/Form/GroupItem/index.tsx b/web/src/components/molecules/Common/Form/GroupItem/index.tsx index 183ce8f8e0..eb4ddedc29 100644 --- a/web/src/components/molecules/Common/Form/GroupItem/index.tsx +++ b/web/src/components/molecules/Common/Form/GroupItem/index.tsx @@ -38,7 +38,7 @@ type Props = { linkItemModalTotalCount?: number; linkItemModalPage?: number; linkItemModalPageSize?: number; - disabled?: boolean; + disabled: boolean; onSearchTerm?: (term?: string) => void; onReferenceModelUpdate?: (modelId: string, referenceFieldId: string) => void; onLinkItemTableReload?: () => void; diff --git a/web/src/components/molecules/Common/LinkAssetModal/LinkAssetModal.tsx b/web/src/components/molecules/Common/LinkAssetModal/LinkAssetModal.tsx index 774111e9e4..b8c0091a95 100644 --- a/web/src/components/molecules/Common/LinkAssetModal/LinkAssetModal.tsx +++ b/web/src/components/molecules/Common/LinkAssetModal/LinkAssetModal.tsx @@ -10,8 +10,10 @@ import { OptionConfig, } from "@reearth-cms/components/atoms/ProTable"; import Search from "@reearth-cms/components/atoms/Search"; +import Space from "@reearth-cms/components/atoms/Space"; import { SorterResult, TablePaginationConfig } from "@reearth-cms/components/atoms/Table"; import { UploadProps, UploadFile } from "@reearth-cms/components/atoms/Upload"; +import UserAvatar from "@reearth-cms/components/atoms/UserAvatar"; import { UploadType } from "@reearth-cms/components/molecules/Asset/AssetList"; import { Asset, SortType } from "@reearth-cms/components/molecules/Asset/types"; import UploadAsset from "@reearth-cms/components/molecules/Asset/UploadAsset"; @@ -35,6 +37,7 @@ type Props = { totalCount?: number; page?: number; pageSize?: number; + hasCreateRight: boolean; onAssetTableChange?: (page: number, pageSize: number, sorter?: SortType) => void; setUploadUrl: (uploadUrl: { url: string; autoUnzip: boolean }) => void; setUploadType?: (type: UploadType) => void; @@ -62,6 +65,7 @@ const LinkAssetModal: React.FC = ({ totalCount, page, pageSize, + hasCreateRight, onAssetTableChange, setUploadUrl, setUploadType, @@ -172,11 +176,17 @@ const LinkAssetModal: React.FC = ({ }, { title: t("Created By"), - dataIndex: "createdBy", + dataIndex: ["createdBy", "name"], key: "createdBy", ellipsis: true, width: 100, minWidth: 100, + render: (_, item) => ( + + + {item.createdBy.name} + + ), }, ], [linkedAsset?.id, onLinkClick, t], @@ -226,6 +236,7 @@ const LinkAssetModal: React.FC = ({ uploadModalVisibility={uploadModalVisibility} uploadUrl={uploadUrl} uploadType={uploadType} + hasCreateRight={hasCreateRight} setUploadUrl={setUploadUrl} setUploadType={setUploadType} displayUploadModal={displayUploadModal} diff --git a/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx b/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx index 021a911249..fcddfbae13 100644 --- a/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx +++ b/web/src/components/molecules/Common/MultiValueField/MultiValueGroup/index.tsx @@ -41,7 +41,7 @@ type Props = { linkItemModalTotalCount?: number; linkItemModalPage?: number; linkItemModalPageSize?: number; - disabled?: boolean; + disabled: boolean; onSearchTerm?: (term?: string) => void; onReferenceModelUpdate?: (modelId: string, referenceFieldId: string) => void; onLinkItemTableReload?: () => void; diff --git a/web/src/components/molecules/Content/Details/index.tsx b/web/src/components/molecules/Content/Details/index.tsx index 8b5c2ca16e..2c47533977 100644 --- a/web/src/components/molecules/Content/Details/index.tsx +++ b/web/src/components/molecules/Content/Details/index.tsx @@ -17,6 +17,10 @@ import { Group } from "@reearth-cms/components/molecules/Schema/types"; import { UserMember } from "@reearth-cms/components/molecules/Workspace/types"; type Props = { + hasRequestCreateRight: boolean; + hasRequestUpdateRight: boolean; + hasPublishRight: boolean; + hasItemUpdateRight: boolean; loadingReference: boolean; linkedItemsModalList?: FormItem[]; showPublishAction: boolean; @@ -109,6 +113,10 @@ type Props = { }; const ContentDetailsMolecule: React.FC = ({ + hasRequestCreateRight, + hasRequestUpdateRight, + hasPublishRight, + hasItemUpdateRight, loadingReference, linkedItemsModalList, showPublishAction, @@ -202,6 +210,10 @@ const ContentDetailsMolecule: React.FC = ({ = ({ item, onNavigateToRequest }) = {t("Created By")} - {item.createdBy} + {item.createdBy?.name} {t("Updated At")} @@ -57,7 +57,7 @@ const ContentSidebarWrapper: React.FC = ({ item, onNavigateToRequest }) = {t("Updated By")} - {item.updatedBy} + {item.updatedBy?.name} diff --git a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/AssetField.tsx b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/AssetField.tsx index 58242d01e1..4c7a66942b 100644 --- a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/AssetField.tsx +++ b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/AssetField.tsx @@ -23,7 +23,7 @@ type AssetFieldProps = { totalCount?: number; page?: number; pageSize?: number; - disabled?: boolean; + disabled: boolean; onAssetTableChange?: (page: number, pageSize: number, sorter?: SortType) => void; onUploadModalCancel?: () => void; setUploadUrl?: (uploadUrl: { url: string; autoUnzip: boolean }) => void; diff --git a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx index 3084f64149..cf3b85ba56 100644 --- a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx +++ b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/GroupField.tsx @@ -30,7 +30,7 @@ type GroupFieldProps = { linkItemModalTotalCount?: number; linkItemModalPage?: number; linkItemModalPageSize?: number; - disabled?: boolean; + disabled: boolean; onSearchTerm?: (term?: string) => void; onReferenceModelUpdate?: (modelId: string, referenceFieldId: string) => void; onLinkItemTableReload?: () => void; diff --git a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx index cf179e4e22..242aa67dea 100644 --- a/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx +++ b/web/src/components/molecules/Content/Form/fields/ComplexFieldComponents/ReferenceField.tsx @@ -15,7 +15,7 @@ type ReferenceFieldProps = { linkItemModalTotalCount?: number; linkItemModalPage?: number; linkItemModalPageSize?: number; - disabled?: boolean; + disabled: boolean; onReferenceModelUpdate?: (modelId: string, referenceFieldId: string) => void; onSearchTerm?: (term?: string) => void; onLinkItemTableReload?: () => void; diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/BoolField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/BoolField.tsx index b26e2efdb8..a83cd8935a 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/BoolField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/BoolField.tsx @@ -9,7 +9,7 @@ type BoolFieldProps = { field: Field; itemGroupId?: string; onMetaUpdate?: () => void; - disabled?: boolean; + disabled: boolean; }; const BoolField: React.FC = ({ field, itemGroupId, onMetaUpdate, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/CheckboxField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/CheckboxField.tsx index e9d8066e4e..d3f2bbf9d1 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/CheckboxField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/CheckboxField.tsx @@ -8,9 +8,10 @@ import FieldTitle from "../../FieldTitle"; type CheckboxFieldProps = { field: Field; onMetaUpdate?: () => void; + disabled: boolean; }; -const CheckboxField: React.FC = ({ field, onMetaUpdate }) => { +const CheckboxField: React.FC = ({ field, onMetaUpdate, disabled }) => { return ( = ({ field, onMetaUpdate }) => valuePropName="checked" label={}> {field.multiple ? ( - + ) : ( - + )} ); diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/DateField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/DateField.tsx index 0fde138e30..b47282ef8f 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/DateField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/DateField.tsx @@ -12,7 +12,7 @@ type DateFieldProps = { field: Field; itemGroupId?: string; onMetaUpdate?: () => void; - disabled?: boolean; + disabled: boolean; }; const DateField: React.FC = ({ field, itemGroupId, onMetaUpdate, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/DefaultField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/DefaultField.tsx index cdefaa8855..7399f13234 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/DefaultField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/DefaultField.tsx @@ -13,7 +13,7 @@ type DefaultFieldProps = { field: Field; itemGroupId?: string; onMetaUpdate?: () => Promise; - disabled?: boolean; + disabled: boolean; }; const DefaultField: React.FC = ({ diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/GeometryField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/GeometryField.tsx index 136349c891..b4d2b86def 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/GeometryField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/GeometryField.tsx @@ -11,7 +11,7 @@ import FieldTitle from "../../FieldTitle"; type DefaultFieldProps = { field: Field; itemGroupId?: string; - disabled?: boolean; + disabled: boolean; }; const GeometryField: React.FC = ({ field, itemGroupId, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/IntegerField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/IntegerField.tsx index 94f44ff614..fedea84610 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/IntegerField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/IntegerField.tsx @@ -11,7 +11,7 @@ import FieldTitle from "../../FieldTitle"; type DefaultFieldProps = { field: Field; itemGroupId?: string; - disabled?: boolean; + disabled: boolean; }; const IntegerField: React.FC = ({ field, itemGroupId, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/MarkdownField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/MarkdownField.tsx index 883b7dcfdc..f29b4f4ef2 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/MarkdownField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/MarkdownField.tsx @@ -12,7 +12,7 @@ import FieldTitle from "../../FieldTitle"; type DefaultFieldProps = { field: Field; itemGroupId?: string; - disabled?: boolean; + disabled: boolean; }; const MarkdownField: React.FC = ({ field, itemGroupId, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/SelectField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/SelectField.tsx index 38ca5cc9b0..67e789f312 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/SelectField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/SelectField.tsx @@ -9,7 +9,7 @@ import FieldTitle from "../../FieldTitle"; type DefaultFieldProps = { field: Field; itemGroupId?: string; - disabled?: boolean; + disabled: boolean; }; const SelectField: React.FC = ({ field, itemGroupId, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/TagField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/TagField.tsx index 62d5d7c0c5..5be0d04bf8 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/TagField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/TagField.tsx @@ -11,9 +11,10 @@ import FieldTitle from "../../FieldTitle"; type TagFieldProps = { field: Field; onMetaUpdate?: () => void; + disabled: boolean; }; -const TagField: React.FC = ({ field, onMetaUpdate }) => { +const TagField: React.FC = ({ field, onMetaUpdate, disabled }) => { const t = useT(); return ( @@ -32,7 +33,8 @@ const TagField: React.FC = ({ field, onMetaUpdate }) => { onChange={onMetaUpdate} mode="multiple" tagRender={props => <>{props.label}} - allowClear> + allowClear + disabled={disabled}> {field.typeProperty?.tags?.map((tag: { id: string; name: string; color: string }) => ( {tag.name} @@ -40,7 +42,7 @@ const TagField: React.FC = ({ field, onMetaUpdate }) => { ))} ) : ( - {field.typeProperty?.tags?.map((tag: { id: string; name: string; color: string }) => ( {tag.name} diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/TextareaField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/TextareaField.tsx index ff92bde91f..03ee71eb31 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/TextareaField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/TextareaField.tsx @@ -12,7 +12,7 @@ import FieldTitle from "../../FieldTitle"; type DefaultFieldProps = { field: Field; itemGroupId?: string; - disabled?: boolean; + disabled: boolean; }; const TextareaField: React.FC = ({ field, itemGroupId, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/fields/FieldComponents/URLField.tsx b/web/src/components/molecules/Content/Form/fields/FieldComponents/URLField.tsx index 21fbf8408b..838871a0c5 100644 --- a/web/src/components/molecules/Content/Form/fields/FieldComponents/URLField.tsx +++ b/web/src/components/molecules/Content/Form/fields/FieldComponents/URLField.tsx @@ -11,7 +11,7 @@ type URLFieldProps = { field: Field; itemGroupId?: string; onMetaUpdate?: () => Promise; - disabled?: boolean; + disabled: boolean; }; const URLField: React.FC = ({ field, itemGroupId, onMetaUpdate, disabled }) => { diff --git a/web/src/components/molecules/Content/Form/index.tsx b/web/src/components/molecules/Content/Form/index.tsx index 9254ebd009..5369aeee37 100644 --- a/web/src/components/molecules/Content/Form/index.tsx +++ b/web/src/components/molecules/Content/Form/index.tsx @@ -42,6 +42,10 @@ import { FIELD_TYPE_COMPONENT_MAP } from "./fields/FieldTypesMap"; type Props = { title: string; item?: Item; + hasRequestCreateRight: boolean; + hasRequestUpdateRight: boolean; + hasPublishRight: boolean; + hasItemUpdateRight: boolean; loadingReference: boolean; linkedItemsModalList?: FormItem[]; showPublishAction: boolean; @@ -129,6 +133,10 @@ type Props = { const ContentForm: React.FC = ({ title, item, + hasRequestCreateRight, + hasRequestUpdateRight, + hasPublishRight, + hasItemUpdateRight, loadingReference, linkedItemsModalList, showPublishAction, @@ -439,7 +447,7 @@ const ContentForm: React.FC = ({ key: "addToRequest", label: t("Add to Request"), onClick: onAddItemToRequestModalOpen, - disabled: item?.status === "PUBLIC", + disabled: item?.status === "PUBLIC" || !hasRequestUpdateRight, }, { key: "unpublish", @@ -447,7 +455,7 @@ const ContentForm: React.FC = ({ onClick: () => { if (itemId) onUnpublish([itemId]); }, - disabled: item?.status === "DRAFT" || item?.status === "REVIEW", + disabled: item?.status === "DRAFT" || item?.status === "REVIEW" || !hasPublishRight, }, ]; if (showPublishAction) { @@ -455,7 +463,7 @@ const ContentForm: React.FC = ({ key: "NewRequest", label: t("New Request"), onClick: onModalOpen, - disabled: item?.status === "PUBLIC", + disabled: item?.status === "PUBLIC" || !hasRequestCreateRight, }); } return menuItems; @@ -463,10 +471,13 @@ const ContentForm: React.FC = ({ t, onAddItemToRequestModalOpen, item?.status, + hasRequestUpdateRight, + hasPublishRight, showPublishAction, itemId, onUnpublish, onModalOpen, + hasRequestCreateRight, ]); const handlePublishSubmit = useCallback(async () => { @@ -482,6 +493,11 @@ const ContentForm: React.FC = ({ setPublishModalOpen(false); }, [setPublishModalOpen]); + const fieldDisabled = useMemo( + () => !!itemId && !hasItemUpdateRight, + [hasItemUpdateRight, itemId], + ); + return ( <> = ({ type="primary" onClick={handlePublishSubmit} loading={publishLoading} - disabled={item?.status === "PUBLIC"}> + disabled={item?.status === "PUBLIC" || !hasPublishRight}> {t("Publish")} )} @@ -512,7 +528,7 @@ const ContentForm: React.FC = ({ )} @@ -544,6 +560,7 @@ const ContentForm: React.FC = ({ totalCount={totalCount} page={page} pageSize={pageSize} + disabled={fieldDisabled} onAssetTableChange={onAssetTableChange} onUploadModalCancel={onUploadModalCancel} setUploadUrl={setUploadUrl} @@ -571,6 +588,7 @@ const ContentForm: React.FC = ({ linkItemModalTotalCount={linkItemModalTotalCount} linkItemModalPage={linkItemModalPage} linkItemModalPageSize={linkItemModalPageSize} + disabled={fieldDisabled} onReferenceModelUpdate={onReferenceModelUpdate} onSearchTerm={onSearchTerm} onLinkItemTableReload={onLinkItemTableReload} @@ -603,6 +621,7 @@ const ContentForm: React.FC = ({ linkItemModalTotalCount={linkItemModalTotalCount} linkItemModalPage={linkItemModalPage} linkItemModalPageSize={linkItemModalPageSize} + disabled={fieldDisabled} onSearchTerm={onSearchTerm} onReferenceModelUpdate={onReferenceModelUpdate} onLinkItemTableReload={onLinkItemTableReload} @@ -629,7 +648,7 @@ const ContentForm: React.FC = ({ return ( - + ); } else { @@ -649,7 +668,7 @@ const ContentForm: React.FC = ({ return ( - + ); } @@ -666,7 +685,11 @@ const ContentForm: React.FC = ({ ] || DefaultField; return ( - + ); })} diff --git a/web/src/components/molecules/Content/LinkItemModal/index.tsx b/web/src/components/molecules/Content/LinkItemModal/index.tsx index 76ee255a1f..fef9ec598e 100644 --- a/web/src/components/molecules/Content/LinkItemModal/index.tsx +++ b/web/src/components/molecules/Content/LinkItemModal/index.tsx @@ -10,6 +10,8 @@ import { OptionConfig, } from "@reearth-cms/components/atoms/ProTable"; import Search from "@reearth-cms/components/atoms/Search"; +import Space from "@reearth-cms/components/atoms/Space"; +import UserAvatar from "@reearth-cms/components/atoms/UserAvatar"; import ResizableProTable from "@reearth-cms/components/molecules/Common/ResizableProTable"; import { CorrespondingField } from "@reearth-cms/components/molecules/Schema/types"; import { useT } from "@reearth-cms/i18n"; @@ -160,6 +162,12 @@ const LinkItemModal: React.FC = ({ ellipsis: true, width: 100, minWidth: 100, + render: (_, item) => ( + + + {item.createdBy} + + ), }, { title: t("Created At"), diff --git a/web/src/components/molecules/Content/LinkItemRequestModal/LinkItemRequestModal.tsx b/web/src/components/molecules/Content/LinkItemRequestModal/LinkItemRequestModal.tsx index 5a870f38fc..0fa78c4b11 100644 --- a/web/src/components/molecules/Content/LinkItemRequestModal/LinkItemRequestModal.tsx +++ b/web/src/components/molecules/Content/LinkItemRequestModal/LinkItemRequestModal.tsx @@ -99,14 +99,17 @@ const LinkItemRequestModal: React.FC = ({ }, { title: t("Created By"), - dataIndex: "createdBy.name", + dataIndex: ["createdBy", "name"], key: "createdBy", ellipsis: true, width: 100, minWidth: 100, - render: (_, request) => { - return request.createdBy?.name; - }, + render: (_, request) => ( + + + {request.createdBy?.name} + + ), }, { title: t("Reviewers"), diff --git a/web/src/components/molecules/Content/List/index.tsx b/web/src/components/molecules/Content/List/index.tsx index 40376f487a..f95bdee980 100644 --- a/web/src/components/molecules/Content/List/index.tsx +++ b/web/src/components/molecules/Content/List/index.tsx @@ -1,5 +1,5 @@ import styled from "@emotion/styled"; -import { Dispatch, SetStateAction } from "react"; +import { Dispatch, SetStateAction, Key } from "react"; import Button from "@reearth-cms/components/atoms/Button"; import Icon from "@reearth-cms/components/atoms/Icon"; @@ -49,7 +49,7 @@ type Props = { onPublish: (itemIds: string[]) => Promise; onUnpublish: (itemIds: string[]) => Promise; onItemSelect: (itemId: string) => void; - setSelectedItems: (input: { selectedRows: { itemId: string; version?: string }[] }) => void; + onSelect: (selectedRowKeys: Key[], selectedRows: ContentTableField[]) => void; onCollapse?: (collapse: boolean) => void; onItemAdd: () => void; onItemsReload: () => void; @@ -62,6 +62,11 @@ type Props = { onAddItemToRequestModalOpen: () => void; onRequestSearchTerm: (term: string) => void; onRequestTableReload: () => void; + hasCreateRight: boolean; + hasDeleteRight: boolean; + hasPublishRight: boolean; + hasRequestUpdateRight: boolean; + showPublishAction: boolean; }; const ContentListMolecule: React.FC = ({ @@ -99,7 +104,7 @@ const ContentListMolecule: React.FC = ({ onSearchTerm, onFilterChange, onContentTableChange, - setSelectedItems, + onSelect, onItemSelect, onCollapse, onItemAdd, @@ -108,6 +113,11 @@ const ContentListMolecule: React.FC = ({ onItemDelete, onRequestSearchTerm, onRequestTableReload, + hasCreateRight, + hasDeleteRight, + hasPublishRight, + hasRequestUpdateRight, + showPublishAction, }) => { const t = useT(); @@ -135,7 +145,7 @@ const ContentListMolecule: React.FC = ({ type="primary" onClick={onItemAdd} icon={} - disabled={!model}> + disabled={!model || !hasCreateRight}> {t("New Item")} } @@ -158,7 +168,7 @@ const ContentListMolecule: React.FC = ({ onSearchTerm={onSearchTerm} onFilterChange={onFilterChange} onContentTableChange={onContentTableChange} - setSelectedItems={setSelectedItems} + onSelect={onSelect} onItemSelect={onItemSelect} onItemsReload={onItemsReload} onItemEdit={onItemEdit} @@ -179,6 +189,10 @@ const ContentListMolecule: React.FC = ({ modelKey={model?.key} onRequestSearchTerm={onRequestSearchTerm} onRequestTableReload={onRequestTableReload} + hasDeleteRight={hasDeleteRight} + hasPublishRight={hasPublishRight} + hasRequestUpdateRight={hasRequestUpdateRight} + showPublishAction={showPublishAction} /> )} diff --git a/web/src/components/molecules/Content/RenderField/index.tsx b/web/src/components/molecules/Content/RenderField/index.tsx index 00218b58a9..e72d4f3ee8 100644 --- a/web/src/components/molecules/Content/RenderField/index.tsx +++ b/web/src/components/molecules/Content/RenderField/index.tsx @@ -36,7 +36,8 @@ export const renderField = ( : option?.key; update?.(value); }} - placeholder="-"> + placeholder="-" + disabled={!update}> {tags?.map(({ id, name, color }) => ( {name} diff --git a/web/src/components/molecules/Content/Table/index.tsx b/web/src/components/molecules/Content/Table/index.tsx index a278702724..17d0750fc9 100644 --- a/web/src/components/molecules/Content/Table/index.tsx +++ b/web/src/components/molecules/Content/Table/index.tsx @@ -76,7 +76,7 @@ type Props = { onFilterChange: (filter?: ConditionInput[]) => void; onContentTableChange: (page: number, pageSize: number, sorter?: ItemSort) => void; onItemSelect: (itemId: string) => void; - setSelectedItems: (input: { selectedRows: { itemId: string; version?: string }[] }) => void; + onSelect: (selectedRowKeys: Key[], selectedRows: ContentTableField[]) => void; onItemEdit: (itemId: string) => void; onItemDelete: (itemIds: string[]) => Promise; onPublish: (itemIds: string[]) => Promise; @@ -90,6 +90,10 @@ type Props = { modelKey?: string; onRequestSearchTerm: (term: string) => void; onRequestTableReload: () => void; + hasDeleteRight: boolean; + hasPublishRight: boolean; + hasRequestUpdateRight: boolean; + showPublishAction: boolean; }; const ContentTable: React.FC = ({ @@ -123,13 +127,17 @@ const ContentTable: React.FC = ({ onFilterChange, onContentTableChange, onItemSelect, - setSelectedItems, + onSelect, onItemEdit, onItemDelete, onItemsReload, modelKey, onRequestSearchTerm, onRequestTableReload, + hasDeleteRight, + hasPublishRight, + hasRequestUpdateRight, + showPublishAction, }) => { const [currentWorkspace] = useWorkspace(); const t = useT(); @@ -226,8 +234,8 @@ const ContentTable: React.FC = ({ sortOrder: sortOrderGet("CREATION_USER"), render: (_, item) => ( - - {item.createdBy} + + {item.createdBy.name} ), sorter: true, @@ -284,14 +292,9 @@ const ContentTable: React.FC = ({ const rowSelection: TableRowSelection = useMemo( () => ({ selectedRowKeys: selectedItems.selectedRows.map(item => item.itemId), - onChange: (_selectedRowKeys: Key[], selectedRows: Item[]) => { - setSelectedItems({ - ...selectedItems, - selectedRows: selectedRows.map(row => ({ itemId: row.id, version: row.version })), - }); - }, + onChange: onSelect, }), - [selectedItems, setSelectedItems], + [onSelect, selectedItems.selectedRows], ); const publishConfirm = useCallback( @@ -322,14 +325,16 @@ const ContentTable: React.FC = ({ onClick={() => { publishConfirm(props.selectedRowKeys); }} - loading={publishLoading}> + loading={publishLoading} + disabled={!hasPublishRight || !showPublishAction}> {t("Publish")} @@ -354,11 +361,15 @@ const ContentTable: React.FC = ({ }, [ deleteLoading, + hasDeleteRight, + hasPublishRight, + hasRequestUpdateRight, onAddItemToRequestModalOpen, onItemDelete, onUnpublish, publishConfirm, publishLoading, + showPublishAction, t, unpublishLoading, ], diff --git a/web/src/components/molecules/Content/types.ts b/web/src/components/molecules/Content/types.ts index 392bfa8e86..d655e19de3 100644 --- a/web/src/components/molecules/Content/types.ts +++ b/web/src/components/molecules/Content/types.ts @@ -1,3 +1,4 @@ +import { User } from "@reearth-cms/components/molecules/AccountSettings/types"; import { Request } from "@reearth-cms/components/molecules/Request/types"; import { FieldType } from "@reearth-cms/components/molecules/Schema/types"; @@ -27,8 +28,8 @@ export type Item = { id: string; version: string; schemaId: string; - createdBy?: string; - updatedBy?: string; + createdBy?: Partial; + updatedBy?: Partial; createdAt: Date; updatedAt: Date; status: ItemStatus; @@ -54,7 +55,7 @@ export type FormItem = { export type ContentTableField = { id: string; - createdBy: string; + createdBy: { id: string; name: string }; updatedBy: string; schemaId: string; status: ItemStatus; diff --git a/web/src/components/molecules/Integration/IntegrationTable/index.tsx b/web/src/components/molecules/Integration/IntegrationTable/index.tsx index a656aba5ee..9aa800d505 100644 --- a/web/src/components/molecules/Integration/IntegrationTable/index.tsx +++ b/web/src/components/molecules/Integration/IntegrationTable/index.tsx @@ -12,6 +12,7 @@ import { } from "@reearth-cms/components/atoms/ProTable"; import Search from "@reearth-cms/components/atoms/Search"; import Space from "@reearth-cms/components/atoms/Space"; +import UserAvatar from "@reearth-cms/components/atoms/UserAvatar"; import ResizableProTable from "@reearth-cms/components/molecules/Common/ResizableProTable"; import { IntegrationMember } from "@reearth-cms/components/molecules/Integration/types"; import { useT } from "@reearth-cms/i18n"; @@ -32,6 +33,9 @@ type Props = { onTableChange: (page: number, pageSize: number) => void; loading: boolean; onReload: () => void; + hasConnectRight: boolean; + hasUpdateRight: boolean; + hasDeleteRight: boolean; }; const IntegrationTable: React.FC = ({ @@ -48,6 +52,9 @@ const IntegrationTable: React.FC = ({ onTableChange, loading, onReload, + hasConnectRight, + hasUpdateRight, + hasDeleteRight, }) => { const t = useT(); @@ -75,20 +82,33 @@ const IntegrationTable: React.FC = ({ key: "creator", width: 250, minWidth: 100, + render: (_, item) => ( + + + {item.integration?.developer.name} + + ), }, { key: "action", render: (_, integrationMember) => ( - onIntegrationSettingsModalOpen(integrationMember)} - icon="settings" + ), - [deleteLoading, onIntegrationRemove, t], + [deleteLoading, hasDeleteRight, onIntegrationRemove, t], ); const options = useMemo( @@ -159,7 +180,11 @@ const IntegrationTable: React.FC = ({ }> + } @@ -173,7 +198,8 @@ const IntegrationTable: React.FC = ({ @@ -235,11 +261,6 @@ const Title = styled.h1` color: #000; `; -const StyledIcon = styled(Icon)` - color: #1890ff; - font-size: 18px; -`; - const TableWrapper = styled.div` background-color: #fff; border-top: 1px solid #f0f0f0; diff --git a/web/src/components/molecules/Member/MemberRoleModal/index.tsx b/web/src/components/molecules/Member/MemberRoleModal/index.tsx index c7ed5f9705..8e0ff200a7 100644 --- a/web/src/components/molecules/Member/MemberRoleModal/index.tsx +++ b/web/src/components/molecules/Member/MemberRoleModal/index.tsx @@ -4,12 +4,12 @@ import Button from "@reearth-cms/components/atoms/Button"; import Form from "@reearth-cms/components/atoms/Form"; import Modal from "@reearth-cms/components/atoms/Modal"; import Select from "@reearth-cms/components/atoms/Select"; -import { RoleUnion } from "@reearth-cms/components/molecules/Member/types"; +import { Role } from "@reearth-cms/components/molecules/Member/types"; import { UserMember } from "@reearth-cms/components/molecules/Workspace/types"; import { useT } from "@reearth-cms/i18n"; type FormValues = { - role: RoleUnion; + role: Role; }; type Props = { @@ -17,7 +17,7 @@ type Props = { member: UserMember; loading: boolean; onClose: () => void; - onSubmit: (userId: string, role: RoleUnion) => Promise; + onSubmit: (userId: string, role: Role) => Promise; }; const MemberRoleModal: React.FC = ({ open, member, loading, onClose, onSubmit }) => { diff --git a/web/src/components/molecules/Member/MemberTable/index.tsx b/web/src/components/molecules/Member/MemberTable/index.tsx index c6eca16a34..49d51abcf7 100644 --- a/web/src/components/molecules/Member/MemberTable/index.tsx +++ b/web/src/components/molecules/Member/MemberTable/index.tsx @@ -22,7 +22,6 @@ type Props = { id?: string; myWorkspace?: string; }; - isOwner: boolean; isAbleToLeave: boolean; handleMemberRemoveFromWorkspace: (userIds: string[]) => Promise; onLeave: (userId: string) => Promise; @@ -39,11 +38,13 @@ type Props = { onTableChange: (page: number, pageSize: number) => void; loading: boolean; onReload: () => void; + hasInviteRight: boolean; + hasRemoveRight: boolean; + hasChangeRoleRight: boolean; }; const MemberTable: React.FC = ({ me, - isOwner, isAbleToLeave, handleMemberRemoveFromWorkspace, onLeave, @@ -58,6 +59,9 @@ const MemberTable: React.FC = ({ onTableChange, loading, onReload, + hasInviteRight, + hasRemoveRight, + hasChangeRoleRight, }) => { const t = useT(); @@ -167,7 +171,7 @@ const MemberTable: React.FC = ({ handleRoleModalOpen(member)} - disabled={!isOwner || member.userId === me.id}> + disabled={!hasChangeRoleRight || member.userId === me.id}> {t("Change Role?")} @@ -185,7 +189,8 @@ const MemberTable: React.FC = ({ type="link" onClick={() => { handleMemberDelete([member.user]); - }}> + }} + disabled={!hasRemoveRight}> {t("Remove")} )} @@ -195,9 +200,10 @@ const MemberTable: React.FC = ({ [ workspaceUserMembers, t, - isOwner, + hasChangeRoleRight, me.id, isAbleToLeave, + hasRemoveRight, handleRoleModalOpen, leaveConfirm, handleMemberDelete, @@ -247,11 +253,17 @@ const MemberTable: React.FC = ({ const alertOptions = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (props: any) => ( - handleMemberDelete(props.selectedRows)}> - {t("Remove")} - + ), - [handleMemberDelete, t], + [handleMemberDelete, t, hasRemoveRight], ); const options = useMemo( @@ -269,7 +281,8 @@ const MemberTable: React.FC = ({ } @@ -340,11 +353,4 @@ const ActionButton = styled(Button)` padding-right: 0; `; -const DeleteButton = styled.a` - color: #ff7875; - :hover { - color: #ff7875b3; - } -`; - export default MemberTable; diff --git a/web/src/components/molecules/Member/types.ts b/web/src/components/molecules/Member/types.ts index 7bd31d15dd..0d041305f9 100644 --- a/web/src/components/molecules/Member/types.ts +++ b/web/src/components/molecules/Member/types.ts @@ -1,7 +1,87 @@ +import { t } from "@reearth-cms/i18n"; + export type User = { id: string; name: string; email: string; }; -export type RoleUnion = "READER" | "WRITER" | "MAINTAINER" | "OWNER"; +export type Role = "READER" | "WRITER" | "MAINTAINER" | "OWNER"; +t("WRITER"); +t("READER"); +t("MAINTAINER"); +t("OWNER"); + +export type UserRights = { + role: Role; + workspace: { + update: boolean; + delete: boolean; + }; + workspaceSetting: { + update: boolean; + }; + integrations: { + connect: boolean; + update: boolean; + delete: boolean; + }; + members: { + invite: boolean; + remove: boolean; + changeRole: boolean; + leave: boolean; + }; + project: { + create: boolean; + read: boolean; + update: boolean; + delete: boolean; + publish: boolean; + }; + model: { + create: boolean; + read: boolean; + update: boolean; + delete: boolean; + publish: boolean; + }; + schema: { + create: boolean; + read: boolean; + update: boolean; + delete: boolean; + }; + view: { + create: boolean; + read: boolean; + update: boolean; + delete: boolean; + }; + content: { + create: boolean; + read: boolean; + update: boolean | null; + delete: boolean | null; + publish: boolean; + }; + asset: { + create: boolean; + read: boolean; + update: boolean | null; + delete: boolean | null; + }; + request: { + create: boolean; + read: boolean; + update: boolean | null; + close: boolean | null; + approve: boolean; + }; + comment: { + create: boolean; + read: boolean; + update: boolean | null; + delete: boolean | null; + }; +}; diff --git a/web/src/components/molecules/Model/ModelsList/Groups.tsx b/web/src/components/molecules/Model/ModelsList/Groups.tsx index 7fa77e0d11..3fb0630b4c 100644 --- a/web/src/components/molecules/Model/ModelsList/Groups.tsx +++ b/web/src/components/molecules/Model/ModelsList/Groups.tsx @@ -8,6 +8,8 @@ type Props = { selectedKey?: string; groups?: Group[]; open: boolean; + hasCreateRight: boolean; + hasUpdateRight: boolean; onModalOpen: () => void; onGroupKeyCheck: (key: string, ignoredKey?: string) => Promise; onClose: () => void; @@ -21,6 +23,8 @@ const Groups: React.FC = ({ selectedKey, groups, open, + hasCreateRight, + hasUpdateRight, onModalOpen, onGroupKeyCheck, onClose, @@ -34,6 +38,8 @@ const Groups: React.FC = ({ selectedKey={selectedKey} groups={groups} collapsed={collapsed} + hasCreateRight={hasCreateRight} + hasUpdateRight={hasUpdateRight} onGroupSelect={onGroupSelect} onModalOpen={onModalOpen} onUpdateGroupsOrder={onUpdateGroupsOrder} diff --git a/web/src/components/molecules/Model/ModelsList/GroupsList.tsx b/web/src/components/molecules/Model/ModelsList/GroupsList.tsx index eb96dd8897..c389963e6a 100644 --- a/web/src/components/molecules/Model/ModelsList/GroupsList.tsx +++ b/web/src/components/molecules/Model/ModelsList/GroupsList.tsx @@ -13,6 +13,8 @@ type Props = { selectedKey?: string; groups?: Group[]; collapsed?: boolean; + hasCreateRight: boolean; + hasUpdateRight: boolean; onModalOpen: () => void; onGroupSelect?: (groupId: string) => void; onUpdateGroupsOrder: (groupIds: string[]) => Promise; @@ -22,6 +24,8 @@ const GroupsList: React.FC = ({ selectedKey, groups, collapsed, + hasCreateRight, + hasUpdateRight, onModalOpen, onGroupSelect, onUpdateGroupsOrder, @@ -86,7 +90,11 @@ const GroupsList: React.FC = ({
{t("GROUPS")} - } type="text"> + } + type="link" + disabled={!hasCreateRight}> {!collapsed && t("Add")} @@ -94,7 +102,7 @@ const GroupsList: React.FC = ({ )} onDragEnd(fromIndex, toIndex)}> ` `; const SchemaAddButton = styled(Button)` - color: #1890ff; padding: 4px; - &:hover, - &:active, - &:focus { - color: #1890ff; - } `; const SchemaStyledMenuTitle = styled.h1` diff --git a/web/src/components/molecules/Model/ModelsList/Models.tsx b/web/src/components/molecules/Model/ModelsList/Models.tsx index 9161a048d7..3814ae15a4 100644 --- a/web/src/components/molecules/Model/ModelsList/Models.tsx +++ b/web/src/components/molecules/Model/ModelsList/Models.tsx @@ -9,6 +9,8 @@ type Props = { selectedKey?: string; models?: Model[]; open: boolean; + hasCreateRight: boolean; + hasUpdateRight: boolean; onModalOpen: () => void; onModelKeyCheck: (key: string, ignoredKey?: string) => Promise; onClose: () => void; @@ -22,6 +24,8 @@ const Models: React.FC = ({ selectedKey, models, open, + hasCreateRight, + hasUpdateRight, onModalOpen, onModelKeyCheck, onClose, @@ -35,6 +39,8 @@ const Models: React.FC = ({ selectedKey={selectedKey} models={models} collapsed={collapsed} + hasCreateRight={hasCreateRight} + hasUpdateRight={hasUpdateRight} onModelSelect={onModelSelect} onModalOpen={onModalOpen} onUpdateModelsOrder={onUpdateModelsOrder} diff --git a/web/src/components/molecules/Model/ModelsList/ModelsList.tsx b/web/src/components/molecules/Model/ModelsList/ModelsList.tsx index e39947fb20..12f770c6e8 100644 --- a/web/src/components/molecules/Model/ModelsList/ModelsList.tsx +++ b/web/src/components/molecules/Model/ModelsList/ModelsList.tsx @@ -13,6 +13,8 @@ type Props = { selectedKey?: string; models?: Model[]; collapsed: boolean; + hasCreateRight: boolean; + hasUpdateRight: boolean; onModalOpen: () => void; onModelSelect: (modelId: string) => void; onUpdateModelsOrder: (modelIds: string[]) => Promise; @@ -22,6 +24,8 @@ const ModelsList: React.FC = ({ selectedKey, models, collapsed, + hasCreateRight, + hasUpdateRight, onModalOpen, onModelSelect, onUpdateModelsOrder, @@ -91,7 +95,11 @@ const ModelsList: React.FC = ({
{t("MODELS")} - } type="text"> + } + type="link" + disabled={!hasCreateRight}> {!collapsed && t("Add")} @@ -99,7 +107,7 @@ const ModelsList: React.FC = ({ )} onDragEnd(fromIndex, toIndex)}> ` `; const SchemaAddButton = styled(Button)` - color: #1890ff; padding: 4px; - &:hover, - &:active, - &:focus { - color: #1890ff; - } `; const SchemaStyledMenuTitle = styled.h1` diff --git a/web/src/components/molecules/ProjectList/ProjectList.tsx b/web/src/components/molecules/ProjectList/ProjectList.tsx index be051b7f1b..5637684c7d 100644 --- a/web/src/components/molecules/ProjectList/ProjectList.tsx +++ b/web/src/components/molecules/ProjectList/ProjectList.tsx @@ -8,6 +8,7 @@ import { Project } from "@reearth-cms/components/molecules/Workspace/types"; import { useT } from "@reearth-cms/i18n"; type Props = { + hasCreateRight: boolean; projects?: Project[]; loading: boolean; onProjectModalOpen: () => void; @@ -15,6 +16,7 @@ type Props = { }; const ProjectList: React.FC = ({ + hasCreateRight, projects, loading, onProjectModalOpen, @@ -31,7 +33,11 @@ const ProjectList: React.FC = ({ {t("No Projects Yet")} {t("Create a new project")} - diff --git a/web/src/components/molecules/ProjectOverview/ModelCard.tsx b/web/src/components/molecules/ProjectOverview/ModelCard.tsx index 462b55c845..d8c0a15782 100644 --- a/web/src/components/molecules/ProjectOverview/ModelCard.tsx +++ b/web/src/components/molecules/ProjectOverview/ModelCard.tsx @@ -9,6 +9,8 @@ import { useT } from "@reearth-cms/i18n"; type Props = { model: Model; + hasUpdateRight: boolean; + hasDeleteRight: boolean; onSchemaNavigate: (modelId: string) => void; onContentNavigate: (modelId: string) => void; onModelDeletionModalOpen: (model: Model) => Promise; @@ -17,6 +19,8 @@ type Props = { const ModelCard: React.FC = ({ model, + hasUpdateRight, + hasDeleteRight, onSchemaNavigate, onContentNavigate, onModelDeletionModalOpen, @@ -31,15 +35,17 @@ const ModelCard: React.FC = ({ key: "edit", label: t("Edit"), onClick: () => onModelUpdateModalOpen(model), + disabled: !hasUpdateRight, }, { key: "delete", label: t("Delete"), onClick: () => onModelDeletionModalOpen(model), danger: true, + disabled: !hasDeleteRight, }, ], - [t, model, onModelUpdateModalOpen, onModelDeletionModalOpen], + [t, hasUpdateRight, hasDeleteRight, onModelUpdateModalOpen, model, onModelDeletionModalOpen], ); return ( diff --git a/web/src/components/molecules/ProjectOverview/index.tsx b/web/src/components/molecules/ProjectOverview/index.tsx index 56839d3ef8..a129b82f95 100644 --- a/web/src/components/molecules/ProjectOverview/index.tsx +++ b/web/src/components/molecules/ProjectOverview/index.tsx @@ -13,6 +13,9 @@ type Props = { projectName?: string; projectDescription?: string; models?: Model[]; + hasCreateRight: boolean; + hasUpdateRight: boolean; + hasDeleteRight: boolean; onModelModalOpen: () => void; onSchemaNavigate: (modelId: string) => void; onContentNavigate: (modelId: string) => void; @@ -24,6 +27,9 @@ const ProjectOverview: React.FC = ({ projectName, projectDescription, models, + hasCreateRight, + hasUpdateRight, + hasDeleteRight, onModelModalOpen, onSchemaNavigate, onContentNavigate, @@ -37,7 +43,11 @@ const ProjectOverview: React.FC = ({ } onClick={onModelModalOpen}> + }> @@ -46,6 +56,8 @@ const ProjectOverview: React.FC = ({ Promise; }; -const DangerZone: React.FC = ({ onProjectDelete }) => { +const DangerZone: React.FC = ({ hasDeleteRight, onProjectDelete }) => { const t = useT(); const { confirm } = Modal; @@ -34,7 +35,11 @@ const DangerZone: React.FC = ({ onProjectDelete }) => { "Permanently removes your project and all of its contents from Re:Earth CMS. This action is not reversible, so please continue with caution.", )} - diff --git a/web/src/components/molecules/ProjectSettings/GeneralForm.tsx b/web/src/components/molecules/ProjectSettings/GeneralForm.tsx index c37452a8b6..fab290f1c0 100644 --- a/web/src/components/molecules/ProjectSettings/GeneralForm.tsx +++ b/web/src/components/molecules/ProjectSettings/GeneralForm.tsx @@ -12,6 +12,7 @@ import { Project } from "../Workspace/types"; type Props = { project: Project; + hasUpdateRight: boolean; onProjectUpdate: (name?: string, alias?: string, description?: string) => Promise; onProjectAliasCheck: (alias: string) => Promise; }; @@ -22,7 +23,12 @@ type FormType = { description: string; }; -const ProjectGeneralForm: React.FC = ({ project, onProjectUpdate, onProjectAliasCheck }) => { +const ProjectGeneralForm: React.FC = ({ + project, + hasUpdateRight, + onProjectUpdate, + onProjectAliasCheck, +}) => { const [form] = Form.useForm(); const t = useT(); const [isDisabled, setIsDisabled] = useState(true); @@ -72,7 +78,7 @@ const ProjectGeneralForm: React.FC = ({ project, onProjectUpdate, onProje name="name" label={t("Name")} rules={[{ required: true, message: t("Please input the name of project!") }]}> - + = ({ project, onProjectUpdate, onProje }, }, ]}> - + -