Compare commits

...

12 Commits

Author SHA1 Message Date
Saurabhkmr98
fe62709724 Merge branch 'preview' into feat/inbox-settings and translations 2025-04-09 20:05:51 +05:30
Saurabhkmr98
05c7dc51db notification task code refactor + copy changes for UI 2025-02-27 16:34:24 +05:30
Saurabhkmr98
75916ce1e2 fixes for null state 2025-02-25 19:39:11 +05:30
Saurabhkmr98
a2717ae43b remove spaces 2025-02-25 16:26:25 +05:30
Saurabhkmr98
5590ccc572 conserve old notification models for revert logic 2025-02-25 16:23:36 +05:30
Saurabhkmr98
6cd1a33577 replace flex with grid layout for setting UI customisation 2025-02-25 13:28:49 +05:30
Saurabhkmr98
77253c97a6 remove console logs 2025-02-24 19:34:58 +05:30
Saurabhkmr98
b22f5f8820 notification task updates for user preference model changes 2025-02-24 17:11:01 +05:30
Saurabhkmr98
561816027f remove console logs 2025-02-19 16:15:46 +05:30
Saurabhkmr98
266de8c55f integrated API with UI 2025-02-19 13:52:41 +05:30
Saurabhkmr98
6b5d007241 fix: computed functions 2025-02-18 20:45:55 +05:30
Saurabhkmr98
831c93677f feat: inbox settings backend apis + web UI and store changes 2025-02-18 20:42:18 +05:30
50 changed files with 2131 additions and 530 deletions

View File

@@ -115,7 +115,11 @@ from .intake import (
from .analytic import AnalyticViewSerializer
from .notification import NotificationSerializer, UserNotificationPreferenceSerializer
from .notification import (
NotificationSerializer,
UserNotificationPreferenceSerializer,
WorkspaceUserNotificationPreferenceSerializer
)
from .exporter import ExporterHistorySerializer

View File

@@ -1,7 +1,7 @@
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
from plane.db.models import Notification, UserNotificationPreference
from plane.db.models import Notification, UserNotificationPreference, WorkspaceUserNotificationPreference
# Third Party imports
from rest_framework import serializers
@@ -22,3 +22,8 @@ class UserNotificationPreferenceSerializer(BaseSerializer):
class Meta:
model = UserNotificationPreference
fields = "__all__"
class WorkspaceUserNotificationPreferenceSerializer(BaseSerializer):
class Meta:
model = WorkspaceUserNotificationPreference
fields = "__all__"

View File

@@ -6,6 +6,7 @@ from plane.app.views import (
UnreadNotificationEndpoint,
MarkAllReadNotificationViewSet,
UserNotificationPreferenceEndpoint,
WorkspaceUserNotificationPreferenceEndpoint,
)
@@ -47,4 +48,14 @@ urlpatterns = [
UserNotificationPreferenceEndpoint.as_view(),
name="user-notification-preferences",
),
path(
"workspaces/<str:slug>/user-notification-preferences/<str:transport>/",
WorkspaceUserNotificationPreferenceEndpoint.as_view(),
name="workspace-user-notification-preference",
),
path(
"workspaces/<str:slug>/user-notification-preferences/",
WorkspaceUserNotificationPreferenceEndpoint.as_view(),
name="workspace-user-notification-preference",
),
]

View File

@@ -203,6 +203,7 @@ from .notification.base import (
NotificationViewSet,
UnreadNotificationEndpoint,
UserNotificationPreferenceEndpoint,
WorkspaceUserNotificationPreferenceEndpoint,
)
from .exporter.base import ExportIssuesEndpoint

View File

