diff --git a/packages/constants/src/module.ts b/packages/constants/src/module.ts index 6ce30f0dc..16a332303 100644 --- a/packages/constants/src/module.ts +++ b/packages/constants/src/module.ts @@ -1,9 +1,16 @@ // types -import { - TModuleLayoutOptions, - TModuleOrderByOptions, - TModuleStatus, -} from "@plane/types"; +import { TModuleLayoutOptions, TModuleOrderByOptions, TModuleStatus } from "@plane/types"; + +export const MODULE_STATUS_COLORS: { + [key in TModuleStatus]: string; +} = { + backlog: "#a3a3a2", + planned: "#3f76ff", + paused: "#525252", + completed: "#16a34a", + cancelled: "#ef4444", + "in-progress": "#f39e1f", +}; export const MODULE_STATUS: { i18n_label: string; @@ -15,42 +22,42 @@ export const MODULE_STATUS: { { i18n_label: "project_modules.status.backlog", value: "backlog", - color: "#a3a3a2", + color: MODULE_STATUS_COLORS.backlog, textColor: "text-custom-text-400", bgColor: "bg-custom-background-80", }, { i18n_label: "project_modules.status.planned", value: "planned", - color: "#3f76ff", + color: MODULE_STATUS_COLORS.planned, textColor: "text-blue-500", bgColor: "bg-indigo-50", }, { i18n_label: "project_modules.status.in_progress", value: "in-progress", - color: "#f39e1f", + color: MODULE_STATUS_COLORS["in-progress"], textColor: "text-amber-500", bgColor: "bg-amber-50", }, { i18n_label: "project_modules.status.paused", value: "paused", - color: "#525252", + color: MODULE_STATUS_COLORS.paused, textColor: "text-custom-text-300", bgColor: "bg-custom-background-90", }, { i18n_label: "project_modules.status.completed", value: "completed", - color: "#16a34a", + color: MODULE_STATUS_COLORS.completed, textColor: "text-green-600", bgColor: "bg-green-100", }, { i18n_label: "project_modules.status.cancelled", value: "cancelled", - color: "#ef4444", + color: MODULE_STATUS_COLORS.cancelled, textColor: "text-red-500", bgColor: "bg-red-50", }, diff --git a/packages/i18n/src/locales/cs/translations.json b/packages/i18n/src/locales/cs/translations.json index b2ac82a65..599765916 100644 --- a/packages/i18n/src/locales/cs/translations.json +++ b/packages/i18n/src/locales/cs/translations.json @@ -872,13 +872,15 @@ "guests": "Hosté", "on_track": "Na správné cestě", "off_track": "Mimo plán", + "at_risk": "V ohrožení", "timeline": "Časová osa", "completion": "Dokončení", "upcoming": "Nadcházející", "completed": "Dokončeno", "in_progress": "Probíhá", "planned": "Plánováno", - "paused": "Pozastaveno" + "paused": "Pozastaveno", + "no_of": "Počet {entity}" }, "chart": { "x_axis": "Osa X", @@ -2467,4 +2469,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane se nespustil. To může být způsobeno tím, že se jeden nebo více služeb Plane nepodařilo spustit.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Vyberte View Logs z setup.sh a Docker logů, abyste si byli jisti." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/de/translations.json b/packages/i18n/src/locales/de/translations.json index b895435ae..1cac1d99f 100644 --- a/packages/i18n/src/locales/de/translations.json +++ b/packages/i18n/src/locales/de/translations.json @@ -872,13 +872,15 @@ "guests": "Gäste", "on_track": "Im Plan", "off_track": "Außer Plan", + "at_risk": "Gefährdet", "timeline": "Zeitleiste", "completion": "Fertigstellung", "upcoming": "Bevorstehend", "completed": "Abgeschlossen", "in_progress": "In Bearbeitung", "planned": "Geplant", - "paused": "Pausiert" + "paused": "Pausiert", + "no_of": "Anzahl {entity}" }, "chart": { "x_axis": "X-Achse", diff --git a/packages/i18n/src/locales/en/translations.json b/packages/i18n/src/locales/en/translations.json index aaf83df2f..654ce3cc6 100644 --- a/packages/i18n/src/locales/en/translations.json +++ b/packages/i18n/src/locales/en/translations.json @@ -617,6 +617,7 @@ "click_to_add_description": "Click to add description", "on_track": "On-Track", "off_track": "Off-Track", + "at_risk": "At risk", "timeline": "Timeline", "completion": "Completion", "upcoming": "Upcoming", @@ -721,7 +722,8 @@ "deactivated_user": "Deactivated user", "apply": "Apply", "applying": "Applying", - "overview": "Overview" + "overview": "Overview", + "no_of": "No. of {entity}" }, "chart": { "x_axis": "X-axis", @@ -2343,4 +2345,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane didn't start up. This could be because one or more Plane services failed to start.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Choose View Logs from setup.sh and Docker logs to be sure." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/es/translations.json b/packages/i18n/src/locales/es/translations.json index 31c8f75e3..97f0792f2 100644 --- a/packages/i18n/src/locales/es/translations.json +++ b/packages/i18n/src/locales/es/translations.json @@ -875,13 +875,15 @@ "guests": "Invitados", "on_track": "En camino", "off_track": "Fuera de camino", + "at_risk": "En riesgo", "timeline": "Cronograma", "completion": "Finalización", "upcoming": "Próximo", "completed": "Completado", "in_progress": "En progreso", "planned": "Planificado", - "paused": "Pausado" + "paused": "Pausado", + "no_of": "N.º de {entity}" }, "chart": { "x_axis": "Eje X", @@ -2469,4 +2471,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane no se inició. Esto podría deberse a que uno o más servicios de Plane fallaron al iniciar.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Selecciona View Logs desde setup.sh y los logs de Docker para estar seguro." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/fr/translations.json b/packages/i18n/src/locales/fr/translations.json index afa6f1844..9fce5a002 100644 --- a/packages/i18n/src/locales/fr/translations.json +++ b/packages/i18n/src/locales/fr/translations.json @@ -873,13 +873,15 @@ "guests": "Invités", "on_track": "Sur la bonne voie", "off_track": "Hors de la bonne voie", + "at_risk": "À risque", "timeline": "Chronologie", "completion": "Achèvement", "upcoming": "À venir", "completed": "Terminé", "in_progress": "En cours", "planned": "Planifié", - "paused": "En pause" + "paused": "En pause", + "no_of": "Nº de {entity}" }, "chart": { "x_axis": "Axe X", @@ -2467,4 +2469,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane n'a pas démarré. Cela pourrait être dû au fait qu'un ou plusieurs services Plane ont échoué à démarrer.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Choisissez View Logs depuis setup.sh et les logs Docker pour en être sûr." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/id/translations.json b/packages/i18n/src/locales/id/translations.json index dc59bdaf1..87c4a952f 100644 --- a/packages/i18n/src/locales/id/translations.json +++ b/packages/i18n/src/locales/id/translations.json @@ -872,13 +872,15 @@ "guests": "Tamu", "on_track": "Sesuai Jalur", "off_track": "Menyimpang", + "at_risk": "Dalam risiko", "timeline": "Linimasa", "completion": "Penyelesaian", "upcoming": "Mendatang", "completed": "Selesai", "in_progress": "Sedang berlangsung", "planned": "Direncanakan", - "paused": "Dijedaikan" + "paused": "Dijedaikan", + "no_of": "Jumlah {entity}" }, "chart": { "x_axis": "Sumbu-X", @@ -2460,5 +2462,6 @@ "self_hosted_maintenance_message": { "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane tidak berhasil dimulai. Ini bisa karena satu atau lebih layanan Plane gagal untuk dimulai.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Pilih View Logs dari setup.sh dan log Docker untuk memastikan." - } -} + }, + "no_of": "Jumlah {entity}" +} \ No newline at end of file diff --git a/packages/i18n/src/locales/it/translations.json b/packages/i18n/src/locales/it/translations.json index 03a1160bf..75df8e9b5 100644 --- a/packages/i18n/src/locales/it/translations.json +++ b/packages/i18n/src/locales/it/translations.json @@ -871,13 +871,15 @@ "guests": "Ospiti", "on_track": "In linea", "off_track": "Fuori rotta", + "at_risk": "A rischio", "timeline": "Cronologia", "completion": "Completamento", "upcoming": "In arrivo", "completed": "Completato", "in_progress": "In corso", "planned": "Pianificato", - "paused": "In pausa" + "paused": "In pausa", + "no_of": "N. di {entity}" }, "chart": { "x_axis": "Asse X", @@ -2466,4 +2468,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane non si è avviato. Questo potrebbe essere dovuto al fatto che uno o più servizi Plane non sono riusciti ad avviarsi.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Scegli View Logs da setup.sh e dai log Docker per essere sicuro." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/ja/translations.json b/packages/i18n/src/locales/ja/translations.json index 0c081974e..e7610eac3 100644 --- a/packages/i18n/src/locales/ja/translations.json +++ b/packages/i18n/src/locales/ja/translations.json @@ -873,13 +873,15 @@ "guests": "ゲスト", "on_track": "順調", "off_track": "遅れ", + "at_risk": "リスクあり", "timeline": "タイムライン", "completion": "完了", "upcoming": "今後の予定", "completed": "完了", "in_progress": "進行中", "planned": "計画済み", - "paused": "一時停止" + "paused": "一時停止", + "no_of": "{entity} の数" }, "chart": { "x_axis": "エックス アクシス", @@ -2467,4 +2469,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Planeが起動しませんでした。これは1つまたは複数のPlaneサービスの起動に失敗したことが原因である可能性があります。", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "setup.shとDockerログからView Logsを選択して確認してください。" } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/ko/translations.json b/packages/i18n/src/locales/ko/translations.json index 66be56308..6100f3fd6 100644 --- a/packages/i18n/src/locales/ko/translations.json +++ b/packages/i18n/src/locales/ko/translations.json @@ -874,13 +874,15 @@ "guests": "게스트", "on_track": "계획대로 진행 중", "off_track": "계획 이탈", + "at_risk": "위험", "timeline": "타임라인", "completion": "완료", "upcoming": "예정된", "completed": "완료됨", "in_progress": "진행 중", "planned": "계획된", - "paused": "일시 중지됨" + "paused": "일시 중지됨", + "no_of": "{entity} 수" }, "chart": { "x_axis": "X축", @@ -2469,4 +2471,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane이 시작되지 않았습니다. 이는 하나 이상의 Plane 서비스가 시작에 실패했기 때문일 수 있습니다.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "확실히 하려면 setup.sh와 Docker 로그에서 View Logs를 선택하세요." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/pl/translations.json b/packages/i18n/src/locales/pl/translations.json index fe74d5c4f..7cd8ba385 100644 --- a/packages/i18n/src/locales/pl/translations.json +++ b/packages/i18n/src/locales/pl/translations.json @@ -874,13 +874,15 @@ "guests": "Goście", "on_track": "Na dobrej drodze", "off_track": "Poza planem", + "at_risk": "W zagrożeniu", "timeline": "Oś czasu", "completion": "Zakończenie", "upcoming": "Nadchodzące", "completed": "Zakończone", "in_progress": "W trakcie", "planned": "Zaplanowane", - "paused": "Wstrzymane" + "paused": "Wstrzymane", + "no_of": "Liczba {entity}" }, "chart": { "x_axis": "Oś X", @@ -2468,4 +2470,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane nie uruchomił się. Może to być spowodowane tym, że jedna lub więcej usług Plane nie mogła się uruchomić.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Wybierz View Logs z setup.sh i logów Docker, aby mieć pewność." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/pt-BR/translations.json b/packages/i18n/src/locales/pt-BR/translations.json index bb8d1da67..f640a9f01 100644 --- a/packages/i18n/src/locales/pt-BR/translations.json +++ b/packages/i18n/src/locales/pt-BR/translations.json @@ -874,13 +874,15 @@ "guests": "Convidados", "on_track": "No caminho certo", "off_track": "Fora do caminho", + "at_risk": "Em risco", "timeline": "Linha do tempo", "completion": "Conclusão", "upcoming": "Próximo", "completed": "Concluído", "in_progress": "Em andamento", "planned": "Planejado", - "paused": "Pausado" + "paused": "Pausado", + "no_of": "Nº de {entity}" }, "chart": { "x_axis": "Eixo X", @@ -2463,4 +2465,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "O Plane não inicializou. Isso pode ser porque um ou mais serviços do Plane falharam ao iniciar.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Escolha View Logs do setup.sh e logs do Docker para ter certeza." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/ro/translations.json b/packages/i18n/src/locales/ro/translations.json index 3c8ab4502..fd59eb3e8 100644 --- a/packages/i18n/src/locales/ro/translations.json +++ b/packages/i18n/src/locales/ro/translations.json @@ -872,13 +872,15 @@ "guests": "Invitați", "on_track": "Pe drumul cel bun", "off_track": "În afara traiectoriei", + "at_risk": "În pericol", "timeline": "Cronologie", "completion": "Finalizare", "upcoming": "Viitor", "completed": "Finalizat", "in_progress": "În desfășurare", "planned": "Planificat", - "paused": "Pauzat" + "paused": "Pauzat", + "no_of": "Nr. de {entity}" }, "chart": { "x_axis": "axa-X", @@ -2461,4 +2463,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane nu a pornit. Aceasta ar putea fi din cauza că unul sau mai multe servicii Plane au eșuat să pornească.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Alegeți View Logs din setup.sh și logurile Docker pentru a fi siguri." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/ru/translations.json b/packages/i18n/src/locales/ru/translations.json index 624179ef6..d4067bd72 100644 --- a/packages/i18n/src/locales/ru/translations.json +++ b/packages/i18n/src/locales/ru/translations.json @@ -874,6 +874,7 @@ "guests": "Гости", "on_track": "По плану", "off_track": "Отклонение от плана", + "at_risk": "Под угрозой", "timeline": "Хронология", "completion": "Завершение", "upcoming": "Предстоящие", @@ -2468,5 +2469,6 @@ "self_hosted_maintenance_message": { "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane не запустился. Это может быть из-за того, что один или несколько сервисов Plane не смогли запуститься.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Выберите View Logs из setup.sh и логов Docker, чтобы убедиться." - } -} + }, + "no_of": "Количество {entity}" +} \ No newline at end of file diff --git a/packages/i18n/src/locales/sk/translations.json b/packages/i18n/src/locales/sk/translations.json index d87be6ce5..e3c3e864e 100644 --- a/packages/i18n/src/locales/sk/translations.json +++ b/packages/i18n/src/locales/sk/translations.json @@ -874,13 +874,15 @@ "guests": "Hostia", "on_track": "Na správnej ceste", "off_track": "Mimo plán", + "at_risk": "V ohrození", "timeline": "Časová os", "completion": "Dokončenie", "upcoming": "Nadchádzajúce", "completed": "Dokončené", "in_progress": "Prebieha", "planned": "Plánované", - "paused": "Pozastavené" + "paused": "Pozastavené", + "no_of": "Počet {entity}" }, "chart": { "x_axis": "Os X", @@ -2468,4 +2470,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane sa nespustil. Toto môže byť spôsobené tým, že sa jedna alebo viac služieb Plane nepodarilo spustiť.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Vyberte View Logs z setup.sh a Docker logov, aby ste si boli istí." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/tr-TR/translations.json b/packages/i18n/src/locales/tr-TR/translations.json index 7c9219b85..4d1c4ab12 100644 --- a/packages/i18n/src/locales/tr-TR/translations.json +++ b/packages/i18n/src/locales/tr-TR/translations.json @@ -875,13 +875,15 @@ "guests": "Misafirler", "on_track": "Yolunda", "off_track": "Yolunda değil", + "at_risk": "Risk altında", "timeline": "Zaman çizelgesi", "completion": "Tamamlama", "upcoming": "Yaklaşan", "completed": "Tamamlandı", "in_progress": "Devam ediyor", "planned": "Planlandı", - "paused": "Durduruldu" + "paused": "Durduruldu", + "no_of": "{entity} sayısı" }, "chart": { "x_axis": "X ekseni", @@ -2447,4 +2449,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane başlatılamadı. Bu, bir veya daha fazla Plane servisinin başlatılamaması nedeniyle olabilir.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Emin olmak için setup.sh ve Docker loglarından View Logs'u seçin." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/ua/translations.json b/packages/i18n/src/locales/ua/translations.json index 603f6c0c5..252a858d5 100644 --- a/packages/i18n/src/locales/ua/translations.json +++ b/packages/i18n/src/locales/ua/translations.json @@ -874,13 +874,15 @@ "guests": "Гості", "on_track": "У межах графіку", "off_track": "Поза графіком", + "at_risk": "Під загрозою", "timeline": "Хронологія", "completion": "Завершення", "upcoming": "Майбутнє", "completed": "Завершено", "in_progress": "В процесі", "planned": "Заплановано", - "paused": "Призупинено" + "paused": "Призупинено", + "no_of": "Кількість {entity}" }, "chart": { "x_axis": "Вісь X", @@ -2468,4 +2470,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane не запустився. Це може бути через те, що один або декілька сервісів Plane не змогли запуститися.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Виберіть View Logs з setup.sh та логів Docker, щоб переконатися." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/vi-VN/translations.json b/packages/i18n/src/locales/vi-VN/translations.json index f78aea31a..d6e6d7999 100644 --- a/packages/i18n/src/locales/vi-VN/translations.json +++ b/packages/i18n/src/locales/vi-VN/translations.json @@ -873,13 +873,15 @@ "guests": "Khách", "on_track": "Đúng tiến độ", "off_track": "Chệch hướng", + "at_risk": "Có nguy cơ", "timeline": "Dòng thời gian", "completion": "Hoàn thành", "upcoming": "Sắp tới", "completed": "Đã hoàn thành", "in_progress": "Đang tiến hành", "planned": "Đã lên kế hoạch", - "paused": "Tạm dừng" + "paused": "Tạm dừng", + "no_of": "Số lượng {entity}" }, "chart": { "x_axis": "Trục X", @@ -2466,4 +2468,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane không khởi động được. Điều này có thể do một hoặc nhiều dịch vụ Plane không khởi động được.", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "Chọn View Logs từ setup.sh và log Docker để chắc chắn." } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/zh-CN/translations.json b/packages/i18n/src/locales/zh-CN/translations.json index b785d8649..d815a5ad7 100644 --- a/packages/i18n/src/locales/zh-CN/translations.json +++ b/packages/i18n/src/locales/zh-CN/translations.json @@ -873,13 +873,15 @@ "guests": "访客", "on_track": "进展顺利", "off_track": "偏离轨道", + "at_risk": "有风险", "timeline": "时间轴", "completion": "完成", "upcoming": "即将发生", "completed": "已完成", "in_progress": "进行中", "planned": "已计划", - "paused": "暂停" + "paused": "暂停", + "no_of": "{entity} 的数量" }, "chart": { "x_axis": "X轴", @@ -2448,4 +2450,4 @@ "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane 未能启动。这可能是因为一个或多个 Plane 服务启动失败。", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "请选择“查看日志”来查看 setup.sh 和 Docker 日志,以确认问题。" } -} +} \ No newline at end of file diff --git a/packages/i18n/src/locales/zh-TW/translations.json b/packages/i18n/src/locales/zh-TW/translations.json index b75d720f5..bec40f2ea 100644 --- a/packages/i18n/src/locales/zh-TW/translations.json +++ b/packages/i18n/src/locales/zh-TW/translations.json @@ -880,7 +880,9 @@ "completed": "已完成", "in_progress": "進行中", "planned": "已計劃", - "paused": "暫停" + "paused": "暫停", + "at_risk": "有風險", + "no_of": "{entity} 的數量" }, "chart": { "x_axis": "X 軸", @@ -2465,9 +2467,8 @@ "previously_edited_by": "先前編輯者", "edited_by": "編輯者" }, - "self_hosted_maintenance_message": { "plane_didnt_start_up_this_could_be_because_one_or_more_plane_services_failed_to_start": "Plane 未能啟動。這可能是因為一個或多個 Plane 服務啟動失敗。", "choose_view_logs_from_setup_sh_and_docker_logs_to_be_sure": "從 setup.sh 和 Docker 日誌中選擇 View Logs 來確認。" } -} +} \ No newline at end of file diff --git a/packages/propel/src/charts/line-chart/root.tsx b/packages/propel/src/charts/line-chart/root.tsx index f3b2ef72c..28a02fc30 100644 --- a/packages/propel/src/charts/line-chart/root.tsx +++ b/packages/propel/src/charts/line-chart/root.tsx @@ -122,7 +122,7 @@ export const LineChart = React.memo((props: angle: -90, position: "bottom", offset: -24, - dx: -16, + dx: yAxis.dx ?? -16, className: AXIS_LABEL_CLASSNAME, } } diff --git a/packages/propel/src/charts/scatter-chart/root.tsx b/packages/propel/src/charts/scatter-chart/root.tsx index d7996c990..5187d131b 100644 --- a/packages/propel/src/charts/scatter-chart/root.tsx +++ b/packages/propel/src/charts/scatter-chart/root.tsx @@ -27,7 +27,6 @@ export const ScatterChart = React.memo((prop margin, xAxis, yAxis, - className, tickCount = { x: undefined, @@ -35,6 +34,7 @@ export const ScatterChart = React.memo((prop }, legend, showTooltip = true, + customTooltipContent, } = props; // states const [activePoint, setActivePoint] = useState(null); @@ -107,7 +107,7 @@ export const ScatterChart = React.memo((prop angle: -90, position: "bottom", offset: -24, - dx: -16, + dx: yAxis.dx ?? -16, className: AXIS_LABEL_CLASSNAME, } } @@ -133,17 +133,21 @@ export const ScatterChart = React.memo((prop wrapperStyle={{ pointerEvents: "auto", }} - content={({ active, label, payload }) => ( - - )} + content={({ active, label, payload }) => + customTooltipContent ? ( + customTooltipContent({ active, label, payload }) + ) : ( + + ) + } /> )} {renderPoints} @@ -152,4 +156,4 @@ export const ScatterChart = React.memo((prop ); }); -ScatterChart.displayName = "ScatterChart"; +ScatterChart.displayName = "ScatterChart"; \ No newline at end of file diff --git a/packages/types/src/analytics.d.ts b/packages/types/src/analytics.d.ts index d55d74f4f..dbcf52f36 100644 --- a/packages/types/src/analytics.d.ts +++ b/packages/types/src/analytics.d.ts @@ -1,5 +1,6 @@ import { ChartXAxisProperty, ChartYAxisMetric } from "@plane/constants"; import { TChartData } from "./charts"; +import { Row } from "@tanstack/react-table"; export type TAnalyticsTabsBase = "overview" | "work-items"; export type TAnalyticsGraphsBase = "projects" | "work-items" | "custom-work-items"; @@ -20,12 +21,6 @@ export interface IAnalyticsResponseFields { filter_count: number; } -export interface IAnalyticsRadarEntity { - key: string; - name: string; - count: number; -} - // chart types export interface IChartResponse { @@ -43,7 +38,7 @@ export interface WorkItemInsightColumns { backlog_work_items: number; un_started_work_items: number; started_work_items: number; - // because of the peek view, we will display the name of the project instead of project__name + // incase of peek view, we will display the display_name instead of project__name display_name?: string; avatar_url?: string; assignee_id?: string; @@ -58,3 +53,9 @@ export interface IAnalyticsParams { y_axis: ChartYAxisMetric; group_by?: ChartXAxisProperty; } + +export type ExportConfig = { + key: string; + value: (row: Row) => string | number; + label?: string; +}; diff --git a/packages/types/src/charts/index.d.ts b/packages/types/src/charts/index.d.ts index 2747973aa..316cfd6b8 100644 --- a/packages/types/src/charts/index.d.ts +++ b/packages/types/src/charts/index.d.ts @@ -1,7 +1,5 @@ - - // ============================================================ -// Chart Base +// Chart Base // ============================================================ export * from "./common"; export type TChartLegend = { @@ -48,10 +46,11 @@ type TChartProps = { y?: number; }; showTooltip?: boolean; + customTooltipContent?: (props: { active?: boolean; label: string; payload: any }) => React.ReactNode; }; // ============================================================ -// Bar Chart +// Bar Chart // ============================================================ export type TBarItem = { @@ -71,7 +70,7 @@ export type TBarChartProps = TChartProps = { @@ -90,7 +89,7 @@ export type TLineChartProps = TChartProps = { @@ -105,7 +104,7 @@ export type TScatterChartProps = TChartProps }; // ============================================================ -// Area Chart +// Area Chart // ============================================================ export type TAreaItem = { @@ -130,7 +129,7 @@ export type TAreaChartProps = TChartProps = { @@ -161,7 +160,7 @@ export type TPieChartProps = Pick< }; // ============================================================ -// Tree Map +// Tree Map // ============================================================ export type TreeMapItem = { @@ -171,13 +170,13 @@ export type TreeMapItem = { textClassName?: string; icon?: React.ReactElement; } & ( - | { + | { fillColor: string; } - | { + | { fillClassName: string; } - ); +); export type TreeMapChartProps = { data: TreeMapItem[]; @@ -217,8 +216,8 @@ export type TRadarItem = { dot?: { r: number; fillOpacity: number; - } -} + }; +}; export type TRadarChartProps = Pick< TChartProps, @@ -231,4 +230,4 @@ export type TRadarChartProps = Pick< label?: string; strokeColor?: string; }; -} +}; diff --git a/packages/ui/src/icons/cycle/helper.tsx b/packages/ui/src/icons/cycle/helper.tsx index ec91cc2c2..c53069bc7 100644 --- a/packages/ui/src/icons/cycle/helper.tsx +++ b/packages/ui/src/icons/cycle/helper.tsx @@ -16,3 +16,12 @@ export const CYCLE_GROUP_COLORS: { completed: "#16A34A", draft: "#525252", }; + +export const CYCLE_GROUP_I18N_LABELS: { + [key in TCycleGroups]: string; +} = { + current: "current", + upcoming: "common.upcoming", + completed: "common.completed", + draft: "project_cycles.status.draft", +}; diff --git a/packages/ui/src/icons/cycle/index.ts b/packages/ui/src/icons/cycle/index.ts index e74c8ff8c..c3a791a2b 100644 --- a/packages/ui/src/icons/cycle/index.ts +++ b/packages/ui/src/icons/cycle/index.ts @@ -3,3 +3,4 @@ export * from "./circle-dot-full-icon"; export * from "./contrast-icon"; export * from "./circle-dot-full-icon"; export * from "./cycle-group-icon"; +export * from "./helper"; diff --git a/web/ce/components/analytics/tabs.ts b/web/ce/components/analytics/tabs.ts index 6ce6daf8e..6f978a3c6 100644 --- a/web/ce/components/analytics/tabs.ts +++ b/web/ce/components/analytics/tabs.ts @@ -5,6 +5,7 @@ export const ANALYTICS_TABS: { key: TAnalyticsTabsBase; i18nKey: string; content: React.FC; + isExtended?: boolean; }[] = [ { key: "overview", i18nKey: "common.overview", content: Overview }, { key: "work-items", i18nKey: "sidebar.work_items", content: WorkItems }, diff --git a/web/core/components/analytics/config.ts b/web/core/components/analytics/config.ts deleted file mode 100644 index 5e297e908..000000000 --- a/web/core/components/analytics/config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { mkConfig } from "export-to-csv"; - -export const csvConfig = (workspaceSlug: string) => - mkConfig({ - fieldSeparator: ",", - filename: `${workspaceSlug}-analytics`, - decimalSeparator: ".", - useKeysAsHeaders: true, - }); diff --git a/web/core/components/analytics/empty-state.tsx b/web/core/components/analytics/empty-state.tsx index 5243e6c88..3704f3e84 100644 --- a/web/core/components/analytics/empty-state.tsx +++ b/web/core/components/analytics/empty-state.tsx @@ -39,7 +39,7 @@ const AnalyticsEmptyState = ({ title, description, assetPath, className }: Props )}

