feat: add timezone selection to workspace settings (#8248)

* feat: add timezone selection to workspace onboarding, creation and settings

* refactor: remove timezone selection from workspace creation and onboarding forms
This commit is contained in:
b-saikrishnakanth 2025-12-10 00:59:39 +05:30 committed by GitHub
parent 0bfb74d4c0
commit 079a624006
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 187 additions and 131 deletions

View file

@ -18,7 +18,7 @@ import { AuthenticationWrapper } from "@/lib/wrappers/authentication-wrapper";
// plane web helpers // plane web helpers
import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper"; import { getIsWorkspaceCreationDisabled } from "@/plane-web/helpers/instance.helper";
function CreateWorkspacePage() { const CreateWorkspacePage = observer(function CreateWorkspacePage() {
const { t } = useTranslation(); const { t } = useTranslation();
// router // router
const router = useAppRouter(); const router = useAppRouter();
@ -104,6 +104,6 @@ function CreateWorkspacePage() {
</div> </div>
</AuthenticationWrapper> </AuthenticationWrapper>
); );
} });
export default observer(CreateWorkspacePage); export default CreateWorkspacePage;

View file

@ -1,4 +1,4 @@
import React, { useState } from "react"; import { useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { CircleCheck } from "lucide-react"; import { CircleCheck } from "lucide-react";
@ -71,47 +71,46 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
const handleCreateWorkspace = async (formData: IWorkspace) => { const handleCreateWorkspace = async (formData: IWorkspace) => {
if (isSubmitting) return; if (isSubmitting) return;
await workspaceService try {
.workspaceSlugCheck(formData.slug) const res = (await workspaceService.workspaceSlugCheck(formData.slug)) as { status: boolean };
.then(async (res) => { if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) {
if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { setSlugError(false);
setSlugError(false); try {
await createWorkspace(formData) const workspaceResponse = await createWorkspace(formData);
.then(async (workspaceResponse) => { setToast({
setToast({ type: TOAST_TYPE.SUCCESS,
type: TOAST_TYPE.SUCCESS, title: t("workspace_creation.toast.success.title"),
title: t("workspace_creation.toast.success.title"), message: t("workspace_creation.toast.success.message"),
message: t("workspace_creation.toast.success.message"), });
}); captureSuccess({
captureSuccess({ eventName: WORKSPACE_TRACKER_EVENTS.create,
eventName: WORKSPACE_TRACKER_EVENTS.create, payload: { slug: formData.slug },
payload: { slug: formData.slug }, });
}); await fetchWorkspaces();
await fetchWorkspaces(); await completeStep(workspaceResponse.id);
await completeStep(workspaceResponse.id); onComplete(formData.organization_size === "Just myself");
onComplete(formData.organization_size === "Just myself"); } catch {
}) captureError({
.catch(() => { eventName: WORKSPACE_TRACKER_EVENTS.create,
captureError({ payload: { slug: formData.slug },
eventName: WORKSPACE_TRACKER_EVENTS.create, error: new Error("Error creating workspace"),
payload: { slug: formData.slug }, });
error: new Error("Error creating workspace"), setToast({
}); type: TOAST_TYPE.ERROR,
setToast({ title: t("workspace_creation.toast.error.title"),
type: TOAST_TYPE.ERROR, message: t("workspace_creation.toast.error.message"),
title: t("workspace_creation.toast.error.title"), });
message: t("workspace_creation.toast.error.message"), }
}); } else {
}); setSlugError(true);
} else setSlugError(true); }
}) } catch {
.catch(() => setToast({
setToast({ type: TOAST_TYPE.ERROR,
type: TOAST_TYPE.ERROR, title: t("workspace_creation.toast.error.title"),
title: t("workspace_creation.toast.error.title"), message: t("workspace_creation.toast.error.message"),
message: t("workspace_creation.toast.error.message"), });
}) }
);
}; };
const completeStep = async (workspaceId: string) => { const completeStep = async (workspaceId: string) => {
@ -136,7 +135,12 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
); );
} }
return ( return (
<form className="flex flex-col gap-10" onSubmit={handleSubmit(handleCreateWorkspace)}> <form
className="flex flex-col gap-10"
onSubmit={(e) => {
void handleSubmit(handleCreateWorkspace)(e);
}}
>
<CommonOnboardingHeader title="Create your workspace" description="All your work — unified." /> <CommonOnboardingHeader title="Create your workspace" description="All your work — unified." />
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@ -181,6 +185,7 @@ export const WorkspaceCreateStep = observer(function WorkspaceCreateStep({
"border-red-500": errors.name, "border-red-500": errors.name,
} }
)} )}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus autoFocus
/> />
</div> </div>