@@ -9,6 +9,7 @@ from rest_framework.response import Response
from plane.app.serializers import (
NotificationSerializer,
UserNotificationPreferenceSerializer,
WorkspaceUserNotificationPreferenceSerializer
)
from plane.db.models import (
Issue,
@@ -17,6 +18,9 @@ from plane.db.models import (
Notification,
UserNotificationPreference,
WorkspaceMember,
Workspace,
WorkspaceUserNotificationPreference,
NotificationTransportChoices
)
from plane.utils.paginator import BasePaginator
from plane.app.permissions import allow_permission, ROLE
@@ -360,3 +364,81 @@ class UserNotificationPreferenceEndpoint(BaseAPIView):
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class WorkspaceUserNotificationPreferenceEndpoint(BaseAPIView):
model = WorkspaceUserNotificationPreference
serializer_class = WorkspaceUserNotificationPreferenceSerializer
@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
)
def get(self, request, slug):
workspace = Workspace.objects.get(slug=slug)
get_notification_preferences = (
WorkspaceUserNotificationPreference.objects.filter(
workspace=workspace, user=request.user
)
)
create_transports = []
transports = [
transport
for transport, _ in NotificationTransportChoices.choices
]
for transport in transports:
if transport not in get_notification_preferences.values_list(
"transport", flat=True
):
create_transports.append(transport)
notification_preferences = (
WorkspaceUserNotificationPreference.objects.bulk_create(
[
WorkspaceUserNotificationPreference(
workspace=workspace,
user=request.user,
transport=transport,
)
for transport in create_transports
]
)
)
notification_preferences = WorkspaceUserNotificationPreference.objects.filter(
workspace=workspace, user=request.user
)
return Response(
WorkspaceUserNotificationPreferenceSerializer(
notification_preferences, many=True
).data,
status=status.HTTP_200_OK,
)
@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
)
def patch(self, request, slug, transport):
notification_preference = WorkspaceUserNotificationPreference.objects.filter(
transport=transport, workspace__slug=slug, user=request.user
).first()
if notification_preference:
serializer = WorkspaceUserNotificationPreferenceSerializer(
notification_preference, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(
{"detail": "Workspace notification preference not found"},
status=status.HTTP_404_NOT_FOUND,
)

View File

@@ -2,7 +2,7 @@
from ..base import BaseAPIView
from plane.db.models.workspace import WorkspaceHomePreference
from plane.app.permissions import allow_permission, ROLE
from plane.db.models import Workspace
from plane.db.models import Workspace, WorkspaceUserNotificationPreference, NotificationTransportChoices
from plane.app.serializers.workspace import WorkspaceHomePreferenceSerializer
# Third party imports
@@ -59,6 +59,37 @@ class WorkspaceHomePreferenceViewSet(BaseAPIView):
user=request.user, workspace_id=workspace.id
)
# Notification preference get or create
workspace = Workspace.objects.get(slug=slug)
get_notification_preferences = (
WorkspaceUserNotificationPreference.objects.filter(
workspace=workspace, user=request.user
)
)
create_transports = []
transports = [
transport
for transport, _ in NotificationTransportChoices.choices
]
for transport in transports:
if transport not in get_notification_preferences.values_list(
"transport", flat=True
):
create_transports.append(transport)
_ = WorkspaceUserNotificationPreference.objects.bulk_create(
[
WorkspaceUserNotificationPreference(
workspace=workspace, user=request.user, transport=transport
)
for transport in create_transports
]
)
return Response(
preference.values("key", "is_enabled", "config", "sort_order"),
status=status.HTTP_200_OK,

File diff suppressed because it is too large Load Diff

View File

@@ -44,7 +44,13 @@ from .issue import (
IssueDescriptionVersion,
)
from .module import Module, ModuleIssue, ModuleLink, ModuleMember, ModuleUserProperties
from .notification import EmailNotificationLog, Notification, UserNotificationPreference
from .notification import (
EmailNotificationLog,
Notification,
UserNotificationPreference,
NotificationTransportChoices,
WorkspaceUserNotificationPreference
)
from .page import Page, PageLabel, PageLog, ProjectPage, PageVersion
from .project import (
Project,

View File

@@ -93,7 +93,6 @@ class UserNotificationPreference(BaseModel):
"""Return the user"""
return f"<{self.user}>"
class EmailNotificationLog(BaseModel):
# receiver
receiver = models.ForeignKey(
@@ -123,3 +122,59 @@ class EmailNotificationLog(BaseModel):
verbose_name_plural = "Email Notification Logs"
db_table = "email_notification_logs"
ordering = ("-created_at",)
class WorkspaceUserNotificationPreference(BaseModel):
# user it is related to
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="user_workspace_notification_preferences",
)
# workspace if it is applicable
workspace = models.ForeignKey(
"db.Workspace",
on_delete=models.CASCADE,
related_name="workspace_user_notification_preferences",
)
# project
project = models.ForeignKey(
"db.Project",
on_delete=models.CASCADE,
related_name="project_user_notification_preferences",
null=True,
)
transport = models.CharField(max_length=50, default="EMAIL")
# task updates
property_change = models.BooleanField(default=False)
state_change = models.BooleanField(default=False)
priority = models.BooleanField(default=False)
assignee = models.BooleanField(default=False)
start_due_date = models.BooleanField(default=False)
# comments fields
comment = models.BooleanField(default=False)
mention = models.BooleanField(default=False)
comment_reactions = models.BooleanField(default=False)
class Meta:
unique_together = ["workspace", "user", "transport", "deleted_at"]
constraints = [
models.UniqueConstraint(
fields=["workspace", "user", "transport"],
condition=models.Q(deleted_at__isnull=True),
name="notification_preferences_unique_workspace_user_transport_when_deleted_at_null",
)
]
verbose_name = "Workspace User Notification Preference"
verbose_name_plural = "Workspace User Notification Preferences"
db_table = "workspace_user_notification_preferences"
ordering = ("-created_at",)
def __str__(self):
"""Return the user"""
return f"<{self.user}>"
class NotificationTransportChoices(models.TextChoices):
EMAIL = "EMAIL", "Email"
IN_APP = "IN_APP", "In App"

View File

@@ -1,4 +1,4 @@
import { TUnreadNotificationsCount } from "@plane/types";
import { TUnreadNotificationsCount, TNotificationSettings } from "@plane/types";
export enum ENotificationTab {
ALL = "all",
@@ -135,3 +135,71 @@ export const allTimeIn30MinutesInterval12HoursFormat: Array<{
{ label: "11:00", value: "11:00" },
{ label: "11:30", value: "11:30" },
];
export enum ENotificationSettingsKey {
PROPERTY_CHANGE = "property_change",
STATE_CHANGE = "state_change",
PRIORITY = "priority",
ASSIGNEE = "assignee",
START_DUE_DATE = "start_due_date",
COMMENTS = "comment",
MENTIONED_COMMENTS = "mention",
COMMENT_REACTIONS = "comment_reactions",
}
export enum EWorkspaceNotificationTransport {
EMAIL = "EMAIL",
IN_APP = "IN_APP",
}
export const TASK_UPDATES_NOTIFICATION_SETTINGS: TNotificationSettings[] = [
{
key: ENotificationSettingsKey.PROPERTY_CHANGE,
i18n_title: "notification_settings.work_item_property_title",
i18n_subtitle: "notification_settings.work_item_property_subtitle",
},
{
key: ENotificationSettingsKey.STATE_CHANGE,
i18n_title: "notification_settings.status_title",
i18n_subtitle: "notification_settings.status_subtitle",
},
{
key: ENotificationSettingsKey.PRIORITY,
i18n_title: "notification_settings.priority_title",
i18n_subtitle: "notification_settings.priority_subtitle",
},
{
key: ENotificationSettingsKey.ASSIGNEE,
i18n_title: "notification_settings.assignee_title",
i18n_subtitle: "notification_settings.assignee_subtitle",
},
{
key: ENotificationSettingsKey.START_DUE_DATE,
i18n_title: "notification_settings.due_date_title",
i18n_subtitle: "notification_settings.due_date_subtitle",
}
]
export const COMMENT_NOTIFICATION_SETTINGS: TNotificationSettings[] = [
{
key: ENotificationSettingsKey.MENTIONED_COMMENTS,
i18n_title: "notification_settings.mentioned_comments_title",
i18n_subtitle: "notification_settings.mentioned_comments_subtitle",
},
{
key: ENotificationSettingsKey.COMMENTS,
i18n_title: "notification_settings.new_comments_title",
i18n_subtitle: "notification_settings.new_comments_subtitle",
},
{
key: ENotificationSettingsKey.COMMENT_REACTIONS,
i18n_title: "notification_settings.reaction_comments_title",
i18n_subtitle: "notification_settings.reaction_comments_subtitle",
},
]
export const NOTIFICATION_SETTINGS: TNotificationSettings[] = [
...TASK_UPDATES_NOTIFICATION_SETTINGS,
...COMMENT_NOTIFICATION_SETTINGS
]

View File

@@ -2435,5 +2435,39 @@
"last_edited_by": "Naposledy upraveno uživatelem",
"previously_edited_by": "Dříve upraveno uživatelem",
"edited_by": "Upraveno uživatelem"
},
"notification_settings": {
"page_label": "{workspace} - Nastavení doručené pošty",
"inbox_settings": "Nastavení doručené pošty",
"inbox_settings_description": "Přizpůsobte si, jak dostáváte oznámení o aktivitách ve vašem pracovním prostoru. Vaše změny jsou automaticky uloženy.",
"advanced_settings": "Pokročilá nastavení",
"in_plane": "V Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Aktualizace pracovních položek",
"task_updates_subtitle": "Dostávejte oznámení, když jsou aktualizovány pracovní položky ve vašem pracovním prostoru.",
"comments": "Komentáře",
"comments_subtitle": "Buďte informováni o diskusích ve vašem pracovním prostoru.",
"work_item_property_title": "Aktualizace jakékoli vlastnosti pracovního položky",
"work_item_property_subtitle": "Dostávejte oznámení, když jsou aktualizovány pracovní položky ve vašem pracovním prostoru.",
"status_title": "Změna stavu",
"status_subtitle": "Když je aktualizován stav pracovního položky.",
"priority_title": "Změna priority",
"priority_subtitle": "Když je upravena priorita pracovního položky.",
"assignee_title": "Změna přiřazení",
"assignee_subtitle": "Když je pracovní položka přiřazena nebo přeřazena někomu.",
"due_date_title": "Změna data",
"due_date_subtitle": "Když je aktualizováno datum zahájení nebo splatnosti pracovního položky.",
"module_title": "Aktualizace modulu",
"cycle_title": "Aktualizace cyklu",
"mentioned_comments_title": "Zmínky",
"mentioned_comments_subtitle": "Když vás někdo zmíní v komentáři.",
"new_comments_title": "Nové komentáře",
"new_comments_subtitle": "Když je přidán nový komentář k úkolu, který sledujete.",
"reaction_comments_title": "Reakce",
"reaction_comments_subtitle": "Dostávejte oznámení, když někdo reaguje na vaše komentáře nebo úkoly pomocí emoji.",
"setting_updated_successfully": "Nastavení bylo úspěšně aktualizováno",
"failed_to_update_setting": "Nepodařilo se aktualizovat nastavení"
}
}

View File

@@ -2393,5 +2393,39 @@
"last_edited_by": "Zuletzt bearbeitet von",
"previously_edited_by": "Zuvor bearbeitet von",
"edited_by": "Bearbeitet von"
},
"notification_settings": {
"page_label": "{workspace} - Posteingangseinstellungen",
"inbox_settings": "Posteingangseinstellungen",
"inbox_settings_description": "Passen Sie an, wie Sie Benachrichtigungen für Aktivitäten in Ihrem Arbeitsbereich erhalten. Ihre Änderungen werden automatisch gespeichert.",
"advanced_settings": "Erweiterte Einstellungen",
"in_plane": "In Plane",
"email": "E-Mail",
"slack": "Slack",
"task_updates": "Aktualisierungen von Arbeitselementen",
"task_updates_subtitle": "Erhalten Sie Benachrichtigungen, wenn Arbeitselemente in Ihrem Arbeitsbereich aktualisiert werden.",
"comments": "Kommentare",
"comments_subtitle": "Bleiben Sie über Diskussionen in Ihrem Arbeitsbereich auf dem Laufenden.",
"work_item_property_title": "Aktualisierung einer Eigenschaft des Arbeitselements",
"work_item_property_subtitle": "Erhalten Sie Benachrichtigungen, wenn Arbeitselemente in Ihrem Arbeitsbereich aktualisiert werden.",
"status_title": "Statusänderung",
"status_subtitle": "Wenn der Status eines Arbeitselements aktualisiert wird.",
"priority_title": "Prioritätsänderung",
"priority_subtitle": "Wenn das Prioritätsniveau eines Arbeitselements angepasst wird.",
"assignee_title": "Zuweisungsänderung",
"assignee_subtitle": "Wenn ein Arbeitselement jemandem zugewiesen oder neu zugewiesen wird.",
"due_date_title": "Datumsänderung",
"due_date_subtitle": "Wenn das Start- oder Fälligkeitsdatum eines Arbeitselements aktualisiert wird.",
"module_title": "Modulaktualisierung",
"cycle_title": "Zyklusaktualisierung",
"mentioned_comments_title": "Erwähnungen",
"mentioned_comments_subtitle": "Wenn jemand Sie in einem Kommentar erwähnt.",
"new_comments_title": "Neue Kommentare",
"new_comments_subtitle": "Wenn ein neuer Kommentar zu einer Aufgabe hinzugefügt wird, die Sie verfolgen.",
"reaction_comments_title": "Reaktionen",
"reaction_comments_subtitle": "Erhalten Sie Benachrichtigungen, wenn jemand mit einem Emoji auf Ihre Kommentare oder Aufgaben reagiert.",
"setting_updated_successfully": "Einstellung erfolgreich aktualisiert",
"failed_to_update_setting": "Fehler beim Aktualisieren der Einstellung"
}
}

View File

@@ -2270,5 +2270,39 @@
"last_edited_by": "Last edited by",
"previously_edited_by": "Previously edited by",
"edited_by": "Edited by"
},
"notification_settings": {
"page_label": "{workspace} - Inbox settings",
"inbox_settings": "Inbox settings",
"inbox_settings_description": "Customize how you receive notifications for activities in your workspace. Your changes are saved automatically.",
"advanced_settings": "Advanced settings",
"in_plane": "In Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Work item updates",
"task_updates_subtitle": "Get notified when work items in your workspace are updated.",
"comments": "Comments",
"comments_subtitle": "Stay updated on discussions in your workspace.",
"work_item_property_title": "Update on any property of the work item",
"work_item_property_subtitle": "Get notified when work items in your workspace are updated.",
"status_title": "State change",
"status_subtitle": "When a work item's state is updated.",
"priority_title": "Priority change",
"priority_subtitle": "When a work item's priority level is adjusted.",
"assignee_title": "Assignee change",
"assignee_subtitle": "When a work item is assigned or reassigned to someone.",
"due_date_title": "Date change",
"due_date_subtitle": "When a work item's start or due date is updated.",
"module_title": "Module update",
"cycle_title": "Cycle update",
"mentioned_comments_title": "Mentions",
"mentioned_comments_subtitle": "When someone mentions you in a comment.",
"new_comments_title": "New comments",
"new_comments_subtitle": "When a new comment is added to a task youre following.",
"reaction_comments_title": "Reactions",
"reaction_comments_subtitle": "Get notified when someone reacts to your comments or tasks with an emoji.",
"setting_updated_successfully": "Setting updated successfully",
"failed_to_update_setting": "Failed to update setting"
}
}