{title}

- {description &&

{description}

} + {description &&

{description}

}
diff --git a/web/core/components/analytics/export.ts b/web/core/components/analytics/export.ts new file mode 100644 index 000000000..f0a448979 --- /dev/null +++ b/web/core/components/analytics/export.ts @@ -0,0 +1,26 @@ +import { ColumnDef, Row } from "@tanstack/react-table"; +import { download, generateCsv, mkConfig } from "export-to-csv"; + +export const csvConfig = (workspaceSlug: string) => + mkConfig({ + fieldSeparator: ",", + filename: `${workspaceSlug}-analytics`, + decimalSeparator: ".", + useKeysAsHeaders: true, + }); + +export const exportCSV = (rows: Row[], columns: ColumnDef[], workspaceSlug: string) => { + const rowData = rows.map((row) => { + const exportColumns = columns.map((col) => col.meta?.export); + const cells = exportColumns.reduce((acc: Record, col) => { + if (col) { + const cell = col?.value(row) ?? "-"; + acc[col.label ?? col.key] = cell; + } + return acc; + }, {}); + return cells; + }); + const csv = generateCsv(csvConfig(workspaceSlug))(rowData); + download(csvConfig(workspaceSlug))(csv); +}; diff --git a/web/core/components/analytics/insight-table/data-table.tsx b/web/core/components/analytics/insight-table/data-table.tsx index 8a66c3caf..35dc3b365 100644 --- a/web/core/components/analytics/insight-table/data-table.tsx +++ b/web/core/components/analytics/insight-table/data-table.tsx @@ -94,7 +94,7 @@ export function DataTable({ columns, data, searchPlaceholder, act ref={inputRef} className="w-full max-w-[234px] border-none bg-transparent text-sm text-custom-text-100 placeholder:text-custom-text-400 focus:outline-none" placeholder="Search" - value={table.getColumn(table.getHeaderGroups()?.[0].headers[0].id)?.getFilterValue() as string} + value={table.getColumn(table.getHeaderGroups()?.[0]?.headers?.[0]?.id)?.getFilterValue() as string} onChange={(e) => { const columnId = table.getHeaderGroups()?.[0]?.headers?.[0]?.id; if (columnId) table.getColumn(columnId)?.setFilterValue(e.target.value); diff --git a/web/core/components/analytics/insight-table/root.tsx b/web/core/components/analytics/insight-table/root.tsx index 583db1b7a..2fd19393b 100644 --- a/web/core/components/analytics/insight-table/root.tsx +++ b/web/core/components/analytics/insight-table/root.tsx @@ -26,24 +26,20 @@ export const InsightTable = >( return (
- {data ? ( - ) => ( - - )} - /> - ) : ( -
{t("common.no_data_yet")}
- )} + ) => ( + + )} + />
); }; diff --git a/web/core/components/analytics/overview/project-insights.tsx b/web/core/components/analytics/overview/project-insights.tsx index 9844a4b4f..994e6e9b7 100644 --- a/web/core/components/analytics/overview/project-insights.tsx +++ b/web/core/components/analytics/overview/project-insights.tsx @@ -32,7 +32,7 @@ const ProjectInsights = observer(() => { const resolvedPath = useResolvedAssetPath({ basePath: "/empty-state/analytics/empty-chart-radar" }); const { data: projectInsightsData, isLoading: isLoadingProjectInsight } = useSWR( - `radar-chart-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`, + `radar-chart-project-insights-${workspaceSlug}-${selectedDuration}-${selectedProjects}-${selectedCycle}-${selectedModule}-${isPeekView}`, () => analyticsService.getAdvanceAnalyticsCharts[]>( workspaceSlug, diff --git a/web/core/components/analytics/trend-piece.tsx b/web/core/components/analytics/trend-piece.tsx index 23daa89be..60062222a 100644 --- a/web/core/components/analytics/trend-piece.tsx +++ b/web/core/components/analytics/trend-piece.tsx @@ -8,6 +8,8 @@ type Props = { percentage: number; className?: string; size?: "xs" | "sm" | "md" | "lg"; + trendIconVisible?: boolean; + variant?: "simple" | "outlined" | "tinted"; }; const sizeConfig = { @@ -29,16 +31,47 @@ const sizeConfig = { }, } as const; +const variants: Record, Record<"ontrack" | "offtrack" | "atrisk", string>> = { + simple: { + ontrack: "text-green-500", + offtrack: "text-yellow-500", + atrisk: "text-red-500", + }, + outlined: { + ontrack: "text-green-500 border border-green-500", + offtrack: "text-yellow-500 border border-yellow-500", + atrisk: "text-red-500 border border-red-500", + }, + tinted: { + ontrack: "text-green-500 bg-green-500/10", + offtrack: "text-yellow-500 bg-yellow-500/10", + atrisk: "text-red-500 bg-red-500/10", + }, +} as const; + const TrendPiece = (props: Props) => { - const { percentage, className, size = "sm" } = props; - const isPositive = percentage > 0; + const { percentage, className, trendIconVisible = true, size = "sm", variant = "simple" } = props; + const isOnTrack = percentage >= 66; + const isOffTrack = percentage >= 33 && percentage < 66; const config = sizeConfig[size]; return (
- {isPositive ? : } + {trendIconVisible && + (isOnTrack ? ( + + ) : isOffTrack ? ( + + ) : ( + + ))} {Math.round(Math.abs(percentage))}%
); diff --git a/web/core/components/analytics/work-items/created-vs-resolved.tsx b/web/core/components/analytics/work-items/created-vs-resolved.tsx index 76215238d..d5deb80e7 100644 --- a/web/core/components/analytics/work-items/created-vs-resolved.tsx +++ b/web/core/components/analytics/work-items/created-vs-resolved.tsx @@ -104,7 +104,7 @@ const CreatedVsResolved = observer(() => { }} yAxis={{ key: "count", - label: t("no_of", { entity: t("work_items") }), + label: t("no_of", { entity: isEpic ? t("epics") : t("work_items") }), offset: -30, dx: -22, }} diff --git a/web/core/components/analytics/work-items/priority-chart.tsx b/web/core/components/analytics/work-items/priority-chart.tsx index 78a05c2c7..354044bda 100644 --- a/web/core/components/analytics/work-items/priority-chart.tsx +++ b/web/core/components/analytics/work-items/priority-chart.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import { ColumnDef, Row, Table } from "@tanstack/react-table"; -import { mkConfig, generateCsv, download } from "export-to-csv"; +import { ColumnDef, RowData, Table } from "@tanstack/react-table"; +import { mkConfig } from "export-to-csv"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { useTheme } from "next-themes"; @@ -18,8 +18,8 @@ import { } from "@plane/constants"; import { useTranslation } from "@plane/i18n"; import { BarChart } from "@plane/propel/charts/bar-chart"; -import { IChartResponse } from "@plane/types"; -import { TBarItem, TChart, TChartData, TChartDatum } from "@plane/types/src/charts"; +import { ExportConfig } from "@plane/types"; +import { TBarItem, TChart, TChartDatum } from "@plane/types/src/charts"; // plane web components import { Button } from "@plane/ui"; import { generateExtendedColors, parseChartData } from "@/components/chart/utils"; @@ -29,10 +29,17 @@ import { useAnalytics } from "@/hooks/store/use-analytics"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { AnalyticsService } from "@/services/analytics.service"; import AnalyticsEmptyState from "../empty-state"; +import { exportCSV } from "../export"; import { DataTable } from "../insight-table/data-table"; import { ChartLoader } from "../loaders"; import { generateBarColor } from "./utils"; +declare module "@tanstack/react-table" { + interface ColumnMeta { + export: ExportConfig; + } +} + interface Props { x_axis: ChartXAxisProperty; y_axis: ChartYAxisMetric; @@ -146,11 +153,25 @@ const PriorityChart = observer((props: Props) => { { accessorKey: "name", header: () => xAxisLabel, + meta: { + export: { + key: xAxisLabel, + value: (row) => row.original.name, + label: xAxisLabel, + }, + }, }, { accessorKey: "count", header: () =>
Count
, cell: ({ row }) =>
{row.original.count}
, + meta: { + export: { + key: "Count", + value: (row) => row.original.count, + label: "Count", + }, + }, }, ], [xAxisLabel] @@ -163,40 +184,18 @@ const PriorityChart = observer((props: Props) => { accessorKey: key, header: () =>
{parsedData.schema[key]}
, cell: ({ row }) =>
{row.original[key]}
, + meta: { + export: { + key, + value: (row) => row.original[key], + label: parsedData.schema[key], + }, + }, })) : [], [parsedData] ); - const csvConfig = mkConfig({ - fieldSeparator: ",", - filename: `${workspaceSlug}-analytics`, - decimalSeparator: ".", - useKeysAsHeaders: true, - }); - - const exportCSV = (rows: Row[]) => { - const rowData = rows.map((row) => { - const hiddenFields = ["key", "avatar_url", "assignee_id", "project_id"]; - const otherFields = Object.keys(row.original).filter( - (key) => key !== "name" && key !== "count" && !hiddenFields.includes(key) && !key.includes("id") - ); - return { - name: row.original.name, - count: row.original.count, - ...otherFields.reduce( - (acc, key) => { - acc[parsedData?.schema[key] ?? key] = row.original[key]; - return acc; - }, - {} as Record - ), - }; - }); - const csv = generateCsv(csvConfig)(rowData); - download(csvConfig)(csv); - }; - return (
{priorityChartLoading ? ( @@ -217,7 +216,7 @@ const PriorityChart = observer((props: Props) => { }} yAxis={{ key: "count", - label: yAxisLabel, + label: t("no_of", { entity: yAxisLabel.replace("_", " ") }), offset: -40, dx: -26, }} @@ -230,7 +229,7 @@ const PriorityChart = observer((props: Props) => { diff --git a/web/core/components/analytics/work-items/workitems-insight-table.tsx b/web/core/components/analytics/work-items/workitems-insight-table.tsx index 45e12b1e3..98935271f 100644 --- a/web/core/components/analytics/work-items/workitems-insight-table.tsx +++ b/web/core/components/analytics/work-items/workitems-insight-table.tsx @@ -1,13 +1,12 @@ -import { useMemo, useCallback } from "react"; -import { ColumnDef, Row } from "@tanstack/react-table"; -import { download, generateCsv } from "export-to-csv"; +import { useMemo } from "react"; +import { ColumnDef, Row, RowData } from "@tanstack/react-table"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import useSWR from "swr"; import { Briefcase, UserRound } from "lucide-react"; // plane package imports import { useTranslation } from "@plane/i18n"; -import { WorkItemInsightColumns, AnalyticsTableDataMap } from "@plane/types"; +import { WorkItemInsightColumns, AnalyticsTableDataMap, ExportConfig } from "@plane/types"; // plane web components import { Avatar } from "@plane/ui"; import { getFileURL } from "@plane/utils"; @@ -17,11 +16,17 @@ import { useAnalytics } from "@/hooks/store/use-analytics"; import { useProject } from "@/hooks/store/use-project"; import { AnalyticsService } from "@/services/analytics.service"; // plane web components -import { csvConfig } from "../config"; +import { exportCSV } from "../export"; import { InsightTable } from "../insight-table"; const analyticsService = new AnalyticsService(); +declare module "@tanstack/react-table" { + interface ColumnMeta { + export: ExportConfig; + } +} + const WorkItemsInsightTable = observer(() => { // router const params = useParams(); @@ -60,104 +65,125 @@ const WorkItemsInsightTable = observer(() => { }), [t] ); - const columns = useMemo( - () => - [ - !isPeekView - ? { - accessorKey: "project__name", - header: () =>
{columnsLabels["project__name"]}
, - cell: ({ row }) => { - const project = getProjectById(row.original.project_id); - return ( -
- {project?.logo_props ? ( - - ) : ( - - )} - {project?.name} -
- ); - }, - } - : { - accessorKey: "display_name", - header: () =>
{columnsLabels["display_name"]}
, - cell: ({ row }: { row: Row }) => ( -
-
- {row.original.avatar_url && row.original.avatar_url !== "" ? ( - - ) : ( -
- {row.original.display_name ? ( - row.original.display_name?.[0] - ) : ( - - )} -
- )} - - {row.original.display_name ?? t(`Unassigned`)} - -
+ const columns: ColumnDef[] = useMemo( + () => [ + !isPeekView + ? { + accessorKey: "project__name", + header: () =>
{columnsLabels["project__name"]}
, + cell: ({ row }) => { + const project = getProjectById(row.original.project_id); + return ( +
+ {project?.logo_props ? ( + + ) : ( + + )} + {project?.name}
- ), + ); }, - { - accessorKey: "backlog_work_items", - header: () =>
{columnsLabels["backlog_work_items"]}
, - cell: ({ row }) =>
{row.original.backlog_work_items}
, + meta: { + export: { + key: columnsLabels["project__name"], + value: (row) => row.original.project__name?.toString() ?? "", + }, + }, + } + : { + accessorKey: "display_name", + header: () =>
{columnsLabels["display_name"]}
, + cell: ({ row }: { row: Row }) => ( +
+
+ {row.original.avatar_url && row.original.avatar_url !== "" ? ( + + ) : ( +
+ {row.original.display_name ? ( + row.original.display_name?.[0] + ) : ( + + )} +
+ )} + + {row.original.display_name ?? t(`Unassigned`)} + +
+
+ ), + meta: { + export: { + key: columnsLabels["display_name"], + value: (row) => row.original.display_name?.toString() ?? "", + }, + }, + }, + { + accessorKey: "backlog_work_items", + header: () =>
{columnsLabels["backlog_work_items"]}
, + cell: ({ row }) =>
{row.original.backlog_work_items}
, + meta: { + export: { + key: columnsLabels["backlog_work_items"], + value: (row) => row.original.backlog_work_items.toString(), + }, }, - { - accessorKey: "started_work_items", - header: () =>
{columnsLabels["started_work_items"]}
, - cell: ({ row }) =>
{row.original.started_work_items}
, + }, + { + accessorKey: "started_work_items", + header: () =>
{columnsLabels["started_work_items"]}
, + cell: ({ row }) =>
{row.original.started_work_items}
, + meta: { + export: { + key: columnsLabels["started_work_items"], + value: (row) => row.original.started_work_items.toString(), + }, }, - { - accessorKey: "un_started_work_items", - header: () =>
{columnsLabels["un_started_work_items"]}
, - cell: ({ row }) =>
{row.original.un_started_work_items}
, + }, + { + accessorKey: "un_started_work_items", + header: () =>
{columnsLabels["un_started_work_items"]}
, + cell: ({ row }) =>
{row.original.un_started_work_items}
, + meta: { + export: { + key: columnsLabels["un_started_work_items"], + value: (row) => row.original.un_started_work_items.toString(), + }, }, - { - accessorKey: "completed_work_items", - header: () =>
{columnsLabels["completed_work_items"]}
, - cell: ({ row }) =>
{row.original.completed_work_items}
, + }, + { + accessorKey: "completed_work_items", + header: () =>
{columnsLabels["completed_work_items"]}
, + cell: ({ row }) =>
{row.original.completed_work_items}
, + meta: { + export: { + key: columnsLabels["completed_work_items"], + value: (row) => row.original.completed_work_items.toString(), + }, }, - { - accessorKey: "cancelled_work_items", - header: () =>
{columnsLabels["cancelled_work_items"]}
, - cell: ({ row }) =>
{row.original.cancelled_work_items}
, + }, + { + accessorKey: "cancelled_work_items", + header: () =>
{columnsLabels["cancelled_work_items"]}
, + cell: ({ row }) =>
{row.original.cancelled_work_items}
, + meta: { + export: { + key: columnsLabels["cancelled_work_items"], + value: (row) => row.original.cancelled_work_items.toString(), + }, }, - ] as ColumnDef[], + }, + ], [columnsLabels, getProjectById, isPeekView, t] ); - - const exportCSV = useCallback( - (rows: Row[]) => { - const rowData: any = rows.map((row) => { - const { project_id, avatar_url, assignee_id, ...exportableData } = row.original; - return Object.fromEntries( - Object.entries(exportableData).map(([key, value]) => { - if (columnsLabels?.[key as keyof typeof columnsLabels]) { - return [columnsLabels[key as keyof typeof columnsLabels], value]; - } - return [key, value]; - }) - ); - }); - const csv = generateCsv(csvConfig(workspaceSlug))(rowData); - download(csvConfig(workspaceSlug))(csv); - }, - [columnsLabels, workspaceSlug] - ); - return ( analyticsType="work-items" @@ -166,7 +192,7 @@ const WorkItemsInsightTable = observer(() => { columns={columns} columnsLabels={columnsLabels} headerText={isPeekView ? t("common.assignee") : t("common.projects")} - onExport={exportCSV} + onExport={(rows) => workItemsData && exportCSV(rows, columns, workspaceSlug)} /> ); });