View file

@ -67,47 +67,46 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
} = useForm<IWorkspace>({ defaultValues, mode: "onChange" }); } = useForm<IWorkspace>({ defaultValues, mode: "onChange" });
const handleCreateWorkspace = async (formData: IWorkspace) => { const handleCreateWorkspace = async (formData: IWorkspace) => {
await workspaceService try {
.workspaceSlugCheck(formData.slug) const res = (await workspaceService.workspaceSlugCheck(formData.slug)) as { status: boolean };
.then(async (res) => { if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) {
if (res.status === true && !RESTRICTED_URLS.includes(formData.slug)) { setSlugError(false);
setSlugError(false);
await createWorkspace(formData) try {
.then(async (res) => { const workspaceResponse = await createWorkspace(formData);
captureSuccess({ captureSuccess({
eventName: WORKSPACE_TRACKER_EVENTS.create, eventName: WORKSPACE_TRACKER_EVENTS.create,
payload: { slug: formData.slug }, payload: { slug: formData.slug },
}); });
setToast({ setToast({
type: TOAST_TYPE.SUCCESS, type: TOAST_TYPE.SUCCESS,
title: t("workspace_creation.toast.success.title"), title: t("workspace_creation.toast.success.title"),
message: t("workspace_creation.toast.success.message"), message: t("workspace_creation.toast.success.message"),
}); });
if (onSubmit) await onSubmit(res); if (onSubmit) await onSubmit(workspaceResponse);
}) } catch {
.catch(() => { captureError({
captureError({ eventName: WORKSPACE_TRACKER_EVENTS.create,
eventName: WORKSPACE_TRACKER_EVENTS.create, payload: { slug: formData.slug },
payload: { slug: formData.slug }, error: new Error("Error creating workspace"),
error: new Error("Error creating workspace"), });
}); setToast({
setToast({ type: TOAST_TYPE.ERROR,
type: TOAST_TYPE.ERROR, title: t("workspace_creation.toast.error.title"),
title: t("workspace_creation.toast.error.title"), message: t("workspace_creation.toast.error.message"),
message: t("workspace_creation.toast.error.message"), });
}); }
}); } else {
} else setSlugError(true); setSlugError(true);
}) }
.catch(() => { } catch {
setToast({ setToast({
type: TOAST_TYPE.ERROR, type: TOAST_TYPE.ERROR,
title: t("workspace_creation.toast.error.title"), title: t("workspace_creation.toast.error.title"),
message: t("workspace_creation.toast.error.message"), message: t("workspace_creation.toast.error.message"),
});
}); });
}
}; };
useEffect( useEffect(
@ -119,7 +118,12 @@ export const CreateWorkspaceForm = observer(function CreateWorkspaceForm(props:
); );
return ( return (
<form className="space-y-6 sm:space-y-9" onSubmit={handleSubmit(handleCreateWorkspace)}> <form
className="space-y-6 sm:space-y-9"
onSubmit={(e) => {
void handleSubmit(handleCreateWorkspace)(e);
}}
>
<div className="space-y-6 sm:space-y-7"> <div className="space-y-6 sm:space-y-7">
<div className="space-y-1 text-sm"> <div className="space-y-1 text-sm">
<label htmlFor="workspaceName"> <label htmlFor="workspaceName">

View file

@ -19,6 +19,7 @@ import { copyUrlToClipboard, getFileURL } from "@plane/utils";
// components // components
import { WorkspaceImageUploadModal } from "@/components/core/modals/workspace-image-upload-modal"; import { WorkspaceImageUploadModal } from "@/components/core/modals/workspace-image-upload-modal";
// helpers // helpers
import { TimezoneSelect } from "@/components/global/timezone-select";
import { captureError, captureSuccess } from "@/helpers/event-tracker.helper"; import { captureError, captureSuccess } from "@/helpers/event-tracker.helper";
import { useWorkspace } from "@/hooks/store/use-workspace"; import { useWorkspace } from "@/hooks/store/use-workspace";
import { useUserPermissions } from "@/hooks/store/user"; import { useUserPermissions } from "@/hooks/store/user";
@ -30,6 +31,7 @@ const defaultValues: Partial<IWorkspace> = {
url: "", url: "",
organization_size: "2-10", organization_size: "2-10",
logo_url: null, logo_url: null,
timezone: "UTC",
}; };
export const WorkspaceDetails = observer(function WorkspaceDetails() { export const WorkspaceDetails = observer(function WorkspaceDetails() {
@ -62,64 +64,69 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
const payload: Partial<IWorkspace> = { const payload: Partial<IWorkspace> = {
name: formData.name, name: formData.name,
organization_size: formData.organization_size, organization_size: formData.organization_size,
timezone: formData.timezone,
}; };
await updateWorkspace(currentWorkspace.slug, payload) try {
.then(() => { await updateWorkspace(currentWorkspace.slug, payload);
captureSuccess({ captureSuccess({
eventName: WORKSPACE_TRACKER_EVENTS.update, eventName: WORKSPACE_TRACKER_EVENTS.update,
payload: { slug: currentWorkspace.slug }, payload: { slug: currentWorkspace.slug },
});
setToast({
title: "Success!",
type: TOAST_TYPE.SUCCESS,
message: "Workspace updated successfully",
});
})
.catch((err) => {
captureError({
eventName: WORKSPACE_TRACKER_EVENTS.update,
payload: { slug: currentWorkspace.slug },
error: err,
});
console.error(err);
}); });
setTimeout(() => { setToast({
setIsLoading(false); title: "Success!",
}, 300); type: TOAST_TYPE.SUCCESS,
message: "Workspace updated successfully",
});
} catch (err: unknown) {
captureError({
eventName: WORKSPACE_TRACKER_EVENTS.update,
payload: { slug: currentWorkspace.slug },
error: err instanceof Error ? err : new Error(String(err)),
});
console.error(err);
} finally {
setTimeout(() => {
setIsLoading(false);
}, 300);
}
}; };
const handleRemoveLogo = async () => { const handleRemoveLogo = async () => {
if (!currentWorkspace) return; if (!currentWorkspace) return;
await updateWorkspace(currentWorkspace.slug, { try {
logo_url: "", await updateWorkspace(currentWorkspace.slug, {
}) logo_url: "",
.then(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Workspace picture removed successfully.",
});
})
.catch(() => {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "There was some error in deleting your profile picture. Please try again.",
});
}); });
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Workspace picture removed successfully.",
});
} catch {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "There was some error in deleting your profile picture. Please try again.",
});
}
}; };
const handleCopyUrl = () => { const handleCopyUrl = () => {
if (!currentWorkspace) return; if (!currentWorkspace) return;
copyUrlToClipboard(`${currentWorkspace.slug}`).then(() => { void copyUrlToClipboard(`${currentWorkspace.slug}`)
setToast({ .then(() => {
type: TOAST_TYPE.SUCCESS, setToast({
title: "Workspace URL copied to the clipboard.", type: TOAST_TYPE.SUCCESS,
title: "Workspace URL copied to the clipboard.",
});
return undefined;
})
.catch(() => {
// Silently handle clipboard errors
}); });
});
}; };
useEffect(() => { useEffect(() => {
@ -264,12 +271,30 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
onChange={onChange} onChange={onChange}
ref={ref} ref={ref}
hasError={Boolean(errors.url)} hasError={Boolean(errors.url)}
className="w-full" className="w-full cursor-not-allowed rounded-md !bg-custom-background-90"
disabled disabled
/> />
)} )}
/> />
</div> </div>
<div className="flex flex-col gap-1 ">
<h4 className="text-sm">{t("workspace_settings.settings.general.workspace_timezone")}</h4>
<Controller
name="timezone"
control={control}
render={({ field: { value, onChange } }) => (
<>
<TimezoneSelect
value={value}
onChange={onChange}
buttonClassName="border-none"
disabled={!isAdmin}
/>
</>
)}
/>
</div>
</div> </div>
{isAdmin && ( {isAdmin && (
@ -277,7 +302,9 @@ export const WorkspaceDetails = observer(function WorkspaceDetails() {
<Button <Button
data-ph-element={WORKSPACE_TRACKER_ELEMENTS.UPDATE_WORKSPACE_BUTTON} data-ph-element={WORKSPACE_TRACKER_ELEMENTS.UPDATE_WORKSPACE_BUTTON}
variant="primary" variant="primary"
onClick={handleSubmit(onSubmit)} onClick={(e) => {
void handleSubmit(onSubmit)(e);
}}
loading={isLoading} loading={isLoading}
> >
{isLoading ? t("updating") : t("workspace_settings.settings.general.update_workspace")} {isLoading ? t("updating") : t("workspace_settings.settings.general.update_workspace")}

View file

@ -1578,6 +1578,7 @@ export default {
name: "Název pracovního prostoru", name: "Název pracovního prostoru",
company_size: "Velikost společnosti", company_size: "Velikost společnosti",
url: "URL pracovního prostoru", url: "URL pracovního prostoru",
workspace_timezone: "Časové pásmo pracovního prostoru",
update_workspace: "Aktualizovat prostor", update_workspace: "Aktualizovat prostor",
delete_workspace: "Smazat tento prostor", delete_workspace: "Smazat tento prostor",
delete_workspace_description: "Smazáním prostoru odstraníte všechna data a zdroje. Akce je nevratná.", delete_workspace_description: "Smazáním prostoru odstraníte všechna data a zdroje. Akce je nevratná.",

View file

@ -1596,6 +1596,7 @@ export default {
name: "Name des Arbeitsbereichs", name: "Name des Arbeitsbereichs",
company_size: "Unternehmensgröße", company_size: "Unternehmensgröße",
url: "URL des Arbeitsbereichs", url: "URL des Arbeitsbereichs",
workspace_timezone: "Zeitzone des Arbeitsbereichs",
update_workspace: "Arbeitsbereich aktualisieren", update_workspace: "Arbeitsbereich aktualisieren",
delete_workspace: "Diesen Arbeitsbereich löschen", delete_workspace: "Diesen Arbeitsbereich löschen",
delete_workspace_description: delete_workspace_description:

View file

@ -1431,6 +1431,7 @@ export default {
name: "Workspace name", name: "Workspace name",
company_size: "Company size", company_size: "Company size",
url: "Workspace URL", url: "Workspace URL",
workspace_timezone: "Workspace Timezone",
update_workspace: "Update workspace", update_workspace: "Update workspace",
delete_workspace: "Delete this workspace", delete_workspace: "Delete this workspace",
delete_workspace_description: delete_workspace_description:

View file

@ -1600,6 +1600,7 @@ export default {
name: "Nombre del espacio de trabajo", name: "Nombre del espacio de trabajo",
company_size: "Tamaño de la empresa", company_size: "Tamaño de la empresa",
url: "URL del espacio de trabajo", url: "URL del espacio de trabajo",
workspace_timezone: "Zona horaria del espacio de trabajo",
update_workspace: "Actualizar espacio de trabajo", update_workspace: "Actualizar espacio de trabajo",
delete_workspace: "Eliminar este espacio de trabajo", delete_workspace: "Eliminar este espacio de trabajo",
delete_workspace_description: delete_workspace_description:

View file

@ -1598,6 +1598,7 @@ export default {
name: "Nom de lespace de travail", name: "Nom de lespace de travail",
company_size: "Taille de lentreprise", company_size: "Taille de lentreprise",
url: "URL de lespace de travail", url: "URL de lespace de travail",
workspace_timezone: "Fuseau horaire de lespace de travail",
update_workspace: "Mettre à jour lespace de travail", update_workspace: "Mettre à jour lespace de travail",
delete_workspace: "Supprimer cet espace de travail", delete_workspace: "Supprimer cet espace de travail",
delete_workspace_description: delete_workspace_description:

View file

@ -1586,6 +1586,7 @@ export default {
name: "Nama ruang kerja", name: "Nama ruang kerja",
company_size: "Ukuran perusahaan", company_size: "Ukuran perusahaan",
url: "URL ruang kerja", url: "URL ruang kerja",
workspace_timezone: "Zona waktu ruang kerja",
update_workspace: "Perbarui ruang kerja", update_workspace: "Perbarui ruang kerja",
delete_workspace: "Hapus ruang kerja ini", delete_workspace: "Hapus ruang kerja ini",
delete_workspace_description: delete_workspace_description:

View file

@ -1590,6 +1590,7 @@ export default {
name: "Nome dello spazio di lavoro", name: "Nome dello spazio di lavoro",
company_size: "Dimensione aziendale", company_size: "Dimensione aziendale",
url: "URL dello spazio di lavoro", url: "URL dello spazio di lavoro",
workspace_timezone: "Fuso orario dello spazio di lavoro",
update_workspace: "Aggiorna spazio di lavoro", update_workspace: "Aggiorna spazio di lavoro",
delete_workspace: "Elimina questo spazio di lavoro", delete_workspace: "Elimina questo spazio di lavoro",
delete_workspace_description: delete_workspace_description:

View file

@ -1577,6 +1577,7 @@ export default {
name: "ワークスペース名", name: "ワークスペース名",
company_size: "会社の規模", company_size: "会社の規模",
url: "ワークスペースURL", url: "ワークスペースURL",
workspace_timezone: "ワークスペースのタイムゾーン",
update_workspace: "ワークスペースを更新", update_workspace: "ワークスペースを更新",
delete_workspace: "このワークスペースを削除", delete_workspace: "このワークスペースを削除",
delete_workspace_description: delete_workspace_description:

View file

@ -1570,6 +1570,7 @@ export default {
name: "작업 공간 이름", name: "작업 공간 이름",
company_size: "회사 규모", company_size: "회사 규모",
url: "작업 공간 URL", url: "작업 공간 URL",
workspace_timezone: "작업 공간 시간대",
update_workspace: "작업 공간 업데이트", update_workspace: "작업 공간 업데이트",
delete_workspace: "이 작업 공간 삭제", delete_workspace: "이 작업 공간 삭제",
delete_workspace_description: delete_workspace_description:

View file

@ -1581,6 +1581,7 @@ export default {
name: "Nazwa przestrzeni roboczej", name: "Nazwa przestrzeni roboczej",
company_size: "Rozmiar firmy", company_size: "Rozmiar firmy",
url: "URL przestrzeni roboczej", url: "URL przestrzeni roboczej",
workspace_timezone: "Strefa czasowa przestrzeni roboczej",
update_workspace: "Zaktualizuj przestrzeń", update_workspace: "Zaktualizuj przestrzeń",
delete_workspace: "Usuń tę przestrzeń", delete_workspace: "Usuń tę przestrzeń",
delete_workspace_description: delete_workspace_description:

View file

@ -1598,6 +1598,7 @@ export default {
name: "Nome do espaço de trabalho", name: "Nome do espaço de trabalho",
company_size: "Tamanho da empresa", company_size: "Tamanho da empresa",
url: "URL do espaço de trabalho", url: "URL do espaço de trabalho",
workspace_timezone: "Fuso horário do espaço de trabalho",
update_workspace: "Atualizar espaço de trabalho", update_workspace: "Atualizar espaço de trabalho",
delete_workspace: "Excluir este espaço de trabalho", delete_workspace: "Excluir este espaço de trabalho",
delete_workspace_description: delete_workspace_description:

View file

@ -1590,6 +1590,7 @@ export default {
name: "Numele spațiului de lucru", name: "Numele spațiului de lucru",
company_size: "Dimensiunea companiei", company_size: "Dimensiunea companiei",
url: "URL-ul spațiului de lucru", url: "URL-ul spațiului de lucru",
workspace_timezone: "Fusul orar al spațiului de lucru",
update_workspace: "Actualizează spațiul de lucru", update_workspace: "Actualizează spațiul de lucru",
delete_workspace: "Șterge acest spațiu de lucru", delete_workspace: "Șterge acest spațiu de lucru",
delete_workspace_description: delete_workspace_description:

View file

@ -1583,6 +1583,7 @@ export default {
name: "Название пространства", name: "Название пространства",
company_size: "Размер компании", company_size: "Размер компании",
url: "URL пространства", url: "URL пространства",
workspace_timezone: "Часовой пояс рабочего пространства",
update_workspace: "Обновить пространство", update_workspace: "Обновить пространство",
delete_workspace: "Удалить пространство", delete_workspace: "Удалить пространство",
delete_workspace_description: "Все данные будут безвозвратно удалены.", delete_workspace_description: "Все данные будут безвозвратно удалены.",

View file

@ -1581,6 +1581,7 @@ export default {
name: "Názov pracovného priestoru", name: "Názov pracovného priestoru",
company_size: "Veľkosť spoločnosti", company_size: "Veľkosť spoločnosti",
url: "URL pracovného priestoru", url: "URL pracovného priestoru",
workspace_timezone: "Časové pásmo pracovného priestoru",
update_workspace: "Aktualizovať priestor", update_workspace: "Aktualizovať priestor",
delete_workspace: "Zmazať tento priestor", delete_workspace: "Zmazať tento priestor",
delete_workspace_description: "Zmazaním priestoru odstránite všetky dáta a zdroje. Akcia je nevratná.", delete_workspace_description: "Zmazaním priestoru odstránite všetky dáta a zdroje. Akcia je nevratná.",

View file

@ -1586,6 +1586,7 @@ export default {
name: "Çalışma Alanı Adı", name: "Çalışma Alanı Adı",
company_size: "Şirket Büyüklüğü", company_size: "Şirket Büyüklüğü",
url: "Çalışma Alanı URL'si", url: "Çalışma Alanı URL'si",
workspace_timezone: "Çalışma Alanı Saat Dilimi",
update_workspace: "Çalışma Alanını Güncelle", update_workspace: "Çalışma Alanını Güncelle",
delete_workspace: "Bu çalışma alanını sil", delete_workspace: "Bu çalışma alanını sil",
delete_workspace_description: delete_workspace_description:

View file

@ -1585,6 +1585,7 @@ export default {
name: "Назва робочого простору", name: "Назва робочого простору",
company_size: "Розмір компанії", company_size: "Розмір компанії",
url: "URL робочого простору", url: "URL робочого простору",
workspace_timezone: "Часовий пояс робочого простору",
update_workspace: "Оновити простір", update_workspace: "Оновити простір",
delete_workspace: "Видалити цей простір", delete_workspace: "Видалити цей простір",
delete_workspace_description: "Видалення простору призведе до втрати всіх даних і ресурсів. Дія незворотна.", delete_workspace_description: "Видалення простору призведе до втрати всіх даних і ресурсів. Дія незворотна.",

View file

@ -1587,6 +1587,7 @@ export default {
name: "Tên không gian làm việc", name: "Tên không gian làm việc",
company_size: "Quy mô công ty", company_size: "Quy mô công ty",
url: "URL không gian làm việc", url: "URL không gian làm việc",
workspace_timezone: "Múi giờ không gian làm việc",
update_workspace: "Cập nhật không gian làm việc", update_workspace: "Cập nhật không gian làm việc",
delete_workspace: "Xóa không gian làm việc này", delete_workspace: "Xóa không gian làm việc này",
delete_workspace_description: delete_workspace_description:

View file

@ -1560,6 +1560,7 @@ export default {
name: "工作区名称", name: "工作区名称",
company_size: "公司规模", company_size: "公司规模",
url: "工作区网址", url: "工作区网址",
workspace_timezone: "工作区时区",
update_workspace: "更新工作区", update_workspace: "更新工作区",
delete_workspace: "删除此工作区", delete_workspace: "删除此工作区",
delete_workspace_description: "删除工作区时,该工作区内的所有数据和资源将被永久删除,且无法恢复。", delete_workspace_description: "删除工作区时,该工作区内的所有数据和资源将被永久删除,且无法恢复。",

View file

@ -1561,6 +1561,7 @@ export default {
name: "工作區名稱", name: "工作區名稱",
company_size: "公司規模", company_size: "公司規模",
url: "工作區網址", url: "工作區網址",
workspace_timezone: "工作區時區",
update_workspace: "更新工作區", update_workspace: "更新工作區",
delete_workspace: "刪除此工作區", delete_workspace: "刪除此工作區",
delete_workspace_description: "刪除工作區時,該工作區內的所有資料和資源都將被永久移除且無法復原。", delete_workspace_description: "刪除工作區時,該工作區內的所有資料和資源都將被永久移除且無法復原。",

View file

@ -27,6 +27,7 @@ export interface IWorkspace {
organization_size: string; organization_size: string;
total_projects?: number; total_projects?: number;
role: number; role: number;
timezone: string;
} }
export interface IWorkspaceLite { export interface IWorkspaceLite {
@ -228,7 +229,7 @@ export interface IWorkspaceProgressResponse {
unstarted_issues: number; unstarted_issues: number;
} }
export interface IWorkspaceAnalyticsResponse { export interface IWorkspaceAnalyticsResponse {
completion_chart: any; completion_chart: Record<string, unknown>;
} }
export type TWorkspacePaginationInfo = TPaginationInfo & { export type TWorkspacePaginationInfo = TPaginationInfo & {