View File

@@ -2439,5 +2439,39 @@
"last_edited_by": "Última edición por",
"previously_edited_by": "Editado anteriormente por",
"edited_by": "Editado por"
},
"notification_settings": {
"page_label": "{workspace} - Configuración de la bandeja de entrada",
"inbox_settings": "Configuración de la bandeja de entrada",
"inbox_settings_description": "Personaliza cómo recibes notificaciones de actividades en tu espacio de trabajo. Tus cambios se guardan automáticamente.",
"advanced_settings": "Configuración avanzada",
"in_plane": "En Plane",
"email": "Correo electrónico",
"slack": "Slack",
"task_updates": "Actualizaciones de elementos de trabajo",
"task_updates_subtitle": "Recibe notificaciones cuando se actualicen los elementos de trabajo en tu espacio de trabajo.",
"comments": "Comentarios",
"comments_subtitle": "Mantente actualizado sobre las discusiones en tu espacio de trabajo.",
"work_item_property_title": "Actualización de cualquier propiedad del elemento de trabajo",
"work_item_property_subtitle": "Recibe notificaciones cuando se actualicen los elementos de trabajo en tu espacio de trabajo.",
"status_title": "Cambio de estado",
"status_subtitle": "Cuando se actualiza el estado de un elemento de trabajo.",
"priority_title": "Cambio de prioridad",
"priority_subtitle": "Cuando se ajusta el nivel de prioridad de un elemento de trabajo.",
"assignee_title": "Cambio de asignado",
"assignee_subtitle": "Cuando un elemento de trabajo se asigna o reasigna a alguien.",
"due_date_title": "Cambio de fecha",
"due_date_subtitle": "Cuando se actualiza la fecha de inicio o vencimiento de un elemento de trabajo.",
"module_title": "Actualización del módulo",
"cycle_title": "Actualización del ciclo",
"mentioned_comments_title": "Menciones",
"mentioned_comments_subtitle": "Cuando alguien te menciona en un comentario.",
"new_comments_title": "Nuevos comentarios",
"new_comments_subtitle": "Cuando se agrega un nuevo comentario a una tarea que sigues.",
"reaction_comments_title": "Reacciones",
"reaction_comments_subtitle": "Recibe notificaciones cuando alguien reacciona a tus comentarios o tareas con un emoji.",
"setting_updated_successfully": "Configuración actualizada con éxito",
"failed_to_update_setting": "Error al actualizar la configuración"
}
}

View File

@@ -2437,5 +2437,40 @@
"last_edited_by": "Dernière modification par",
"previously_edited_by": "Précédemment modifié par",
"edited_by": "Modifié par"
},
"notification_settings": {
"page_label": "{workspace} - Paramètres de la boîte de réception",
"inbox_settings": "Paramètres de la boîte de réception",
"inbox_settings_description": "Personnalisez la façon dont vous recevez les notifications pour les activités dans votre espace de travail. Vos modifications sont enregistrées automatiquement.",
"advanced_settings": "Paramètres avancés",
"in_plane": "Dans Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Mises à jour des éléments de travail",
"task_updates_subtitle": "Recevez une notification lorsque les éléments de travail dans votre espace de travail sont mis à jour.",
"comments": "Commentaires",
"comments_subtitle": "Restez informé des discussions dans votre espace de travail.",
"work_item_property_title": "Mise à jour de toute propriété de l'élément de travail",
"work_item_property_subtitle": "Recevez une notification lorsque les éléments de travail dans votre espace de travail sont mis à jour.",
"status_title": "Changement d'état",
"status_subtitle": "Lorsqu'un élément de travail change d'état.",
"priority_title": "Changement de priorité",
"priority_subtitle": "Lorsqu'un niveau de priorité d'un élément de travail est ajusté.",
"assignee_title": "Changement de responsable",
"assignee_subtitle": "Lorsqu'un élément de travail est assigné ou réassigné à quelqu'un.",
"due_date_title": "Changement de date",
"due_date_subtitle": "Lorsqu'une date de début ou d'échéance d'un élément de travail est mise à jour.",
"module_title": "Mise à jour du module",
"cycle_title": "Mise à jour du cycle",
"mentioned_comments_title": "Mentions",
"mentioned_comments_subtitle": "Lorsqu'une personne vous mentionne dans un commentaire.",
"new_comments_title": "Nouveaux commentaires",
"new_comments_subtitle": "Lorsqu'un nouveau commentaire est ajouté à une tâche que vous suivez.",
"reaction_comments_title": "Réactions",
"reaction_comments_subtitle": "Recevez une notification lorsque quelqu'un réagit à vos commentaires ou tâches avec un emoji.",
"setting_updated_successfully": "Paramètre mis à jour avec succès",
"failed_to_update_setting": "Échec de la mise à jour du paramètre"
}
}

View File

@@ -2431,5 +2431,39 @@
"last_edited_by": "Terakhir disunting oleh",
"previously_edited_by": "Sebelumnya disunting oleh",
"edited_by": "Disunting oleh"
},
"notification_settings": {
"page_label": "{workspace} - Pengaturan kotak masuk",
"inbox_settings": "Pengaturan kotak masuk",
"inbox_settings_description": "Sesuaikan cara Anda menerima notifikasi untuk aktivitas di ruang kerja Anda. Perubahan Anda disimpan secara otomatis.",
"advanced_settings": "Pengaturan lanjutan",
"in_plane": "Di Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Pembaruan item kerja",
"task_updates_subtitle": "Dapatkan notifikasi ketika item kerja di ruang kerja Anda diperbarui.",
"comments": "Komentar",
"comments_subtitle": "Tetap terinformasi tentang diskusi di ruang kerja Anda.",
"work_item_property_title": "Pembaruan properti item kerja",
"work_item_property_subtitle": "Dapatkan notifikasi ketika item kerja di ruang kerja Anda diperbarui.",
"status_title": "Perubahan status",
"status_subtitle": "Ketika status item kerja diperbarui.",
"priority_title": "Perubahan prioritas",
"priority_subtitle": "Ketika tingkat prioritas item kerja disesuaikan.",
"assignee_title": "Perubahan penugasan",
"assignee_subtitle": "Ketika item kerja ditugaskan atau ditugaskan ulang kepada seseorang.",
"due_date_title": "Perubahan tanggal",
"due_date_subtitle": "Ketika tanggal mulai atau jatuh tempo item kerja diperbarui.",
"module_title": "Pembaruan modul",
"cycle_title": "Pembaruan siklus",
"mentioned_comments_title": "Penyebutan",
"mentioned_comments_subtitle": "Ketika seseorang menyebut Anda dalam komentar.",
"new_comments_title": "Komentar baru",
"new_comments_subtitle": "Ketika komentar baru ditambahkan ke tugas yang Anda ikuti.",
"reaction_comments_title": "Reaksi",
"reaction_comments_subtitle": "Dapatkan notifikasi ketika seseorang bereaksi terhadap komentar atau tugas Anda dengan emoji.",
"setting_updated_successfully": "Pengaturan berhasil diperbarui",
"failed_to_update_setting": "Gagal memperbarui pengaturan"
}
}

View File

@@ -2436,5 +2436,39 @@
"last_edited_by": "Ultima modifica di",
"previously_edited_by": "Precedentemente modificato da",
"edited_by": "Modificato da"
},
"notification_settings": {
"page_label": "{workspace} - Impostazioni della casella di posta",
"inbox_settings": "Impostazioni della casella di posta",
"inbox_settings_description": "Personalizza come ricevi le notifiche per le attività nel tuo spazio di lavoro. Le tue modifiche vengono salvate automaticamente.",
"advanced_settings": "Impostazioni avanzate",
"in_plane": "In Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Aggiornamenti degli elementi di lavoro",
"task_updates_subtitle": "Ricevi una notifica quando gli elementi di lavoro nel tuo spazio di lavoro vengono aggiornati.",
"comments": "Commenti",
"comments_subtitle": "Rimani aggiornato sulle discussioni nel tuo spazio di lavoro.",
"work_item_property_title": "Aggiornamento di qualsiasi proprietà dell'elemento di lavoro",
"work_item_property_subtitle": "Ricevi una notifica quando gli elementi di lavoro nel tuo spazio di lavoro vengono aggiornati.",
"status_title": "Cambio di stato",
"status_subtitle": "Quando lo stato di un elemento di lavoro viene aggiornato.",
"priority_title": "Cambio di priorità",
"priority_subtitle": "Quando il livello di priorità di un elemento di lavoro viene regolato.",
"assignee_title": "Cambio di assegnatario",
"assignee_subtitle": "Quando un elemento di lavoro viene assegnato o riassegnato a qualcuno.",
"due_date_title": "Cambio di data",
"due_date_subtitle": "Quando la data di inizio o di scadenza di un elemento di lavoro viene aggiornata.",
"module_title": "Aggiornamento del modulo",
"cycle_title": "Aggiornamento del ciclo",
"mentioned_comments_title": "Menzioni",
"mentioned_comments_subtitle": "Quando qualcuno ti menziona in un commento.",
"new_comments_title": "Nuovi commenti",
"new_comments_subtitle": "Quando viene aggiunto un nuovo commento a un'attività che stai seguendo.",
"reaction_comments_title": "Reazioni",
"reaction_comments_subtitle": "Ricevi una notifica quando qualcuno reagisce ai tuoi commenti o attività con un'emoji.",
"setting_updated_successfully": "Impostazione aggiornata con successo",
"failed_to_update_setting": "Aggiornamento dell'impostazione non riuscito"
}
}

