Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21617234ed | |||
| 58b836dc66 | |||
| ace317445d | |||
| c5df671913 |
@@ -88,7 +88,7 @@ jobs:
|
||||
|
||||
full_build_push:
|
||||
if: ${{ needs.branch_build_setup.outputs.do_full_build == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
BUILD_TYPE: full
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
|
||||
slim_build_push:
|
||||
if: ${{ needs.branch_build_setup.outputs.do_slim_build == 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
BUILD_TYPE: slim
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
full_build_push:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
needs: [branch_build_setup]
|
||||
env:
|
||||
BUILD_TYPE: full
|
||||
|
||||
@@ -11,7 +11,7 @@ env:
|
||||
|
||||
jobs:
|
||||
sync_changes:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
@@ -853,7 +853,7 @@ def delete_cycle_issue_activity(
|
||||
issues = requested_data.get("issues")
|
||||
for issue in issues:
|
||||
current_issue = Issue.objects.filter(pk=issue).first()
|
||||
if current_issue:
|
||||
if issue:
|
||||
current_issue.updated_at = timezone.now()
|
||||
current_issue.save(update_fields=["updated_at"])
|
||||
issue_activities.append(
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 4.2.17 on 2025-03-04 19:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("db", "0092_alter_deprecateddashboardwidget_unique_together_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="page",
|
||||
name="moved_to_page",
|
||||
field=models.UUIDField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="page",
|
||||
name="moved_to_project",
|
||||
field=models.UUIDField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="pageversion",
|
||||
name="sub_pages_data",
|
||||
field=models.JSONField(blank=True, default=dict),
|
||||
),
|
||||
]
|
||||
@@ -82,4 +82,4 @@ from .label import Label
|
||||
|
||||
from .device import Device, DeviceSession
|
||||
|
||||
from .sticky import Sticky
|
||||
from .sticky import Sticky
|
||||
@@ -50,6 +50,8 @@ class Page(BaseModel):
|
||||
projects = models.ManyToManyField(
|
||||
"db.Project", related_name="pages", through="db.ProjectPage"
|
||||
)
|
||||
moved_to_page = models.UUIDField(null=True, blank=True)
|
||||
moved_to_project = models.UUIDField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Page"
|
||||
@@ -172,6 +174,7 @@ class PageVersion(BaseModel):
|
||||
description_html = models.TextField(blank=True, default="<p></p>")
|
||||
description_stripped = models.TextField(blank=True, null=True)
|
||||
description_json = models.JSONField(default=dict, blank=True)
|
||||
sub_pages_data = models.JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Page Version"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM nginx:1.25.0-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY nginx/nginx.conf.template /etc/nginx/nginx.conf.template
|
||||
COPY nginx.conf.template /etc/nginx/nginx.conf.template
|
||||
|
||||
COPY nginx/env.sh /docker-entrypoint.sh
|
||||
COPY ./env.sh /docker-entrypoint.sh
|
||||
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
# Update all environment variables
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM nginx:1.25.0-alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
COPY nginx/nginx.conf.dev /etc/nginx/nginx.conf.template
|
||||
COPY nginx.conf.dev /etc/nginx/nginx.conf.template
|
||||
|
||||
COPY nginx/env.sh /docker-entrypoint.sh
|
||||
COPY ./env.sh /docker-entrypoint.sh
|
||||
|
||||
RUN chmod +x /docker-entrypoint.sh
|
||||
# Update all environment variables
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Editor } from "@tiptap/react";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { Editor } from "@tiptap/react";
|
||||
// plane utils
|
||||
import { cn } from "@plane/utils";
|
||||
// constants
|
||||
@@ -71,7 +71,7 @@ export const EditorContainer: FC<EditorContainerProps> = (props) => {
|
||||
onClick={handleContainerClick}
|
||||
onMouseLeave={handleContainerMouseLeave}
|
||||
className={cn(
|
||||
`editor-container cursor-text relative line-spacing-${displayConfig.lineSpacing ?? DEFAULT_DISPLAY_CONFIG.lineSpacing}`,
|
||||
"editor-container cursor-text relative",
|
||||
{
|
||||
"active-editor": editor?.isFocused && editor?.isEditable,
|
||||
},
|
||||
|
||||
@@ -4,7 +4,6 @@ import { TDisplayConfig } from "@/types";
|
||||
export const DEFAULT_DISPLAY_CONFIG: TDisplayConfig = {
|
||||
fontSize: "large-font",
|
||||
fontStyle: "sans-serif",
|
||||
lineSpacing: "regular",
|
||||
};
|
||||
|
||||
export const ACCEPTED_FILE_MIME_TYPES = ["image/jpeg", "image/jpg", "image/png", "image/webp", "image/gif"];
|
||||
|
||||
@@ -26,12 +26,12 @@ export const CoreEditorExtensionsWithoutProps = [
|
||||
StarterKit.configure({
|
||||
bulletList: {
|
||||
HTMLAttributes: {
|
||||
class: "list-disc pl-7 space-y-[--list-spacing-y]",
|
||||
class: "list-disc pl-7 space-y-2",
|
||||
},
|
||||
},
|
||||
orderedList: {
|
||||
HTMLAttributes: {
|
||||
class: "list-decimal pl-7 space-y-[--list-spacing-y]",
|
||||
class: "list-decimal pl-7 space-y-2",
|
||||
},
|
||||
},
|
||||
listItem: {
|
||||
|
||||
@@ -55,12 +55,12 @@ export const CoreEditorExtensions = (args: TArguments): Extensions => {
|
||||
StarterKit.configure({
|
||||
bulletList: {
|
||||
HTMLAttributes: {
|
||||
class: "list-disc pl-7 space-y-[--list-spacing-y]",
|
||||
class: "list-disc pl-7 space-y-2",
|
||||
},
|
||||
},
|
||||
orderedList: {
|
||||
HTMLAttributes: {
|
||||
class: "list-decimal pl-7 space-y-[--list-spacing-y]",
|
||||
class: "list-decimal pl-7 space-y-2",
|
||||
},
|
||||
},
|
||||
listItem: {
|
||||
|
||||
@@ -46,12 +46,12 @@ export const CoreReadOnlyEditorExtensions = (props: Props): Extensions => {
|
||||
StarterKit.configure({
|
||||
bulletList: {
|
||||
HTMLAttributes: {
|
||||
class: "list-disc pl-7 space-y-[--list-spacing-y]",
|
||||
class: "list-disc pl-7 space-y-2",
|
||||
},
|
||||
},
|
||||
orderedList: {
|
||||
HTMLAttributes: {
|
||||
class: "list-decimal pl-7 space-y-[--list-spacing-y]",
|
||||
class: "list-decimal pl-7 space-y-2",
|
||||
},
|
||||
},
|
||||
listItem: {
|
||||
|
||||
@@ -23,10 +23,7 @@ export type TEditorFontStyle = "sans-serif" | "serif" | "monospace";
|
||||
|
||||
export type TEditorFontSize = "small-font" | "large-font";
|
||||
|
||||
export type TEditorLineSpacing = "regular" | "small";
|
||||
|
||||
export type TDisplayConfig = {
|
||||
fontStyle?: TEditorFontStyle;
|
||||
fontSize?: TEditorFontSize;
|
||||
lineSpacing?: TEditorLineSpacing;
|
||||
};
|
||||
|
||||
@@ -252,7 +252,7 @@ ul[data-type="taskList"] li[data-checked="true"] {
|
||||
|
||||
div[data-type="horizontalRule"] {
|
||||
line-height: 0;
|
||||
padding: var(--divider-padding-top) 0 var(--divider-padding-bottom) 0;
|
||||
padding: 0.25rem 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -335,10 +335,10 @@ p.editor-paragraph-block {
|
||||
/* tailwind typography */
|
||||
.prose :where(h1.editor-heading-block):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--heading-1-padding-top);
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
padding-bottom: var(--heading-1-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h1);
|
||||
line-height: var(--line-height-h1);
|
||||
font-weight: 600;
|
||||
@@ -346,10 +346,10 @@ p.editor-paragraph-block {
|
||||
|
||||
.prose :where(h2.editor-heading-block):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--heading-2-padding-top);
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
padding-bottom: var(--heading-2-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h2);
|
||||
line-height: var(--line-height-h2);
|
||||
font-weight: 600;
|
||||
@@ -357,10 +357,10 @@ p.editor-paragraph-block {
|
||||
|
||||
.prose :where(h3.editor-heading-block):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--heading-3-padding-top);
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
padding-bottom: var(--heading-3-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h3);
|
||||
line-height: var(--line-height-h3);
|
||||
font-weight: 600;
|
||||
@@ -368,10 +368,10 @@ p.editor-paragraph-block {
|
||||
|
||||
.prose :where(h4.editor-heading-block):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--heading-4-padding-top);
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
padding-bottom: var(--heading-4-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h4);
|
||||
line-height: var(--line-height-h4);
|
||||
font-weight: 600;
|
||||
@@ -379,10 +379,10 @@ p.editor-paragraph-block {
|
||||
|
||||
.prose :where(h5.editor-heading-block):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--heading-5-padding-top);
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
padding-bottom: var(--heading-5-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h5);
|
||||
line-height: var(--line-height-h5);
|
||||
font-weight: 600;
|
||||
@@ -390,10 +390,10 @@ p.editor-paragraph-block {
|
||||
|
||||
.prose :where(h6.editor-heading-block):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--heading-6-padding-top);
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
padding-bottom: var(--heading-6-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
font-size: var(--font-size-h6);
|
||||
line-height: var(--line-height-h6);
|
||||
font-weight: 600;
|
||||
@@ -405,16 +405,16 @@ p.editor-paragraph-block {
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
padding-top: var(--paragraph-padding-top);
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
&:not(td p.editor-paragraph-block, th p.editor-paragraph-block) {
|
||||
&:last-child {
|
||||
padding-bottom: var(--paragraph-padding-bottom);
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
padding-bottom: var(--paragraph-padding-between);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ p.editor-paragraph-block {
|
||||
}
|
||||
|
||||
p.editor-paragraph-block + p.editor-paragraph-block {
|
||||
padding-top: var(--paragraph-padding-between) !important;
|
||||
padding-top: 8px !important;
|
||||
}
|
||||
|
||||
.prose :where(ol):not(:where([class~="not-prose"], [class~="not-prose"] *)) li p.editor-paragraph-block,
|
||||
|
||||
@@ -57,48 +57,4 @@
|
||||
--font-style: monospace;
|
||||
}
|
||||
/* end font styles */
|
||||
|
||||
/* spacing */
|
||||
&.line-spacing-regular {
|
||||
--heading-1-padding-top: 28px;
|
||||
--heading-1-padding-bottom: 4px;
|
||||
--heading-2-padding-top: 28px;
|
||||
--heading-2-padding-bottom: 4px;
|
||||
--heading-3-padding-top: 28px;
|
||||
--heading-3-padding-bottom: 4px;
|
||||
--heading-4-padding-top: 28px;
|
||||
--heading-4-padding-bottom: 4px;
|
||||
--heading-5-padding-top: 20px;
|
||||
--heading-5-padding-bottom: 4px;
|
||||
--heading-6-padding-top: 20px;
|
||||
--heading-6-padding-bottom: 4px;
|
||||
--paragraph-padding-top: 4px;
|
||||
--paragraph-padding-bottom: 4px;
|
||||
--paragraph-padding-between: 8px;
|
||||
--list-spacing-y: 8px;
|
||||
--divider-padding-top: 4px;
|
||||
--divider-padding-bottom: 4px;
|
||||
}
|
||||
|
||||
&.line-spacing-small {
|
||||
--heading-1-padding-top: 16px;
|
||||
--heading-1-padding-bottom: 4px;
|
||||
--heading-2-padding-top: 16px;
|
||||
--heading-2-padding-bottom: 4px;
|
||||
--heading-3-padding-top: 16px;
|
||||
--heading-3-padding-bottom: 4px;
|
||||
--heading-4-padding-top: 16px;
|
||||
--heading-4-padding-bottom: 4px;
|
||||
--heading-5-padding-top: 12px;
|
||||
--heading-5-padding-bottom: 4px;
|
||||
--heading-6-padding-top: 12px;
|
||||
--heading-6-padding-bottom: 4px;
|
||||
--paragraph-padding-top: 2px;
|
||||
--paragraph-padding-bottom: 2px;
|
||||
--paragraph-padding-between: 4px;
|
||||
--list-spacing-y: 0px;
|
||||
--divider-padding-top: 0px;
|
||||
--divider-padding-bottom: 4px;
|
||||
}
|
||||
/* end spacing */
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ export const SUPPORTED_LANGUAGES: ILanguageOption[] = [
|
||||
{ label: "日本語", value: "ja" },
|
||||
{ label: "中文", value: "zh-CN" },
|
||||
{ label: "Русский", value: "ru" },
|
||||
{ label: "Italian", value: "it" },
|
||||
];
|
||||
|
||||
export const STORAGE_KEY = "userLanguage";
|
||||
|
||||
@@ -461,8 +461,6 @@
|
||||
"states": "States",
|
||||
"state": "State",
|
||||
"state_groups": "State groups",
|
||||
"state_group": "State group",
|
||||
"priorities": "Priorities",
|
||||
"priority": "Priority",
|
||||
"team_project": "Team project",
|
||||
"project": "Project",
|
||||
@@ -471,16 +469,12 @@
|
||||
"module": "Module",
|
||||
"modules": "Modules",
|
||||
"labels": "Labels",
|
||||
"label": "Label",
|
||||
"assignees": "Assignees",
|
||||
"assignee": "Assignee",
|
||||
"created_by": "Created by",
|
||||
"none": "None",
|
||||
"link": "Link",
|
||||
"estimates": "Estimates",
|
||||
"estimate": "Estimate",
|
||||
"created_at": "Created at",
|
||||
"completed_at": "Completed at",
|
||||
"layout": "Layout",
|
||||
"filters": "Filters",
|
||||
"display": "Display",
|
||||
@@ -523,6 +517,7 @@
|
||||
"add_more": "Add more",
|
||||
"defaults": "Defaults",
|
||||
"add_label": "Add label",
|
||||
"estimates": "Estimates",
|
||||
"customize_time_range": "Customize time range",
|
||||
"loading": "Loading",
|
||||
"attachments": "Attachments",
|
||||
@@ -661,6 +656,8 @@
|
||||
"select": "Select",
|
||||
"upgrade": "Upgrade",
|
||||
"add_seats": "Add Seats",
|
||||
"label": "Label",
|
||||
"priorities": "Priorities",
|
||||
"projects": "Projects",
|
||||
"workspace": "Workspace",
|
||||
"workspaces": "Workspaces",
|
||||
|
||||
@@ -633,8 +633,6 @@
|
||||
"states": "Estados",
|
||||
"state": "Estado",
|
||||
"state_groups": "Grupos de estados",
|
||||
"state_group": "Grupos de estado",
|
||||
"priorities": "Prioridades",
|
||||
"priority": "Prioridad",
|
||||
"team_project": "Proyecto de equipo",
|
||||
"project": "Proyecto",
|
||||
@@ -643,16 +641,12 @@
|
||||
"module": "Módulo",
|
||||
"modules": "Módulos",
|
||||
"labels": "Etiquetas",
|
||||
"label": "Etiqueta",
|
||||
"assignees": "Asignados",
|
||||
"assignee": "Asignado",
|
||||
"created_by": "Creado por",
|
||||
"none": "Ninguno",
|
||||
"link": "Enlace",
|
||||
"estimates": "Estimaciones",
|
||||
"estimate": "Estimación",
|
||||
"created_at": "Creado en",
|
||||
"completed_at": "Completado en",
|
||||
"layout": "Diseño",
|
||||
"filters": "Filtros",
|
||||
"display": "Mostrar",
|
||||
@@ -695,6 +689,7 @@
|
||||
"add_more": "Agregar más",
|
||||
"defaults": "Valores predeterminados",
|
||||
"add_label": "Agregar etiqueta",
|
||||
"estimates": "Estimaciones",
|
||||
"customize_time_range": "Personalizar rango de tiempo",
|
||||
"loading": "Cargando",
|
||||
"attachments": "Archivos adjuntos",
|
||||
@@ -832,6 +827,8 @@
|
||||
"select": "Seleccionar",
|
||||
"upgrade": "Mejorar",
|
||||
"add_seats": "Agregar asientos",
|
||||
"label": "Etiqueta",
|
||||
"priorities": "Prioridades",
|
||||
"projects": "Proyectos",
|
||||
"workspace": "Espacio de trabajo",
|
||||
"workspaces": "Espacios de trabajo",
|
||||
|
||||
@@ -631,8 +631,6 @@
|
||||
"states": "États",
|
||||
"state": "État",
|
||||
"state_groups": "Groupes d'états",
|
||||
"state_group": "Groupe d'état",
|
||||
"priorities": "Priorités",
|
||||
"priority": "Priorité",
|
||||
"team_project": "Projet d'équipe",
|
||||
"project": "Projet",
|
||||
@@ -641,16 +639,12 @@
|
||||
"module": "Module",
|
||||
"modules": "Modules",
|
||||
"labels": "Étiquettes",
|
||||
"label": "Étiquette",
|
||||
"assignees": "Assignés",
|
||||
"assignee": "Assigné",
|
||||
"created_by": "Créé par",
|
||||
"none": "Aucun",
|
||||
"link": "Lien",
|
||||
"estimates": "Estimations",
|
||||
"estimate": "Estimation",
|
||||
"created_at": "Créé le",
|
||||
"completed_at": "Terminé le",
|
||||
"layout": "Disposition",
|
||||
"filters": "Filtres",
|
||||
"display": "Affichage",
|
||||
@@ -693,6 +687,7 @@
|
||||
"add_more": "Ajouter plus",
|
||||
"defaults": "Par défaut",
|
||||
"add_label": "Ajouter une étiquette",
|
||||
"estimates": "Estimations",
|
||||
"customize_time_range": "Personnaliser la plage de temps",
|
||||
"loading": "Chargement",
|
||||
"attachments": "Pièces jointes",
|
||||
@@ -830,6 +825,8 @@
|
||||
"select": "Sélectionner",
|
||||
"upgrade": "Mettre à niveau",
|
||||
"add_seats": "Ajouter des sièges",
|
||||
"label": "Étiquette",
|
||||
"priorities": "Priorités",
|
||||
"projects": "Projets",
|
||||
"workspace": "Espace de travail",
|
||||
"workspaces": "Espaces de travail",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -631,8 +631,6 @@
|
||||
"states": "ステータス",
|
||||
"state": "ステータス",
|
||||
"state_groups": "ステータスグループ",
|
||||
"state_group": "ステート グループ",
|
||||
"priorities": "優先度",
|
||||
"priority": "優先度",
|
||||
"team_project": "チームプロジェクト",
|
||||
"project": "プロジェクト",
|
||||
@@ -641,16 +639,12 @@
|
||||
"module": "モジュール",
|
||||
"modules": "モジュール",
|
||||
"labels": "ラベル",
|
||||
"label": "ラベル",
|
||||
"assignees": "担当者",
|
||||
"assignee": "担当者",
|
||||
"created_by": "作成者",
|
||||
"none": "なし",
|
||||
"link": "リンク",
|
||||
"estimates": "見積もり",
|
||||
"estimate": "見積もり",
|
||||
"created_at": "クリエイテッド アット",
|
||||
"completed_at": "コンプリーテッド アット",
|
||||
"layout": "レイアウト",
|
||||
"filters": "フィルター",
|
||||
"display": "表示",
|
||||
@@ -693,6 +687,7 @@
|
||||
"add_more": "さらに追加",
|
||||
"defaults": "デフォルト",
|
||||
"add_label": "ラベルを追加",
|
||||
"estimates": "見積もり",
|
||||
"customize_time_range": "期間をカスタマイズ",
|
||||
"loading": "読み込み中",
|
||||
"attachments": "添付ファイル",
|
||||
@@ -830,6 +825,8 @@
|
||||
"select": "選択",
|
||||
"upgrade": "アップグレード",
|
||||
"add_seats": "シートを追加",
|
||||
"label": "ラベル",
|
||||
"priorities": "優先度",
|
||||
"projects": "プロジェクト",
|
||||
"workspace": "ワークスペース",
|
||||
"workspaces": "ワークスペース",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -631,8 +631,6 @@
|
||||
"states": "状态",
|
||||
"state": "状态",
|
||||
"state_groups": "状态组",
|
||||
"state_group": "状态组",
|
||||
"priorities": "优先级",
|
||||
"priority": "优先级",
|
||||
"team_project": "团队项目",
|
||||
"project": "项目",
|
||||
@@ -641,16 +639,12 @@
|
||||
"module": "模块",
|
||||
"modules": "模块",
|
||||
"labels": "标签",
|
||||
"label": "标签",
|
||||
"assignees": "负责人",
|
||||
"assignee": "负责人",
|
||||
"created_by": "创建者",
|
||||
"none": "无",
|
||||
"link": "链接",
|
||||
"estimates": "估算",
|
||||
"estimate": "估算",
|
||||
"created_at": "创建于",
|
||||
"completed_at": "完成于",
|
||||
"layout": "布局",
|
||||
"filters": "筛选",
|
||||
"display": "显示",
|
||||
@@ -693,6 +687,7 @@
|
||||
"add_more": "添加更多",
|
||||
"defaults": "默认值",
|
||||
"add_label": "添加标签",
|
||||
"estimates": "估算",
|
||||
"customize_time_range": "自定义时间范围",
|
||||
"loading": "加载中",
|
||||
"attachments": "附件",
|
||||
@@ -830,6 +825,8 @@
|
||||
"select": "选择",
|
||||
"upgrade": "升级",
|
||||
"add_seats": "添加席位",
|
||||
"label": "标签",
|
||||
"priorities": "优先级",
|
||||
"projects": "项目",
|
||||
"workspace": "工作区",
|
||||
"workspaces": "工作区",
|
||||
|
||||
@@ -149,8 +149,6 @@ export class TranslationStore {
|
||||
return import("../locales/zh-CN/translations.json");
|
||||
case "ru":
|
||||
return import("../locales/ru/translations.json");
|
||||
case "it":
|
||||
return import("../locales/it/translations.json");
|
||||
default:
|
||||
throw new Error(`Unsupported language: ${language}`);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type TLanguage = "en" | "fr" | "es" | "ja" | "zh-CN" | "ru" | "it";
|
||||
export type TLanguage = "en" | "fr" | "es" | "ja" | "zh-CN" | "ru";
|
||||
|
||||
export interface ILanguageOption {
|
||||
label: string;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
nginx.conf.template
|
||||
@@ -1,39 +0,0 @@
|
||||
(plane_proxy) {
|
||||
request_body {
|
||||
max_size {$FILE_SIZE_LIMIT}
|
||||
}
|
||||
|
||||
reverse_proxy /spaces/* space:3000
|
||||
reverse_proxy /god-mode/* admin:3000
|
||||
reverse_proxy /api/* api:8000
|
||||
reverse_proxy /auth/* api:8000
|
||||
reverse_proxy /live/* live:3000
|
||||
reverse_proxy /{$BUCKET_NAME} plane-minio:9000
|
||||
reverse_proxy /{$BUCKET_NAME}/* plane-minio:9000
|
||||
reverse_proxy /* web:3000
|
||||
}
|
||||
|
||||
{
|
||||
email {$CERT_EMAIL:admin@example.com}
|
||||
acme_ca {$CERT_ACME_CA}
|
||||
{$CERT_ACME_DNS}
|
||||
servers {
|
||||
timeouts {
|
||||
read_body 600s
|
||||
read_header 30s
|
||||
write 600s
|
||||
idle 600s
|
||||
}
|
||||
max_header_size 25MB
|
||||
client_ip_headers X-Forwarded-For X-Real-IP
|
||||
trusted_proxies static {$TRUSTED_PROXIES:0.0.0.0/0}
|
||||
}
|
||||
log {
|
||||
output {$LOG_OUTPUT:stdout}
|
||||
level {$LOG_LEVEL:INFO}
|
||||
}
|
||||
}
|
||||
|
||||
{$SITE_ADDRESS} {
|
||||
import plane_proxy
|
||||
}
|
||||
@@ -108,7 +108,7 @@ export const ExtendedProjectSidebar = observer(() => {
|
||||
<div
|
||||
ref={extendedProjectSidebarRef}
|
||||
className={cn(
|
||||
"absolute top-0 h-full z-[19] flex flex-col gap-2 w-[300px] transform transition-all duration-300 ease-in-out bg-custom-sidebar-background-100 border-r border-custom-sidebar-border-200 shadow-md",
|
||||
"fixed top-0 h-full z-[19] flex flex-col gap-2 w-[300px] transform transition-all duration-300 ease-in-out bg-custom-sidebar-background-100 border-r border-custom-sidebar-border-200 shadow-md",
|
||||
{
|
||||
"translate-x-0 opacity-100 pointer-events-auto": extendedProjectSidebarCollapsed,
|
||||
"-translate-x-full opacity-0 pointer-events-none": !extendedProjectSidebarCollapsed,
|
||||
|
||||
@@ -104,7 +104,7 @@ export const ExtendedAppSidebar = observer(() => {
|
||||
<div
|
||||
ref={extendedSidebarRef}
|
||||
className={cn(
|
||||
"absolute top-0 h-full z-[19] flex flex-col gap-0.5 w-[300px] transform transition-all duration-300 ease-in-out bg-custom-sidebar-background-100 border-r border-custom-sidebar-border-200 p-4 shadow-md pb-6",
|
||||
"fixed top-0 h-full z-[19] flex flex-col gap-0.5 w-[300px] transform transition-all duration-300 ease-in-out bg-custom-sidebar-background-100 border-r border-custom-sidebar-border-200 p-4 shadow-md pb-6",
|
||||
{
|
||||
"translate-x-0 opacity-100 pointer-events-auto": extendedSidebarCollapsed,
|
||||
"-translate-x-full opacity-0 pointer-events-none": !extendedSidebarCollapsed,
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { ArchiveIcon, ArchiveRestoreIcon, ChevronRight, EllipsisIcon, LinkIcon, Trash2 } from "lucide-react";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ArchiveRestoreIcon,
|
||||
CalendarCheck2,
|
||||
CalendarClock,
|
||||
ChevronRight,
|
||||
EllipsisIcon,
|
||||
LinkIcon,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
// types
|
||||
import { CYCLE_STATUS, CYCLE_UPDATED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
@@ -11,7 +20,7 @@ import { ICycle } from "@plane/types";
|
||||
// ui
|
||||
import { CustomMenu, setToast, TOAST_TYPE } from "@plane/ui";
|
||||
// components
|
||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
import { DateDropdown } from "@/components/dropdowns";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper";
|
||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||
@@ -54,7 +63,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// form info
|
||||
const { control, reset } = useForm({
|
||||
const { control, reset, getValues } = useForm({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
@@ -101,10 +110,10 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const submitChanges = async (data: Partial<ICycle>, changedProperty: string) => {
|
||||
const submitChanges = (data: Partial<ICycle>, changedProperty: string) => {
|
||||
if (!workspaceSlug || !projectId || !cycleDetails.id) return;
|
||||
|
||||
await updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleDetails.id.toString(), data)
|
||||
updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleDetails.id.toString(), data)
|
||||
.then((res) => {
|
||||
captureCycleEvent({
|
||||
eventName: CYCLE_UPDATED,
|
||||
@@ -145,22 +154,16 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateChange = async (startDate: Date | undefined, endDate: Date | undefined) => {
|
||||
const handleDateChange = async (payload: { start_date?: string | null; end_date?: string | null }) => {
|
||||
let isDateValid = false;
|
||||
|
||||
const payload = {
|
||||
start_date: renderFormattedPayloadDate(startDate) || null,
|
||||
end_date: renderFormattedPayloadDate(endDate) || null,
|
||||
};
|
||||
|
||||
if (payload?.start_date && payload.end_date) {
|
||||
if (cycleDetails?.start_date && cycleDetails?.end_date)
|
||||
isDateValid = await dateChecker({
|
||||
...payload,
|
||||
cycle_id: cycleDetails.id,
|
||||
cycle_id: cycleDetails?.id,
|
||||
});
|
||||
} else {
|
||||
isDateValid = true;
|
||||
}
|
||||
else isDateValid = await dateChecker(payload);
|
||||
|
||||
if (isDateValid) {
|
||||
submitChanges(payload, "date_range");
|
||||
setToast({
|
||||
@@ -174,6 +177,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
title: t("project_cycles.action.update.failed.title"),
|
||||
message: t("project_cycles.action.update.error.already_exists"),
|
||||
});
|
||||
reset({ ...cycleDetails });
|
||||
}
|
||||
return isDateValid;
|
||||
};
|
||||
@@ -284,41 +288,79 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Controller
|
||||
name="start_date"
|
||||
control={control}
|
||||
rules={{ required: "Please select a date" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DateDropdown
|
||||
value={value ?? null}
|
||||
onChange={async (val) => {
|
||||
let isDateValid;
|
||||
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||
if (getValues("end_date")) {
|
||||
isDateValid = await handleDateChange({
|
||||
start_date: valDate,
|
||||
end_date: renderFormattedPayloadDate(getValues("end_date")),
|
||||
});
|
||||
} else {
|
||||
isDateValid = await handleDateChange({
|
||||
start_date: valDate,
|
||||
end_date: valDate,
|
||||
});
|
||||
}
|
||||
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||
}}
|
||||
placeholder={t("common.order_by.start_date")}
|
||||
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived || isCompleted ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={!isEditingAllowed || isArchived || isCompleted}
|
||||
showTooltip
|
||||
maxDate={getDate(getValues("end_date"))}
|
||||
isClearable={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="end_date"
|
||||
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
||||
<DateRangeDropdown
|
||||
className="h-7"
|
||||
buttonVariant="border-with-text"
|
||||
minDate={new Date()}
|
||||
value={{
|
||||
from: getDate(startDateValue),
|
||||
to: getDate(endDateValue),
|
||||
}}
|
||||
onSelect={async (val) => {
|
||||
const isDateValid = await handleDateChange(val?.from, val?.to);
|
||||
if (isDateValid) {
|
||||
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
||||
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
||||
}
|
||||
}}
|
||||
placeholder={{
|
||||
from: "Start date",
|
||||
to: "End date",
|
||||
}}
|
||||
required={cycleDetails.status !== "draft"}
|
||||
disabled={!isEditingAllowed || isArchived || isCompleted}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="end_date"
|
||||
control={control}
|
||||
rules={{ required: "Please select a date" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DateDropdown
|
||||
value={getDate(value) ?? null}
|
||||
onChange={async (val) => {
|
||||
let isDateValid;
|
||||
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||
if (getValues("start_date")) {
|
||||
isDateValid = await handleDateChange({
|
||||
end_date: valDate,
|
||||
start_date: renderFormattedPayloadDate(getValues("start_date")),
|
||||
});
|
||||
} else {
|
||||
isDateValid = await handleDateChange({
|
||||
end_date: valDate,
|
||||
start_date: valDate,
|
||||
});
|
||||
}
|
||||
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||
}}
|
||||
placeholder={t("common.order_by.due_date")}
|
||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived || isCompleted ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={!isEditingAllowed || isArchived || isCompleted}
|
||||
showTooltip
|
||||
minDate={getDate(getValues("start_date"))}
|
||||
isClearable={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,21 +3,31 @@
|
||||
import React, { FC, MouseEvent, useEffect, useMemo, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Eye, Users } from "lucide-react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { CalendarCheck2, CalendarClock, Eye, Users } from "lucide-react";
|
||||
// types
|
||||
import { CYCLE_FAVORITED, CYCLE_UNFAVORITED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ICycle, TCycleGroups } from "@plane/types";
|
||||
// ui
|
||||
import { Avatar, AvatarGroup, FavoriteStar, LayersIcon, Tooltip, TransferIcon, setPromiseToast } from "@plane/ui";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarGroup,
|
||||
FavoriteStar,
|
||||
LayersIcon,
|
||||
TOAST_TYPE,
|
||||
Tooltip,
|
||||
TransferIcon,
|
||||
setPromiseToast,
|
||||
setToast,
|
||||
} from "@plane/ui";
|
||||
// components
|
||||
import { CycleQuickActions, TransferIssuesModal } from "@/components/cycles";
|
||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
import { DateDropdown } from "@/components/dropdowns";
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
// constants
|
||||
// helpers
|
||||
import { getDate } from "@/helpers/date-time.helper";
|
||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { generateQueryParams } from "@/helpers/router.helper";
|
||||
@@ -26,7 +36,11 @@ import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane web components
|
||||
import { CycleAdditionalActions } from "@/plane-web/components/cycles";
|
||||
// plane web constants
|
||||
// services
|
||||
import { CycleService } from "@/services/cycle.service";
|
||||
|
||||
const cycleService = new CycleService();
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
@@ -141,6 +155,48 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const submitChanges = (data: Partial<ICycle>) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
updateCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), data);
|
||||
};
|
||||
|
||||
const dateChecker = async (payload: any) => {
|
||||
try {
|
||||
const res = await cycleService.cycleDateCheck(workspaceSlug as string, projectId as string, payload);
|
||||
return res.status;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateChange = async (payload: { start_date?: string | null; end_date?: string | null }) => {
|
||||
let isDateValid = false;
|
||||
|
||||
if (cycleDetails?.start_date && cycleDetails?.end_date)
|
||||
isDateValid = await dateChecker({
|
||||
...payload,
|
||||
cycle_id: cycleDetails?.id,
|
||||
});
|
||||
else isDateValid = await dateChecker(payload);
|
||||
|
||||
if (isDateValid) {
|
||||
submitChanges(payload);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: t("project_cycles.action.update.success.title"),
|
||||
message: t("project_cycles.action.update.success.description"),
|
||||
});
|
||||
} else {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("project_cycles.action.update.failed.title"),
|
||||
message: t("project_cycles.action.update.error.already_exists"),
|
||||
});
|
||||
reset({ ...cycleDetails });
|
||||
}
|
||||
return isDateValid;
|
||||
};
|
||||
|
||||
const createdByDetails = cycleDetails.created_by ? getUserDetails(cycleDetails.created_by) : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
@@ -150,6 +206,10 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
});
|
||||
}, [cycleDetails, reset]);
|
||||
|
||||
const isArchived = Boolean(cycleDetails.archived_at);
|
||||
const isCompleted = cycleStatus === "completed";
|
||||
|
||||
const isDisabled = !isEditingAllowed || isArchived || isCompleted;
|
||||
// handlers
|
||||
const openCycleOverview = (e: MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {
|
||||
e.preventDefault();
|
||||
@@ -198,27 +258,81 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isActive && cycleDetails.start_date && (
|
||||
<DateRangeDropdown
|
||||
buttonVariant={"transparent-with-text"}
|
||||
buttonContainerClassName={`h-6 w-full cursor-auto flex items-center gap-1.5 text-custom-text-300 rounded text-xs [&>div]:hover:bg-transparent`}
|
||||
buttonClassName="p-0"
|
||||
minDate={new Date()}
|
||||
value={{
|
||||
from: getDate(cycleDetails.start_date),
|
||||
to: getDate(cycleDetails.end_date),
|
||||
}}
|
||||
placeholder={{
|
||||
from: "Start date",
|
||||
to: "End date",
|
||||
}}
|
||||
showTooltip
|
||||
required={cycleDetails.status !== "draft"}
|
||||
disabled
|
||||
hideIcon={{
|
||||
from: false,
|
||||
to: false,
|
||||
}}
|
||||
{!isActive && (
|
||||
<Controller
|
||||
name="start_date"
|
||||
control={control}
|
||||
rules={{ required: "Please select a date" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DateDropdown
|
||||
value={value ?? null}
|
||||
onChange={async (val) => {
|
||||
let isDateValid;
|
||||
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||
if (getValues("end_date")) {
|
||||
isDateValid = await handleDateChange({
|
||||
start_date: valDate,
|
||||
end_date: renderFormattedPayloadDate(getValues("end_date")),
|
||||
});
|
||||
} else {
|
||||
isDateValid = await handleDateChange({
|
||||
start_date: valDate,
|
||||
end_date: valDate,
|
||||
});
|
||||
}
|
||||
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||
}}
|
||||
placeholder={t("common.order_by.start_date")}
|
||||
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={isDisabled}
|
||||
renderByDefault={isMobile}
|
||||
showTooltip
|
||||
maxDate={getDate(getValues("end_date"))}
|
||||
isClearable={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isActive && (
|
||||
<Controller
|
||||
name="end_date"
|
||||
control={control}
|
||||
rules={{ required: "Please select a date" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DateDropdown
|
||||
value={getDate(value) ?? null}
|
||||
onChange={async (val) => {
|
||||
let isDateValid;
|
||||
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||
if (getValues("start_date")) {
|
||||
isDateValid = await handleDateChange({
|
||||
end_date: valDate,
|
||||
start_date: renderFormattedPayloadDate(getValues("start_date")),
|
||||
});
|
||||
} else {
|
||||
isDateValid = await handleDateChange({
|
||||
end_date: valDate,
|
||||
start_date: valDate,
|
||||
});
|
||||
}
|
||||
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||
}}
|
||||
placeholder={t("common.order_by.due_date")}
|
||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={isDisabled}
|
||||
renderByDefault={isMobile}
|
||||
showTooltip
|
||||
minDate={getDate(getValues("start_date"))}
|
||||
isClearable={false}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ type Props = {
|
||||
};
|
||||
minDate?: Date;
|
||||
maxDate?: Date;
|
||||
onSelect?: (range: DateRange | undefined) => void;
|
||||
onSelect: (range: DateRange | undefined) => void;
|
||||
placeholder?: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
@@ -204,7 +204,11 @@ export const DateRangeDropdown: React.FC<Props> = (props) => {
|
||||
classNames={{ root: `p-3 rounded-md` }}
|
||||
selected={dateRange}
|
||||
onSelect={(val) => {
|
||||
onSelect?.(val);
|
||||
onSelect(val);
|
||||
setDateRange({
|
||||
from: val?.from ?? undefined,
|
||||
to: val?.to ?? undefined,
|
||||
});
|
||||
}}
|
||||
mode="range"
|
||||
disabled={disabledDays}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useParams } from "next/navigation";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
ArchiveRestoreIcon,
|
||||
CalendarCheck2,
|
||||
CalendarClock,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
@@ -42,7 +43,7 @@ import {
|
||||
TextArea,
|
||||
} from "@plane/ui";
|
||||
// components
|
||||
import { DateRangeDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||
import { DateDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||
import {
|
||||
ArchiveModuleModal,
|
||||
DeleteModuleModal,
|
||||
@@ -104,7 +105,7 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const estimateType = areEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId);
|
||||
const isEstimatePointValid = estimateType && estimateType?.type == EEstimateSystem.POINTS ? true : false;
|
||||
|
||||
const { reset, control } = useForm({
|
||||
const { reset, control, getValues } = useForm({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
@@ -194,11 +195,8 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDateChange = async (startDate: Date | undefined, targetDate: Date | undefined) => {
|
||||
submitChanges({
|
||||
start_date: startDate ? renderFormattedPayloadDate(startDate) : null,
|
||||
target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null,
|
||||
});
|
||||
const handleDateChange = async (data: Partial<IModule>) => {
|
||||
submitChanges(data);
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
@@ -415,40 +413,59 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
<div className="flex items-center justify-start gap-1">
|
||||
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
||||
<CalendarClock className="h-4 w-4" />
|
||||
<span className="text-base">{t("date_range")}</span>
|
||||
<span className="text-base">Start date</span>
|
||||
</div>
|
||||
<div className="h-7">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => {
|
||||
const startDate = getDate(startDateValue);
|
||||
const endDate = getDate(endDateValue);
|
||||
return (
|
||||
<DateRangeDropdown
|
||||
buttonContainerClassName="w-full"
|
||||
buttonVariant="background-with-text"
|
||||
value={{
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
}}
|
||||
onSelect={(val) => {
|
||||
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
||||
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
||||
handleDateChange(val?.from, val?.to);
|
||||
}}
|
||||
placeholder={{
|
||||
from: t("start_date"),
|
||||
to: t("end_date"),
|
||||
}}
|
||||
disabled={!isEditingAllowed || isArchived}
|
||||
/>
|
||||
);
|
||||
control={control}
|
||||
rules={{ required: "Please select a date" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DateDropdown
|
||||
value={value ?? null}
|
||||
onChange={(val) => {
|
||||
console.log(val);
|
||||
onChange(renderFormattedPayloadDate(val));
|
||||
handleDateChange({ start_date: val ? renderFormattedPayloadDate(val) : null });
|
||||
}}
|
||||
placeholder={t("common.order_by.start_date")}
|
||||
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-30"
|
||||
disabled={!isEditingAllowed || isArchived}
|
||||
showTooltip
|
||||
maxDate={getDate(getValues("target_date"))}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-start gap-1">
|
||||
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
||||
<CalendarClock className="h-4 w-4" />
|
||||
<span className="text-base">Due date</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Controller
|
||||
name="target_date"
|
||||
control={control}
|
||||
rules={{ required: "Please select a date" }}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<DateDropdown
|
||||
value={getDate(value) ?? null}
|
||||
onChange={(val) => {
|
||||
onChange(renderFormattedPayloadDate(val));
|
||||
handleDateChange({ target_date: val ? renderFormattedPayloadDate(val) : null });
|
||||
}}
|
||||
placeholder={t("common.order_by.due_date")}
|
||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-30"
|
||||
disabled={!isEditingAllowed || isArchived}
|
||||
showTooltip
|
||||
minDate={getDate(getValues("start_date"))}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { SyntheticEvent, useRef } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||
import { Info, SquareUser } from "lucide-react";
|
||||
import { CalendarCheck2, CalendarClock, Info, SquareUser } from "lucide-react";
|
||||
// plane package imports
|
||||
import {
|
||||
MODULE_STATUS,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
EUserPermissions,
|
||||
EUserPermissionsLevel,
|
||||
} from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IModule } from "@plane/types";
|
||||
import {
|
||||
Card,
|
||||
@@ -26,7 +27,7 @@ import {
|
||||
setToast,
|
||||
} from "@plane/ui";
|
||||
// components
|
||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
import { DateDropdown } from "@/components/dropdowns";
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
import { ModuleQuickActions } from "@/components/modules";
|
||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||
@@ -58,7 +59,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||
const { getUserDetails } = useMember();
|
||||
const { captureEvent } = useEventTracker();
|
||||
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const moduleDetails = getModuleById(moduleId);
|
||||
const isEditingAllowed = allowPermissions(
|
||||
@@ -66,7 +67,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
EUserPermissionsLevel.PROJECT
|
||||
);
|
||||
const isDisabled = !isEditingAllowed || !!moduleDetails?.archived_at;
|
||||
const renderIcon = Boolean(moduleDetails?.start_date) || Boolean(moduleDetails?.target_date);
|
||||
|
||||
const { isMobile } = usePlatformOS();
|
||||
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
@@ -236,27 +236,38 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
</div>
|
||||
<LinearProgressIndicator size="lg" data={progressIndicatorData} />
|
||||
<div className="flex items-center justify-between py-0.5" onClick={handleEventPropagation}>
|
||||
<DateRangeDropdown
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 border-[0.5px] border-custom-border-300 rounded text-xs`}
|
||||
buttonVariant="transparent-with-text"
|
||||
className="h-7"
|
||||
value={{
|
||||
from: getDate(moduleDetails.start_date),
|
||||
to: getDate(moduleDetails.target_date),
|
||||
}}
|
||||
onSelect={(val) => {
|
||||
<div className="flex items-center gap-2 py-0.5" onClick={handleEventPropagation}>
|
||||
<DateDropdown
|
||||
value={moduleDetails.start_date}
|
||||
onChange={(val) => {
|
||||
handleModuleDetailsChange({
|
||||
start_date: val?.from ? renderFormattedPayloadDate(val.from) : null,
|
||||
target_date: val?.to ? renderFormattedPayloadDate(val.to) : null,
|
||||
start_date: val ? renderFormattedPayloadDate(val) : null,
|
||||
});
|
||||
}}
|
||||
placeholder={{
|
||||
from: "Start date",
|
||||
to: "End date",
|
||||
}}
|
||||
placeholder={t("common.order_by.start_date")}
|
||||
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={moduleDetails.start_date ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={isDisabled}
|
||||
hideIcon={{ from: renderIcon ?? true, to: renderIcon }}
|
||||
showTooltip
|
||||
maxDate={getDate(moduleDetails.target_date)}
|
||||
/>
|
||||
<DateDropdown
|
||||
value={moduleDetails.target_date}
|
||||
onChange={(val) => {
|
||||
handleModuleDetailsChange({
|
||||
target_date: val ? renderFormattedPayloadDate(val) : null,
|
||||
});
|
||||
}}
|
||||
placeholder={t("common.order_by.due_date")}
|
||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={moduleDetails.target_date ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={isDisabled}
|
||||
showTooltip
|
||||
minDate={getDate(moduleDetails.start_date)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { SquareUser } from "lucide-react";
|
||||
import { CalendarCheck2, CalendarClock, SquareUser } from "lucide-react";
|
||||
// types
|
||||
import {
|
||||
MODULE_STATUS,
|
||||
@@ -18,7 +18,7 @@ import { IModule } from "@plane/types";
|
||||
// ui
|
||||
import { FavoriteStar, TOAST_TYPE, Tooltip, setPromiseToast, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||
import { DateDropdown } from "@/components/dropdowns";
|
||||
import { ModuleQuickActions } from "@/components/modules";
|
||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||
// constants
|
||||
@@ -138,28 +138,38 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DateRangeDropdown
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 border-[0.5px] border-custom-border-300 rounded text-xs`}
|
||||
buttonVariant="transparent-with-text"
|
||||
className="h-7"
|
||||
value={{
|
||||
from: getDate(moduleDetails.start_date),
|
||||
to: getDate(moduleDetails.target_date),
|
||||
}}
|
||||
onSelect={(val) => {
|
||||
<DateDropdown
|
||||
value={moduleDetails.start_date}
|
||||
onChange={(val) => {
|
||||
handleModuleDetailsChange({
|
||||
start_date: val?.from ? renderFormattedPayloadDate(val.from) : null,
|
||||
target_date: val?.to ? renderFormattedPayloadDate(val.to) : null,
|
||||
start_date: val ? renderFormattedPayloadDate(val) : null,
|
||||
});
|
||||
}}
|
||||
placeholder={{
|
||||
from: t("start_date"),
|
||||
to: t("end_date"),
|
||||
}}
|
||||
placeholder={t("common.order_by.start_date")}
|
||||
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={moduleDetails.start_date ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={isDisabled}
|
||||
hideIcon={{ from: renderIcon ?? true, to: renderIcon }}
|
||||
showTooltip
|
||||
maxDate={getDate(moduleDetails.target_date)}
|
||||
/>
|
||||
<DateDropdown
|
||||
value={moduleDetails.target_date}
|
||||
onChange={(val) => {
|
||||
handleModuleDetailsChange({
|
||||
target_date: val ? renderFormattedPayloadDate(val) : null,
|
||||
});
|
||||
}}
|
||||
placeholder={t("common.order_by.due_date")}
|
||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||
buttonVariant={moduleDetails.target_date ? "border-with-text" : "border-without-text"}
|
||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||
optionsClassName="z-10"
|
||||
disabled={isDisabled}
|
||||
showTooltip
|
||||
minDate={getDate(moduleDetails.start_date)}
|
||||
/>
|
||||
|
||||
{moduleStatus && (
|
||||
<ModuleStatusDropdown
|
||||
isDisabled={isDisabled}
|
||||
|
||||
Reference in New Issue
Block a user