View File

@@ -2437,5 +2437,39 @@
"last_edited_by": "最終編集者",
"previously_edited_by": "以前の編集者",
"edited_by": "編集者"
},
"notification_settings": {
"page_label": "{workspace} - 受信トレイ設定",
"inbox_settings": "受信トレイ設定",
"inbox_settings_description": "ワークスペース内のアクティビティに関する通知の受け取り方法をカスタマイズします。変更は自動的に保存されます。",
"advanced_settings": "詳細設定",
"in_plane": "Plane内",
"email": "メール",
"slack": "Slack",
"task_updates": "作業項目の更新",
"task_updates_subtitle": "ワークスペース内の作業項目が更新されたときに通知を受け取ります。",
"comments": "コメント",
"comments_subtitle": "ワークスペース内のディスカッションを最新の状態に保ちます。",
"work_item_property_title": "作業項目のプロパティの更新",
"work_item_property_subtitle": "ワークスペース内の作業項目が更新されたときに通知を受け取ります。",
"status_title": "状態の変更",
"status_subtitle": "作業項目の状態が更新されたとき。",
"priority_title": "優先度の変更",
"priority_subtitle": "作業項目の優先度レベルが調整されたとき。",
"assignee_title": "担当者の変更",
"assignee_subtitle": "作業項目が誰かに割り当てられたり再割り当てされたとき。",
"due_date_title": "日付の変更",
"due_date_subtitle": "作業項目の開始日または期限が更新されたとき。",
"module_title": "モジュールの更新",
"cycle_title": "サイクルの更新",
"mentioned_comments_title": "メンション",
"mentioned_comments_subtitle": "誰かがコメントであなたをメンションしたとき。",
"new_comments_title": "新しいコメント",
"new_comments_subtitle": "フォローしているタスクに新しいコメントが追加されたとき。",
"reaction_comments_title": "リアクション",
"reaction_comments_subtitle": "誰かがあなたのコメントやタスクに絵文字で反応したときに通知を受け取ります。",
"setting_updated_successfully": "設定が正常に更新されました",
"failed_to_update_setting": "設定の更新に失敗しました"
}
}

View File

@@ -2439,5 +2439,39 @@
"last_edited_by": "마지막 편집자",
"previously_edited_by": "이전 편집자",
"edited_by": "편집자"
},
"notification_settings": {
"page_label": "{workspace} - 받은 편지함 설정",
"inbox_settings": "받은 편지함 설정",
"inbox_settings_description": "작업 공간의 활동에 대한 알림을 받는 방법을 사용자 지정합니다. 변경 사항은 자동으로 저장됩니다.",
"advanced_settings": "고급 설정",
"in_plane": "Plane 내",
"email": "이메일",
"slack": "Slack",
"task_updates": "작업 항목 업데이트",
"task_updates_subtitle": "작업 공간의 작업 항목이 업데이트될 때 알림을 받습니다.",
"comments": "댓글",
"comments_subtitle": "작업 공간의 토론을 최신 상태로 유지합니다.",
"work_item_property_title": "작업 항목의 속성 업데이트",
"work_item_property_subtitle": "작업 공간의 작업 항목이 업데이트될 때 알림을 받습니다.",
"status_title": "상태 변경",
"status_subtitle": "작업 항목의 상태가 업데이트될 때.",
"priority_title": "우선순위 변경",
"priority_subtitle": "작업 항목의 우선순위 수준이 조정될 때.",
"assignee_title": "담당자 변경",
"assignee_subtitle": "작업 항목이 누군가에게 할당되거나 재할당될 때.",
"due_date_title": "날짜 변경",
"due_date_subtitle": "작업 항목의 시작일 또는 마감일이 업데이트될 때.",
"module_title": "모듈 업데이트",
"cycle_title": "사이클 업데이트",
"mentioned_comments_title": "멘션",
"mentioned_comments_subtitle": "누군가가 댓글에서 당신을 멘션할 때.",
"new_comments_title": "새 댓글",
"new_comments_subtitle": "팔로우 중인 작업에 새 댓글이 추가될 때.",
"reaction_comments_title": "반응",
"reaction_comments_subtitle": "누군가가 이모티콘으로 당신의 댓글이나 작업에 반응할 때 알림을 받습니다.",
"setting_updated_successfully": "설정이 성공적으로 업데이트되었습니다",
"failed_to_update_setting": "설정 업데이트 실패"
}
}

View File

@@ -2396,5 +2396,39 @@
"last_edited_by": "Ostatnio edytowane przez",
"previously_edited_by": "Wcześniej edytowane przez",
"edited_by": "Edytowane przez"
},
"notification_settings": {
"page_label": "{workspace} - Ustawienia skrzynki odbiorczej",
"inbox_settings": "Ustawienia skrzynki odbiorczej",
"inbox_settings_description": "Dostosuj sposób otrzymywania powiadomień o aktywnościach w swoim obszarze roboczym. Twoje zmiany są zapisywane automatycznie.",
"advanced_settings": "Zaawansowane ustawienia",
"in_plane": "W Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Aktualizacje elementów pracy",
"task_updates_subtitle": "Otrzymuj powiadomienia, gdy elementy pracy w twoim obszarze roboczym są aktualizowane.",
"comments": "Komentarze",
"comments_subtitle": "Bądź na bieżąco z dyskusjami w swoim obszarze roboczym.",
"work_item_property_title": "Aktualizacja dowolnej właściwości elementu pracy",
"work_item_property_subtitle": "Otrzymuj powiadomienia, gdy elementy pracy w twoim obszarze roboczym są aktualizowane.",
"status_title": "Zmiana stanu",
"status_subtitle": "Gdy stan elementu pracy jest aktualizowany.",
"priority_title": "Zmiana priorytetu",
"priority_subtitle": "Gdy poziom priorytetu elementu pracy jest dostosowywany.",
"assignee_title": "Zmiana przypisanego",
"assignee_subtitle": "Gdy element pracy jest przypisywany lub przypisywany ponownie komuś.",
"due_date_title": "Zmiana daty",
"due_date_subtitle": "Gdy data rozpoczęcia lub termin elementu pracy jest aktualizowany.",
"module_title": "Aktualizacja modułu",
"cycle_title": "Aktualizacja cyklu",
"mentioned_comments_title": "Wzmianki",
"mentioned_comments_subtitle": "Gdy ktoś wspomina cię w komentarzu.",
"new_comments_title": "Nowe komentarze",
"new_comments_subtitle": "Gdy nowy komentarz zostanie dodany do zadania, które śledzisz.",
"reaction_comments_title": "Reakcje",
"reaction_comments_subtitle": "Otrzymuj powiadomienia, gdy ktoś reaguje na twoje komentarze lub zadania za pomocą emotikony.",
"setting_updated_successfully": "Ustawienie zaktualizowane pomyślnie",
"failed_to_update_setting": "Nie udało się zaktualizować ustawienia"
}
}

View File

@@ -2432,5 +2432,39 @@
"last_edited_by": "Última edição por",
"previously_edited_by": "Anteriormente editado por",
"edited_by": "Editado por"
},
"notification_settings": {
"page_label": "{workspace} - Configurações da caixa de entrada",
"inbox_settings": "Configurações da caixa de entrada",
"inbox_settings_description": "Personalize como você recebe notificações para atividades no seu espaço de trabalho. Suas alterações são salvas automaticamente.",
"advanced_settings": "Configurações avançadas",
"in_plane": "No Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Atualizações de itens de trabalho",
"task_updates_subtitle": "Receba notificações quando os itens de trabalho no seu espaço de trabalho forem atualizados.",
"comments": "Comentários",
"comments_subtitle": "Fique atualizado sobre as discussões no seu espaço de trabalho.",
"work_item_property_title": "Atualização de qualquer propriedade do item de trabalho",
"work_item_property_subtitle": "Receba notificações quando os itens de trabalho no seu espaço de trabalho forem atualizados.",
"status_title": "Mudança de estado",
"status_subtitle": "Quando o estado de um item de trabalho é atualizado.",
"priority_title": "Mudança de prioridade",
"priority_subtitle": "Quando o nível de prioridade de um item de trabalho é ajustado.",
"assignee_title": "Mudança de responsável",
"assignee_subtitle": "Quando um item de trabalho é atribuído ou reatribuído a alguém.",
"due_date_title": "Mudança de data",
"due_date_subtitle": "Quando a data de início ou de vencimento de um item de trabalho é atualizada.",
"module_title": "Atualização de módulo",
"cycle_title": "Atualização de ciclo",
"mentioned_comments_title": "Menções",
"mentioned_comments_subtitle": "Quando alguém menciona você em um comentário.",
"new_comments_title": "Novos comentários",
"new_comments_subtitle": "Quando um novo comentário é adicionado a uma tarefa que você está seguindo.",
"reaction_comments_title": "Reações",
"reaction_comments_subtitle": "Receba notificações quando alguém reagir aos seus comentários ou tarefas com um emoji.",
"setting_updated_successfully": "Configuração atualizada com sucesso",
"failed_to_update_setting": "Falha ao atualizar a configuração"
}
}

View File

@@ -2431,5 +2431,39 @@
"last_edited_by": "Ultima editare de către",
"previously_edited_by": "Editat anterior de către",
"edited_by": "Editat de"
},
"notification_settings": {
"page_label": "{workspace} - Setări inbox",
"inbox_settings": "Setări inbox",
"inbox_settings_description": "Personalizează modul în care primești notificări pentru activitățile din spațiul tău de lucru. Modificările tale sunt salvate automat.",
"advanced_settings": "Setări avansate",
"in_plane": "În Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Actualizări ale elementelor de lucru",
"task_updates_subtitle": "Primește notificări când elementele de lucru din spațiul tău de lucru sunt actualizate.",
"comments": "Comentarii",
"comments_subtitle": "Rămâi la curent cu discuțiile din spațiul tău de lucru.",
"work_item_property_title": "Actualizare a oricărei proprietăți a elementului de lucru",
"work_item_property_subtitle": "Primește notificări când elementele de lucru din spațiul tău de lucru sunt actualizate.",
"status_title": "Schimbare de stare",
"status_subtitle": "Când starea unui element de lucru este actualizată.",
"priority_title": "Schimbare de prioritate",
"priority_subtitle": "Când nivelul de prioritate al unui element de lucru este ajustat.",
"assignee_title": "Schimbare de responsabil",
"assignee_subtitle": "Când un element de lucru este atribuit sau reatribuit cuiva.",
"due_date_title": "Schimbare de dată",
"due_date_subtitle": "Când data de început sau de finalizare a unui element de lucru este actualizată.",
"module_title": "Actualizare modul",
"cycle_title": "Actualizare ciclu",
"mentioned_comments_title": "Mențiuni",
"mentioned_comments_subtitle": "Când cineva te menționează într-un comentariu.",
"new_comments_title": "Comentarii noi",
"new_comments_subtitle": "Când un comentariu nou este adăugat la o sarcină pe care o urmărești.",
"reaction_comments_title": "Reacții",
"reaction_comments_subtitle": "Primește notificări când cineva reacționează la comentariile sau sarcinile tale cu un emoji.",
"setting_updated_successfully": "Setarea a fost actualizată cu succes",
"failed_to_update_setting": "Actualizarea setării a eșuat"
}
}

View File

@@ -2437,5 +2437,39 @@
"last_edited_by": "Последнее редактирование",
"previously_edited_by": "Ранее отредактировано",
"edited_by": "Отредактировано"
},
"notification_settings": {
"page_label": "{workspace} - Настройки входящих",
"inbox_settings": "Настройки входящих",
"inbox_settings_description": "Настройте, как вы получаете уведомления о действиях в вашем рабочем пространстве. Ваши изменения сохраняются автоматически.",
"advanced_settings": "Расширенные настройки",
"in_plane": "В Plane",
"email": "Электронная почта",
"slack": "Slack",
"task_updates": "Обновления рабочих элементов",
"task_updates_subtitle": "Получайте уведомления, когда рабочие элементы в вашем рабочем пространстве обновляются.",
"comments": "Комментарии",
"comments_subtitle": "Будьте в курсе обсуждений в вашем рабочем пространстве.",
"work_item_property_title": "Обновление любой свойства рабочего элемента",
"work_item_property_subtitle": "Получайте уведомления, когда рабочие элементы в вашем рабочем пространстве обновляются.",
"status_title": "Изменение состояния",
"status_subtitle": "Когда состояние рабочего элемента обновляется.",
"priority_title": "Изменение приоритета",
"priority_subtitle": "Когда уровень приоритета рабочего элемента изменяется.",
"assignee_title": "Изменение назначенного",
"assignee_subtitle": "Когда рабочий элемент назначается или переназначается кому-то.",
"due_date_title": "Изменение даты",
"due_date_subtitle": "Когда дата начала или срок выполнения рабочего элемента обновляется.",
"module_title": "Обновление модуля",
"cycle_title": "Обновление цикла",
"mentioned_comments_title": "Упоминания",
"mentioned_comments_subtitle": "Когда кто-то упоминает вас в комментарии.",
"new_comments_title": "Новые комментарии",
"new_comments_subtitle": "Когда новый комментарий добавляется к задаче, которую вы отслеживаете.",
"reaction_comments_title": "Реакции",
"reaction_comments_subtitle": "Получайте уведомления, когда кто-то реагирует на ваши комментарии или задачи с помощью эмодзи.",
"setting_updated_successfully": "Настройка успешно обновлена",
"failed_to_update_setting": "Не удалось обновить настройку"
}
}

View File

@@ -2436,5 +2436,39 @@
"last_edited_by": "Naposledy upravené používateľom",
"previously_edited_by": "Predtým upravené používateľom",
"edited_by": "Upravené používateľom"
},
"notification_settings": {
"page_label": "{workspace} - Nastavenia doručenej pošty",
"inbox_settings": "Nastavenia doručenej pošty",
"inbox_settings_description": "Prispôsobte si, ako dostávate upozornenia na aktivity vo vašom pracovnom priestore. Vaše zmeny sa ukladajú automaticky.",
"advanced_settings": "Pokročilé nastavenia",
"in_plane": "V Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Aktualizácie pracovných položiek",
"task_updates_subtitle": "Dostávajte upozornenia, keď sa aktualizujú pracovné položky vo vašom pracovnom priestore.",
"comments": "Komentáre",
"comments_subtitle": "Buďte informovaní o diskusiách vo vašom pracovnom priestore.",
"work_item_property_title": "Aktualizácia akejkoľvek vlastnosti pracovnej položky",
"work_item_property_subtitle": "Dostávajte upozornenia, keď sa aktualizujú pracovné položky vo vašom pracovnom priestore.",
"status_title": "Zmena stavu",
"status_subtitle": "Keď sa aktualizuje stav pracovnej položky.",
"priority_title": "Zmena priority",
"priority_subtitle": "Keď sa upraví úroveň priority pracovnej položky.",
"assignee_title": "Zmena priradeného",
"assignee_subtitle": "Keď sa pracovná položka priradí alebo priradí niekomu inému.",
"due_date_title": "Zmena dátumu",
"due_date_subtitle": "Keď sa aktualizuje dátum začiatku alebo termín pracovnej položky.",
"module_title": "Aktualizácia modulu",
"cycle_title": "Aktualizácia cyklu",
"mentioned_comments_title": "Zmienenia",
"mentioned_comments_subtitle": "Keď vás niekto zmieni v komentári.",
"new_comments_title": "Nové komentáre",
"new_comments_subtitle": "Keď sa pridá nový komentár k úlohe, ktorú sledujete.",
"reaction_comments_title": "Reakcie",
"reaction_comments_subtitle": "Dostávajte upozornenia, keď niekto reaguje na vaše komentáre alebo úlohy pomocou emotikonu.",
"setting_updated_successfully": "Nastavenie bolo úspešne aktualizované",
"failed_to_update_setting": "Nepodarilo sa aktualizovať nastavenie"
}
}

View File

@@ -2395,5 +2395,39 @@
"last_edited_by": "Останнє редагування",
"previously_edited_by": "Раніше відредаговано",
"edited_by": "Відредаговано"
},
"notification_settings": {
"page_label": "{workspace} - Налаштування вхідних",
"inbox_settings": "Налаштування вхідних",
"inbox_settings_description": "Налаштуйте, як ви отримуєте сповіщення про активності у вашому робочому просторі. Ваші зміни зберігаються автоматично.",
"advanced_settings": "Розширені налаштування",
"in_plane": "У Plane",
"email": "Електронна пошта",
"slack": "Slack",
"task_updates": "Оновлення робочих елементів",
"task_updates_subtitle": "Отримуйте сповіщення, коли робочі елементи у вашому робочому просторі оновлюються.",
"comments": "Коментарі",
"comments_subtitle": "Будьте в курсі обговорень у вашому робочому просторі.",
"work_item_property_title": "Оновлення будь-якої властивості робочого елемента",
"work_item_property_subtitle": "Отримуйте сповіщення, коли робочі елементи у вашому робочому просторі оновлюються.",
"status_title": "Зміна стану",
"status_subtitle": "Коли стан робочого елемента оновлюється.",
"priority_title": "Зміна пріоритету",
"priority_subtitle": "Коли рівень пріоритету робочого елемента змінюється.",
"assignee_title": "Зміна відповідального",
"assignee_subtitle": "Коли робочий елемент призначається або перепризначається комусь.",
"due_date_title": "Зміна дати",
"due_date_subtitle": "Коли дата початку або термін виконання робочого елемента оновлюється.",
"module_title": "Оновлення модуля",
"cycle_title": "Оновлення циклу",
"mentioned_comments_title": "Згадки",
"mentioned_comments_subtitle": "Коли хтось згадує вас у коментарі.",
"new_comments_title": "Нові коментарі",
"new_comments_subtitle": "Коли додається новий коментар до завдання, яке ви відстежуєте.",
"reaction_comments_title": "Реакції",
"reaction_comments_subtitle": "Отримуйте сповіщення, коли хтось реагує на ваші коментарі або завдання за допомогою емодзі.",
"setting_updated_successfully": "Налаштування успішно оновлено",
"failed_to_update_setting": "Не вдалося оновити налаштування"
}
}

View File

@@ -2393,5 +2393,39 @@
"last_edited_by": "Chỉnh sửa lần cuối bởi",
"previously_edited_by": "Trước đây được chỉnh sửa bởi",
"edited_by": "Được chỉnh sửa bởi"
},
"notification_settings": {
"page_label": "{workspace} - Cài đặt hộp thư đến",
"inbox_settings": "Cài đặt hộp thư đến",
"inbox_settings_description": "Tùy chỉnh cách bạn nhận thông báo cho các hoạt động trong không gian làm việc của bạn. Các thay đổi của bạn được lưu tự động.",
"advanced_settings": "Cài đặt nâng cao",
"in_plane": "Trong Plane",
"email": "Email",
"slack": "Slack",
"task_updates": "Cập nhật mục công việc",
"task_updates_subtitle": "Nhận thông báo khi các mục công việc trong không gian làm việc của bạn được cập nhật.",
"comments": "Bình luận",
"comments_subtitle": "Cập nhật thông tin về các cuộc thảo luận trong không gian làm việc của bạn.",
"work_item_property_title": "Cập nhật bất kỳ thuộc tính nào của mục công việc",
"work_item_property_subtitle": "Nhận thông báo khi các mục công việc trong không gian làm việc của bạn được cập nhật.",
"status_title": "Thay đổi trạng thái",
"status_subtitle": "Khi trạng thái của một mục công việc được cập nhật.",
"priority_title": "Thay đổi ưu tiên",
"priority_subtitle": "Khi mức độ ưu tiên của một mục công việc được điều chỉnh.",
"assignee_title": "Thay đổi người được giao",
"assignee_subtitle": "Khi một mục công việc được giao hoặc giao lại cho ai đó.",
"due_date_title": "Thay đổi ngày",
"due_date_subtitle": "Khi ngày bắt đầu hoặc ngày đến hạn của một mục công việc được cập nhật.",
"module_title": "Cập nhật mô-đun",
"cycle_title": "Cập nhật chu kỳ",
"mentioned_comments_title": "Đề cập",
"mentioned_comments_subtitle": "Khi ai đó đề cập đến bạn trong một bình luận.",
"new_comments_title": "Bình luận mới",
"new_comments_subtitle": "Khi một bình luận mới được thêm vào một nhiệm vụ bạn đang theo dõi.",
"reaction_comments_title": "Phản ứng",
"reaction_comments_subtitle": "Nhận thông báo khi ai đó phản ứng với bình luận hoặc nhiệm vụ của bạn bằng một biểu tượng cảm xúc.",
"setting_updated_successfully": "Cài đặt đã được cập nhật thành công",
"failed_to_update_setting": "Không thể cập nhật cài đặt"
}
}

View File

@@ -2427,5 +2427,39 @@
"last_edited_by": "最后编辑者",
"previously_edited_by": "之前编辑者",
"edited_by": "编辑者"
},
"notification_settings": {
"page_label": "{workspace} - 收件箱设置",
"inbox_settings": "收件箱设置",
"inbox_settings_description": "自定义您如何接收工作区活动的通知。您的更改会自动保存。",
"advanced_settings": "高级设置",
"in_plane": "在 Plane",
"email": "电子邮件",
"slack": "Slack",
"task_updates": "任务更新",
"task_updates_subtitle": "当您工作区中的任务更新时收到通知。",
"comments": "评论",
"comments_subtitle": "随时了解您工作区中的讨论。",
"work_item_property_title": "工作项任何属性的更新",
"work_item_property_subtitle": "当您工作区中的工作项更新时获得通知。",
"status_title": "状态变更",
"status_subtitle": "当工作项的状态更新时。",
"priority_title": "优先级变更",
"priority_subtitle": "当工作项的优先级被调整时。",
"assignee_title": "负责人变更",
"assignee_subtitle": "当工作项被分配或重新分配给某人时。",
"due_date_title": "日期变更",
"due_date_subtitle": "当工作项的开始日期或截止日期被更新时。",
"module_title": "模块变更",
"cycle_title": "周期变更",
"mentioned_comments_title": "提及",
"mentioned_comments_subtitle": "当有人在评论中提及您时。",
"new_comments_title": "新评论",
"new_comments_subtitle": "当您关注的任务中添加新评论时。",
"reaction_comments_title": "反应",
"reaction_comments_subtitle": "当有人用表情符号对您的评论或任务做出反应时收到通知。",
"setting_updated_successfully": "设置已成功更新",
"failed_to_update_setting": "无法更新设置"
}
}

View File

@@ -2439,5 +2439,39 @@
"last_edited_by": "最後編輯者",
"previously_edited_by": "先前編輯者",
"edited_by": "編輯者"
},
"notification_settings": {
"page_label": "{workspace} - 收件匣設定",
"inbox_settings": "收件匣設定",
"inbox_settings_description": "自訂您如何接收工作區活動的通知。您的更改會自動保存。",
"advanced_settings": "進階設定",
"in_plane": "在 Plane",
"email": "電子郵件",
"slack": "Slack",
"task_updates": "工作項更新",
"task_updates_subtitle": "當工作區中的工作項更新時收到通知。",
"comments": "評論",
"comments_subtitle": "隨時了解工作區中的討論。",
"work_item_property_title": "工作項的任何屬性更新",
"work_item_property_subtitle": "當工作區中的工作項更新時收到通知。",
"status_title": "狀態更改",
"status_subtitle": "當工作項的狀態更新時。",
"priority_title": "優先級更改",
"priority_subtitle": "當工作項的優先級級別調整時。",
"assignee_title": "負責人更改",
"assignee_subtitle": "當工作項被分配或重新分配給某人時。",
"due_date_title": "日期更改",
"due_date_subtitle": "當工作項的開始或截止日期更新時。",
"module_title": "模組更新",
"cycle_title": "週期更新",
"mentioned_comments_title": "提及",
"mentioned_comments_subtitle": "當有人在評論中提到您時。",
"new_comments_title": "新評論",
"new_comments_subtitle": "當您關注的任務中添加新評論時。",
"reaction_comments_title": "反應",
"reaction_comments_subtitle": "當有人用表情符號對您的評論或任務做出反應時收到通知。",
"setting_updated_successfully": "設定更新成功",
"failed_to_update_setting": "設定更新失敗"
}
}

View File

@@ -41,4 +41,5 @@ export * from "./epics";
export * from "./charts";
export * from "./home";
export * from "./stickies";
export * from "./notification";
export * from "./utils";

21
packages/types/src/notification.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
import { ENotificationSettingsKey, EWorkspaceNotificationTransport } from "@plane/constants";
export type TNotificationSettings = {
i18n_title: string,
i18n_subtitle?: string,
key: ENotificationSettingsKey
}
export type TWorkspaceUserNotification = {
workspace: string,
user: string,
transport: EWorkspaceNotificationTransport,
property_change: boolean,
state_change: boolean,
priority: boolean,
assignee: boolean,
start_due_date: boolean,
comment: boolean,
mention: boolean,
comment_reactions: boolean
}

View File

@@ -0,0 +1,44 @@
"use client";
import { FC } from "react";
import { observer } from "mobx-react";
import { Inbox, Settings } from "lucide-react";
// ui
import { useTranslation } from "@plane/i18n";
import { Breadcrumbs, Header } from "@plane/ui";
// components
import { BreadcrumbLink } from "@/components/common";
// hooks
import { useWorkspace } from "@/hooks/store";
export const NotificationsSettingsHeader: FC = observer(() => {
const { currentWorkspace, loader } = useWorkspace();
const { t } = useTranslation();
return (
<Header>
<Header.LeftItem>
<Breadcrumbs isLoading={loader}>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
href={`/${currentWorkspace?.slug}/notifications/`}
label={t("notification.label")}
icon={<Inbox className="h-4 w-4 text-custom-text-300" />}
/>
}
/>
<Breadcrumbs.BreadcrumbItem
type="text"
link={
<BreadcrumbLink
label={t("notification_settings.inbox_settings")}
icon={<Settings className="h-4 w-4 text-custom-text-300" />} />
}
/>
</Breadcrumbs>
</Header.LeftItem>
</Header>
);
});

View File

@@ -0,0 +1,24 @@
"use client"
import { FC, ReactNode } from "react"
// plane ui
import { AppHeader } from "@/components/core"
import { NotificationsSettingsHeader } from "./header";
export interface INotificationsSettingsLayoutProps {
children: ReactNode;
}
const NotificationsSettingsLayout: FC<INotificationsSettingsLayoutProps> = (props) => {
const { children } = props
return (
<>
<AppHeader header={<NotificationsSettingsHeader />} />
{children}
</>
)
}
export default NotificationsSettingsLayout;

View File

@@ -0,0 +1,45 @@
"use client";
import { observer } from "mobx-react";
// components
import useSWR from "swr";
import { useTranslation } from "@plane/i18n";
import { PageHead } from "@/components/core";
// hooks
import { InboxSettingsContentHeader, InboxSettingContentWrapper } from "@/components/inbox/settings";
import { EmailSettingsLoader } from "@/components/ui";
import { NOTIFICATION_SETTINGS } from "@/constants/fetch-keys";
import { useWorkspaceNotificationSettings } from "@/hooks/store";
import { InboxSettingsRoot } from "@/plane-web/components/inbox/settings/root";
const NotificationsSettingsPage = observer(() => {
// store hooks
const { workspace: currentWorkspace, fetchWorkspaceUserNotificationSettings } = useWorkspaceNotificationSettings();
const { t } = useTranslation();
// derived values
const pageTitle = currentWorkspace?.name
? t("notification_settings.page_label", { workspace: currentWorkspace.name })
: undefined;
const { data, isLoading } = useSWR(currentWorkspace?.slug ? NOTIFICATION_SETTINGS(currentWorkspace?.slug) : null, () => fetchWorkspaceUserNotificationSettings());
if (!data || isLoading) {
return <EmailSettingsLoader />;
}
return (
<>
<PageHead title={pageTitle} />
<InboxSettingContentWrapper>
<InboxSettingsContentHeader
title={t("notification_settings.inbox_settings")}
description={t("notification_settings.inbox_settings_description")}
/>
<InboxSettingsRoot />
</InboxSettingContentWrapper>
</>
);
});
export default NotificationsSettingsPage;

View File

@@ -0,0 +1 @@
export * from "./settings"

View File

@@ -0,0 +1,2 @@
export * from "./root";
export * from "./update-setting-row";

View File

@@ -0,0 +1,56 @@
"use client";
import { FC } from "react";
import { COMMENT_NOTIFICATION_SETTINGS, TASK_UPDATES_NOTIFICATION_SETTINGS } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { InboxSettingUpdateRow } from "./update-setting-row";
export const InboxSettingsRoot: FC = () => {
const { t } = useTranslation();
return (
<>
<div className="flex flex-col gap-5 pt-4 pb-10 border-b border-custom-border-100">
<div className="flex flex-col gap-1">
<div className="text-lg font-normal text-custom-text-100">
{t("notification_settings.task_updates")}
</div>
<div className="text-sm text-custom-text-350">
{t("notification_settings.task_updates_subtitle")}
</div>
</div>
<div className="grid gap-4 grid-cols-[50%_repeat(auto-fit,minmax(0,1fr))] text-sm text-custom-text-350 font-semibold">
<div>{t("notification_settings.advanced_settings")}</div>
<div>{t("notification_settings.in_plane")}</div>
<div>{t("notification_settings.email")}</div>
</div>
{
TASK_UPDATES_NOTIFICATION_SETTINGS?.map((item) => (
<InboxSettingUpdateRow key={item.key} settings_key={item.key} title={item.i18n_title} subtitle={item.i18n_subtitle} />
))
}
</div>
<div className="flex flex-col gap-5 py-4">
<div className="flex flex-col gap-1">
<div className="text-lg font-normal text-custom-text-100">
{t("notification_settings.comments")}
</div>
<div className="text-sm text-custom-text-350">
{t("notification_settings.comments_subtitle")}
</div>
</div>
<div className="grid gap-4 grid-cols-[50%_repeat(auto-fit,minmax(0,1fr))] text-sm text-custom-text-350 font-semibold">
<div>{t("notification_settings.advanced_settings")}</div>
<div>{t("notification_settings.in_plane")}</div>
<div>{t("notification_settings.email")}</div>
</div>
{
COMMENT_NOTIFICATION_SETTINGS?.map((item, index) => (
<InboxSettingUpdateRow key={index} settings_key={item.key} title={item.i18n_title} subtitle={item.i18n_subtitle} />
))
}
</div>
</>
);
};

View File

@@ -0,0 +1,40 @@
"use client"
import { FC } from "react";
import { observer } from "mobx-react";
import { ENotificationSettingsKey, EWorkspaceNotificationTransport } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { InboxSettingUpdate } from "@/components/inbox";
type InboxSettingUpdateRowProps = {
settings_key: ENotificationSettingsKey;
title: string;
subtitle?: string
}
export const InboxSettingUpdateRow: FC<InboxSettingUpdateRowProps> = observer((props: InboxSettingUpdateRowProps) => {
const { title, subtitle, settings_key } = props;
const { t } = useTranslation()
return (
<div className="w-full grid gap-4 grid-cols-[50%_repeat(auto-fit,minmax(0,1fr))]">
<div className="flex flex-col gap-1">
<div className="text-base font-normal text-custom-text-200">
{t(title)}
</div>
{
subtitle && <div className="text-sm text-custom-text-350">
{t(subtitle)}
</div>
}
</div>
<div className="">
<InboxSettingUpdate transport={EWorkspaceNotificationTransport.IN_APP} settings_key={settings_key} />
</div>
<div className="">
<InboxSettingUpdate transport={EWorkspaceNotificationTransport.EMAIL} settings_key={settings_key} />
</div>
</div>
);
})

View File

@@ -4,3 +4,4 @@ export * from "./sidebar";
export * from "./inbox-filter";
export * from "./content";
export * from "./inbox-issue-status";
export * from "./settings";

View File

@@ -0,0 +1,17 @@
"use client";
import React, { FC } from "react";
type Props = {
title: string;
description?: string;
};
export const InboxSettingsContentHeader: FC<Props> = (props) => {
const { title, description } = props;
return (
<div className="flex flex-col gap-1 py-4 border-b border-custom-border-100">
<div className="text-xl font-medium text-custom-text-100">{title}</div>
{description && <div className="text-sm font-normal text-custom-text-300">{description}</div>}
</div>
);
};

View File

@@ -0,0 +1,31 @@
"use client";
import React, { FC } from "react";
// helpers
import { SidebarHamburgerToggle } from "@/components/core";
import { cn } from "@/helpers/common.helper";
type Props = {
children: React.ReactNode;
className?: string;
};
export const InboxSettingContentWrapper: FC<Props> = (props) => {
const { children, className = "" } = props;
return (
<div className="flex h-full flex-col">
<div className="block flex-shrink-0 border-b border-custom-border-200 p-4 md:hidden">
<SidebarHamburgerToggle />
</div>
<div
className={cn(
"vertical-scrollbar scrollbar-md mx-auto h-full w-full flex flex-col px-8 md:px-20 lg:px-36 xl:px-56 py-10 md:py-16",
className
)}
>
{children}
</div>
</div>
);
};

View File

@@ -0,0 +1,3 @@
export * from "./content-header"
export * from "./content-wrapper"
export * from "./update-setting"

View File

@@ -0,0 +1,54 @@
"use client"
import { FC } from "react";
import { observer } from "mobx-react";
import { ENotificationSettingsKey, EWorkspaceNotificationTransport } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { setToast, TOAST_TYPE, ToggleSwitch } from "@plane/ui";
import { useWorkspaceNotificationSettings } from "@/hooks/store";
type InboxSettingUpdateProps = {
settings_key: ENotificationSettingsKey;
transport: EWorkspaceNotificationTransport;
}
export const InboxSettingUpdate: FC<InboxSettingUpdateProps> = observer((props: InboxSettingUpdateProps) => {
const { transport, settings_key } = props;
const { t } = useTranslation()
const { getNotificationSettingsForTransport, updateWorkspaceUserNotificationSettings } = useWorkspaceNotificationSettings();
const notificationSettings = getNotificationSettingsForTransport(transport);
const handleChange = async (value: boolean) => {
try {
await updateWorkspaceUserNotificationSettings(transport, {
[settings_key]: value,
});
setToast({
title: t("success"),
type: TOAST_TYPE.SUCCESS,
message: t("notification_settings.setting_updated_successfully"),
})
} catch (error) {
setToast({
title: t("error"),
type: TOAST_TYPE.ERROR,
message: t("notification_settings.failed_to_update_setting"),
})
}
}
return (
<ToggleSwitch
value={notificationSettings ? notificationSettings[settings_key] : false}
onChange={(newValue) => {
handleChange(newValue);
}}
size="md"
/>
);
})

View File

@@ -221,6 +221,8 @@ export const GITHUB_REPOSITORY_INFO = (workspaceSlug: string, repoName: string)
// slack-project-integration
export const SLACK_CHANNEL_INFO = (workspaceSlug: string, projectId: string) =>
`SLACK_CHANNEL_INFO_${workspaceSlug.toString().toUpperCase()}_${projectId.toUpperCase()}`;
export const SLACK_USER_CONNECTION_STATUS = (workspaceSlug: string) =>
`SLACK_USER_CONNECTION_STATUS_${workspaceSlug.toString().toUpperCase()}`;
// Pages
export const RECENT_PAGES_LIST = (projectId: string) => `RECENT_PAGES_LIST_${projectId.toUpperCase()}`;
@@ -273,3 +275,7 @@ export const COMMENT_REACTION_LIST = (workspaceSlug: string, projectId: string,
export const API_TOKENS_LIST = (workspaceSlug: string) => `API_TOKENS_LIST_${workspaceSlug.toUpperCase()}`;
export const API_TOKEN_DETAILS = (workspaceSlug: string, tokenId: string) =>
`API_TOKEN_DETAILS_${workspaceSlug.toUpperCase()}_${tokenId.toUpperCase()}`;
// notification settings
export const NOTIFICATION_SETTINGS = (workspaceSlug: string) =>
`NOTIFICATION_SETTINGS_${workspaceSlug.toUpperCase()}`;

View File

@@ -1,2 +1,3 @@
export * from "./use-workspace-notifications";
export * from "./use-notification";
export * from './use-workspace-notification-settings';

View File

@@ -0,0 +1,12 @@
import { useContext } from "react";
// mobx store
import { StoreContext } from "@/lib/store-context";
// mobx store
import { IWorkspaceNotificationSettingsStore } from "@/store/notifications/workspace-notification-settings.store";
export const useWorkspaceNotificationSettings = (): IWorkspaceNotificationSettingsStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useNotification must be used within StoreProvider");
return context.workspaceNotificationSettings;
};

View File

@@ -0,0 +1,46 @@
/* eslint-disable no-useless-catch */
import { EWorkspaceNotificationTransport } from "@plane/constants";
import type {
TWorkspaceUserNotification,
} from "@plane/types";
// helpers
import { API_BASE_URL } from "@/helpers/common.helper";
// services
import { APIService } from "@/services/api.service";
export class WorkspaceNotificationSettingsService extends APIService {
constructor() {
super(API_BASE_URL);
}
async fetchNotificationSettings(workspaceSlug: string): Promise<TWorkspaceUserNotification[] | undefined> {
try {
const { data } = await this.get(`/api/workspaces/${workspaceSlug}/user-notification-preferences/`);
return data || undefined;
} catch (error) {
throw error;
}
}
async updateNotificationSettings(
workspaceSlug: string,
transport: EWorkspaceNotificationTransport,
payload: Partial<TWorkspaceUserNotification>
): Promise<TWorkspaceUserNotification | undefined> {
try {
const { data } = await this.patch(
`/api/workspaces/${workspaceSlug}/user-notification-preferences/${transport}/`,
payload
);
return data || undefined;
} catch (error) {
throw error;
}
}
}
const workspaceNotificationSettingService = new WorkspaceNotificationSettingsService();
export default workspaceNotificationSettingService;

View File

@@ -0,0 +1,164 @@
import set from "lodash/set";
import { action, autorun, makeObservable, observable, runInAction } from "mobx";
import { computedFn } from "mobx-utils";
import { EWorkspaceNotificationTransport } from "@plane/constants";
import { IUser, IWorkspace, TWorkspaceUserNotification } from "@plane/types";
// plane web services
// plane web root store
import { WorkspaceNotificationSettingsService } from "@/services/workspace-notification-settings.service";
import { CoreRootStore } from "../root.store";
export interface IWorkspaceNotificationSettingsStore {
// observables
error: object;
user: IUser | undefined;
workspace: IWorkspace | undefined;
settings: Record<string, Record<EWorkspaceNotificationTransport, TWorkspaceUserNotification>>; // workspaceSlug -> transport -> settings
// computed functions
notificationSettingsForWorkspace: () => Record<string, TWorkspaceUserNotification> | undefined;
getNotificationSettingsForTransport: (
transport: EWorkspaceNotificationTransport
) => TWorkspaceUserNotification | undefined;
// helper actions
fetchWorkspaceUserNotificationSettings: () => Promise<TWorkspaceUserNotification[] | undefined>;
updateWorkspaceUserNotificationSettings: (
transport: EWorkspaceNotificationTransport,
settings: Partial<TWorkspaceUserNotification>
) => Promise<TWorkspaceUserNotification | undefined>;
}
export class WorkspaceNotificationSettingsStore implements IWorkspaceNotificationSettingsStore {
// observables
error: object = {};
user: IUser | undefined = undefined;
workspace: IWorkspace | undefined = undefined;
settings: Record<string, Record<EWorkspaceNotificationTransport, TWorkspaceUserNotification>> = {};
settingService: WorkspaceNotificationSettingsService;
constructor(public store: CoreRootStore) {
makeObservable(this, {
// observables
error: observable,
user: observable,
workspace: observable,
settings: observable,
// actions
fetchWorkspaceUserNotificationSettings: action,
updateWorkspaceUserNotificationSettings: action,
});
autorun(() => {
const {
workspaceRoot: { currentWorkspace },
user: { data: currentUser },
} = this.store;
if (
currentWorkspace &&
currentUser &&
(!this.workspace ||
!this.user ||
this.workspace?.id !== currentWorkspace?.id ||
this.user?.id !== currentUser?.id)
) {
this.user = currentUser;
this.workspace = currentWorkspace;
}
});
this.settingService = new WorkspaceNotificationSettingsService();
}
// computed functions
/**
* @description get project ids by workspace slug
* @param { string } workspaceSlug
* @returns { string[] | undefined }
*/
notificationSettingsForWorkspace = computedFn(() => {
const workspaceSlug = this.store.workspaceRoot?.currentWorkspace?.slug;
if (!workspaceSlug) {
return;
}
return this.settings[workspaceSlug];
});
/**
* @description get notification settings for the workspace for a transport
* @param { EWorkspaceNotificationTransport } transport
* @returns { TWorkspaceUserNotification }
*/
getNotificationSettingsForTransport = computedFn((transport: EWorkspaceNotificationTransport) => {
const workspaceSlug = this.store.workspaceRoot?.currentWorkspace?.slug;
if (!workspaceSlug || !transport) {
return;
}
const notificationSettingsForTransport = this.settings[workspaceSlug][transport] || undefined;
return notificationSettingsForTransport;
});
// helper actions
/**
* @description handle states
* @returns { TWorkspaceUserNotification[] | undefined }
*/
fetchWorkspaceUserNotificationSettings = async (): Promise<TWorkspaceUserNotification[] | undefined> => {
const workspaceSlug = this.store.workspaceRoot.currentWorkspace?.slug;
if (!workspaceSlug) return undefined;
this.error = {};
try {
const notificationSettings = await this.settingService.fetchNotificationSettings(workspaceSlug)
if (notificationSettings) {
runInAction(() => {
notificationSettings.forEach((state) => {
const { transport } = state;
set(this.settings, [workspaceSlug, transport], state);
});
});
}
return notificationSettings;
} catch (error) {
runInAction(() => {
this.error = error as unknown as object;
});
throw error;
}
};
/**
* @description - updates user notification settings for a transport
* @param transport
* @param settings
* @returns { EWorkspaceNotificationTransport }
*/
updateWorkspaceUserNotificationSettings = async (
transport: EWorkspaceNotificationTransport,
settings: Partial<TWorkspaceUserNotification>): Promise<TWorkspaceUserNotification | undefined> => {
const workspaceSlug = this.store.workspaceRoot.currentWorkspace?.slug;
if (!workspaceSlug || !transport || !settings) {
return undefined;
}
try {
const notificationSetting = await this.settingService.updateNotificationSettings(workspaceSlug, transport, settings)
if (notificationSetting) {
runInAction(() => {
set(this.settings, [workspaceSlug, transport], notificationSetting)
})
}
return notificationSetting;
} catch (error) {
runInAction(() => {
this.error = error as unknown as object;
});
throw error;
}
};
}

View File

@@ -22,6 +22,7 @@ import { IMemberRootStore, MemberRootStore } from "./member";
import { IModuleStore, ModulesStore } from "./module.store";
import { IModuleFilterStore, ModuleFilterStore } from "./module_filter.store";
import { IMultipleSelectStore, MultipleSelectStore } from "./multiple_select.store";
import { IWorkspaceNotificationSettingsStore, WorkspaceNotificationSettingsStore } from "./notifications/workspace-notification-settings.store";
import { IWorkspaceNotificationStore, WorkspaceNotificationStore } from "./notifications/workspace-notifications.store";
import { IProjectPageStore, ProjectPageStore } from "./pages/project-page.store";
import { IProjectRootStore, ProjectRootStore } from "./project";
@@ -60,6 +61,7 @@ export class CoreRootStore {
projectEstimate: IProjectEstimateStore;
multipleSelect: IMultipleSelectStore;
workspaceNotification: IWorkspaceNotificationStore;
workspaceNotificationSettings: IWorkspaceNotificationSettingsStore;
favorite: IFavoriteStore;
transient: ITransientStore;
stickyStore: IStickyStore;
@@ -89,6 +91,7 @@ export class CoreRootStore {
this.projectInbox = new ProjectInboxStore(this);
this.projectPages = new ProjectPageStore(this as unknown as RootStore);
this.projectEstimate = new ProjectEstimateStore(this);
this.workspaceNotificationSettings = new WorkspaceNotificationSettingsStore(this);
this.workspaceNotification = new WorkspaceNotificationStore(this);
this.favorite = new FavoriteStore(this);
this.transient = new TransientStore();
@@ -122,6 +125,7 @@ export class CoreRootStore {
this.projectPages = new ProjectPageStore(this as unknown as RootStore);
this.multipleSelect = new MultipleSelectStore();
this.projectEstimate = new ProjectEstimateStore(this);
this.workspaceNotificationSettings = new WorkspaceNotificationSettingsStore(this);
this.workspaceNotification = new WorkspaceNotificationStore(this);
this.favorite = new FavoriteStore(this);
this.transient = new TransientStore();