chore: handled the cycle date time using project timezone (#6187)
* chore: handled the cycle date time using project timezone * chore: reverted the frontend commit
This commit is contained in:
parent
38e8a5c807
commit
9ad8b43408
9 changed files with 185 additions and 54 deletions
|
|
@ -5,6 +5,7 @@ from rest_framework import serializers
|
||||||
from .base import BaseSerializer
|
from .base import BaseSerializer
|
||||||
from .issue import IssueStateSerializer
|
from .issue import IssueStateSerializer
|
||||||
from plane.db.models import Cycle, CycleIssue, CycleUserProperties
|
from plane.db.models import Cycle, CycleIssue, CycleUserProperties
|
||||||
|
from plane.utils.timezone_converter import convert_to_utc
|
||||||
|
|
||||||
|
|
||||||
class CycleWriteSerializer(BaseSerializer):
|
class CycleWriteSerializer(BaseSerializer):
|
||||||
|
|
@ -15,6 +16,17 @@ class CycleWriteSerializer(BaseSerializer):
|
||||||
and data.get("start_date", None) > data.get("end_date", None)
|
and data.get("start_date", None) > data.get("end_date", None)
|
||||||
):
|
):
|
||||||
raise serializers.ValidationError("Start date cannot exceed end date")
|
raise serializers.ValidationError("Start date cannot exceed end date")
|
||||||
|
if (
|
||||||
|
data.get("start_date", None) is not None
|
||||||
|
and data.get("end_date", None) is not None
|
||||||
|
):
|
||||||
|
project_id = self.initial_data.get("project_id") or self.instance.project_id
|
||||||
|
data["start_date"] = convert_to_utc(
|
||||||
|
str(data.get("start_date").date()), project_id
|
||||||
|
)
|
||||||
|
data["end_date"] = convert_to_utc(
|
||||||
|
str(data.get("end_date", None).date()), project_id, is_end_date=True
|
||||||
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import json
|
import json
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.contrib.postgres.aggregates import ArrayAgg
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
|
@ -52,6 +54,11 @@ from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||||
# Module imports
|
# Module imports
|
||||||
from .. import BaseAPIView, BaseViewSet
|
from .. import BaseAPIView, BaseViewSet
|
||||||
from plane.bgtasks.webhook_task import model_activity
|
from plane.bgtasks.webhook_task import model_activity
|
||||||
|
from plane.utils.timezone_converter import (
|
||||||
|
convert_utc_to_project_timezone,
|
||||||
|
convert_to_utc,
|
||||||
|
user_timezone_converter,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CycleViewSet(BaseViewSet):
|
class CycleViewSet(BaseViewSet):
|
||||||
|
|
@ -67,6 +74,19 @@ class CycleViewSet(BaseViewSet):
|
||||||
project_id=self.kwargs.get("project_id"),
|
project_id=self.kwargs.get("project_id"),
|
||||||
workspace__slug=self.kwargs.get("slug"),
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
project = Project.objects.get(id=self.kwargs.get("project_id"))
|
||||||
|
|
||||||
|
# Fetch project for the specific record or pass project_id dynamically
|
||||||
|
project_timezone = project.timezone
|
||||||
|
|
||||||
|
# Convert the current time (timezone.now()) to the project's timezone
|
||||||
|
local_tz = pytz.timezone(project_timezone)
|
||||||
|
current_time_in_project_tz = timezone.now().astimezone(local_tz)
|
||||||
|
|
||||||
|
# Convert project local time back to UTC for comparison (start_date is stored in UTC)
|
||||||
|
current_time_in_utc = current_time_in_project_tz.astimezone(pytz.utc)
|
||||||
|
|
||||||
return self.filter_queryset(
|
return self.filter_queryset(
|
||||||
super()
|
super()
|
||||||
.get_queryset()
|
.get_queryset()
|
||||||
|
|
@ -119,12 +139,15 @@ class CycleViewSet(BaseViewSet):
|
||||||
.annotate(
|
.annotate(
|
||||||
status=Case(
|
status=Case(
|
||||||
When(
|
When(
|
||||||
Q(start_date__lte=timezone.now())
|
Q(start_date__lte=current_time_in_utc)
|
||||||
& Q(end_date__gte=timezone.now()),
|
& Q(end_date__gte=current_time_in_utc),
|
||||||
then=Value("CURRENT"),
|
then=Value("CURRENT"),
|
||||||
),
|
),
|
||||||
When(start_date__gt=timezone.now(), then=Value("UPCOMING")),
|
When(
|
||||||
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
|
start_date__gt=current_time_in_utc,
|
||||||
|
then=Value("UPCOMING"),
|
||||||
|
),
|
||||||
|
When(end_date__lt=current_time_in_utc, then=Value("COMPLETED")),
|
||||||
When(
|
When(
|
||||||
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
Q(start_date__isnull=True) & Q(end_date__isnull=True),
|
||||||
then=Value("DRAFT"),
|
then=Value("DRAFT"),
|
||||||
|
|
@ -160,10 +183,22 @@ class CycleViewSet(BaseViewSet):
|
||||||
# Update the order by
|
# Update the order by
|
||||||
queryset = queryset.order_by("-is_favorite", "-created_at")
|
queryset = queryset.order_by("-is_favorite", "-created_at")
|
||||||
|
|
||||||
|
project = Project.objects.get(id=self.kwargs.get("project_id"))
|
||||||
|
|
||||||
|
# Fetch project for the specific record or pass project_id dynamically
|
||||||
|
project_timezone = project.timezone
|
||||||
|
|
||||||
|
# Convert the current time (timezone.now()) to the project's timezone
|
||||||
|
local_tz = pytz.timezone(project_timezone)
|
||||||
|
current_time_in_project_tz = timezone.now().astimezone(local_tz)
|
||||||
|
|
||||||
|
# Convert project local time back to UTC for comparison (start_date is stored in UTC)
|
||||||
|
current_time_in_utc = current_time_in_project_tz.astimezone(pytz.utc)
|
||||||
|
|
||||||
# Current Cycle
|
# Current Cycle
|
||||||
if cycle_view == "current":
|
if cycle_view == "current":
|
||||||
queryset = queryset.filter(
|
queryset = queryset.filter(
|
||||||
start_date__lte=timezone.now(), end_date__gte=timezone.now()
|
start_date__lte=current_time_in_utc, end_date__gte=current_time_in_utc
|
||||||
)
|
)
|
||||||
|
|
||||||
data = queryset.values(
|
data = queryset.values(
|
||||||
|
|
@ -191,6 +226,8 @@ class CycleViewSet(BaseViewSet):
|
||||||
"version",
|
"version",
|
||||||
"created_by",
|
"created_by",
|
||||||
)
|
)
|
||||||
|
datetime_fields = ["start_date", "end_date"]
|
||||||
|
data = user_timezone_converter(data, datetime_fields, project_timezone)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
@ -221,6 +258,8 @@ class CycleViewSet(BaseViewSet):
|
||||||
"version",
|
"version",
|
||||||
"created_by",
|
"created_by",
|
||||||
)
|
)
|
||||||
|
datetime_fields = ["start_date", "end_date"]
|
||||||
|
data = user_timezone_converter(data, datetime_fields, project_timezone)
|
||||||
return Response(data, status=status.HTTP_200_OK)
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||||
|
|
@ -365,6 +404,7 @@ class CycleViewSet(BaseViewSet):
|
||||||
|
|
||||||
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
|
||||||
def retrieve(self, request, slug, project_id, pk):
|
def retrieve(self, request, slug, project_id, pk):
|
||||||
|
project = Project.objects.get(id=project_id)
|
||||||
queryset = self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk)
|
queryset = self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk)
|
||||||
data = (
|
data = (
|
||||||
self.get_queryset()
|
self.get_queryset()
|
||||||
|
|
@ -417,6 +457,8 @@ class CycleViewSet(BaseViewSet):
|
||||||
)
|
)
|
||||||
|
|
||||||
queryset = queryset.first()
|
queryset = queryset.first()
|
||||||
|
datetime_fields = ["start_date", "end_date"]
|
||||||
|
data = user_timezone_converter(data, datetime_fields, project.timezone)
|
||||||
|
|
||||||
recent_visited_task.delay(
|
recent_visited_task.delay(
|
||||||
slug=slug,
|
slug=slug,
|
||||||
|
|
@ -492,6 +534,9 @@ class CycleDateCheckEndpoint(BaseAPIView):
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
start_date = convert_to_utc(str(start_date), project_id)
|
||||||
|
end_date = convert_to_utc(str(end_date), project_id, is_end_date=True)
|
||||||
|
|
||||||
# Check if any cycle intersects in the given interval
|
# Check if any cycle intersects in the given interval
|
||||||
cycles = Cycle.objects.filter(
|
cycles = Cycle.objects.filter(
|
||||||
Q(workspace__slug=slug)
|
Q(workspace__slug=slug)
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ from plane.utils.issue_filters import issue_filters
|
||||||
from plane.utils.order_queryset import order_issue_queryset
|
from plane.utils.order_queryset import order_issue_queryset
|
||||||
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
|
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
|
||||||
from .. import BaseAPIView, BaseViewSet
|
from .. import BaseAPIView, BaseViewSet
|
||||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
from plane.utils.timezone_converter import user_timezone_converter
|
||||||
from plane.bgtasks.recent_visited_task import recent_visited_task
|
from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||||
from plane.utils.global_paginator import paginate
|
from plane.utils.global_paginator import paginate
|
||||||
from plane.bgtasks.webhook_task import model_activity
|
from plane.bgtasks.webhook_task import model_activity
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ from plane.app.serializers import IssueSerializer
|
||||||
from plane.app.permissions import ProjectEntityPermission
|
from plane.app.permissions import ProjectEntityPermission
|
||||||
from plane.db.models import Issue, IssueLink, FileAsset, CycleIssue
|
from plane.db.models import Issue, IssueLink, FileAsset, CycleIssue
|
||||||
from plane.bgtasks.issue_activities_task import issue_activity
|
from plane.bgtasks.issue_activities_task import issue_activity
|
||||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
from plane.utils.timezone_converter import user_timezone_converter
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ from plane.app.permissions import ProjectEntityPermission
|
||||||
from plane.app.serializers import ModuleDetailSerializer
|
from plane.app.serializers import ModuleDetailSerializer
|
||||||
from plane.db.models import Issue, Module, ModuleLink, UserFavorite, Project
|
from plane.db.models import Issue, Module, ModuleLink, UserFavorite, Project
|
||||||
from plane.utils.analytics_plot import burndown_plot
|
from plane.utils.analytics_plot import burndown_plot
|
||||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
from plane.utils.timezone_converter import user_timezone_converter
|
||||||
|
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ from plane.db.models import (
|
||||||
Project,
|
Project,
|
||||||
)
|
)
|
||||||
from plane.utils.analytics_plot import burndown_plot
|
from plane.utils.analytics_plot import burndown_plot
|
||||||
from plane.utils.user_timezone_converter import user_timezone_converter
|
from plane.utils.timezone_converter import user_timezone_converter
|
||||||
from plane.bgtasks.webhook_task import model_activity
|
from plane.bgtasks.webhook_task import model_activity
|
||||||
from .. import BaseAPIView, BaseViewSet
|
from .. import BaseAPIView, BaseViewSet
|
||||||
from plane.bgtasks.recent_visited_task import recent_visited_task
|
from plane.bgtasks.recent_visited_task import recent_visited_task
|
||||||
|
|
|
||||||
100
apiserver/plane/utils/timezone_converter.py
Normal file
100
apiserver/plane/utils/timezone_converter.py
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
import pytz
|
||||||
|
from plane.db.models import Project
|
||||||
|
from datetime import datetime, time
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
def user_timezone_converter(queryset, datetime_fields, user_timezone):
|
||||||
|
# Create a timezone object for the user's timezone
|
||||||
|
user_tz = pytz.timezone(user_timezone)
|
||||||
|
|
||||||
|
# Check if queryset is a dictionary (single item) or a list of dictionaries
|
||||||
|
if isinstance(queryset, dict):
|
||||||
|
queryset_values = [queryset]
|
||||||
|
else:
|
||||||
|
queryset_values = list(queryset)
|
||||||
|
|
||||||
|
# Iterate over the dictionaries in the list
|
||||||
|
for item in queryset_values:
|
||||||
|
# Iterate over the datetime fields
|
||||||
|
for field in datetime_fields:
|
||||||
|
# Convert the datetime field to the user's timezone
|
||||||
|
if field in item and item[field]:
|
||||||
|
item[field] = item[field].astimezone(user_tz)
|
||||||
|
|
||||||
|
# If queryset was a single item, return a single item
|
||||||
|
if isinstance(queryset, dict):
|
||||||
|
return queryset_values[0]
|
||||||
|
else:
|
||||||
|
return queryset_values
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_utc(date, project_id, is_end_date=False):
|
||||||
|
"""
|
||||||
|
Converts a start date string to the project's local timezone at 12:00 AM
|
||||||
|
and then converts it to UTC for storage.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
date (str): The date string in "YYYY-MM-DD" format.
|
||||||
|
project_id (int): The project's ID to fetch the associated timezone.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
datetime: The UTC datetime.
|
||||||
|
"""
|
||||||
|
# Retrieve the project's timezone using the project ID
|
||||||
|
project = Project.objects.get(id=project_id)
|
||||||
|
project_timezone = project.timezone
|
||||||
|
if not date or not project_timezone:
|
||||||
|
raise ValueError("Both date and timezone must be provided.")
|
||||||
|
|
||||||
|
# Parse the string into a date object
|
||||||
|
start_date = datetime.strptime(date, "%Y-%m-%d").date()
|
||||||
|
|
||||||
|
# Get the project's timezone
|
||||||
|
local_tz = pytz.timezone(project_timezone)
|
||||||
|
|
||||||
|
# Combine the date with 12:00 AM time
|
||||||
|
local_datetime = datetime.combine(start_date, time.min)
|
||||||
|
|
||||||
|
# Localize the datetime to the project's timezone
|
||||||
|
localized_datetime = local_tz.localize(local_datetime)
|
||||||
|
|
||||||
|
# If it's an end date, subtract one minute
|
||||||
|
if is_end_date:
|
||||||
|
localized_datetime -= timedelta(minutes=1)
|
||||||
|
|
||||||
|
# Convert the localized datetime to UTC
|
||||||
|
utc_datetime = localized_datetime.astimezone(pytz.utc)
|
||||||
|
|
||||||
|
# Return the UTC datetime for storage
|
||||||
|
return utc_datetime
|
||||||
|
|
||||||
|
|
||||||
|
def convert_utc_to_project_timezone(utc_datetime, project_id):
|
||||||
|
"""
|
||||||
|
Converts a UTC datetime (stored in the database) to the project's local timezone.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
utc_datetime (datetime): The UTC datetime to be converted.
|
||||||
|
project_id (int): The project's ID to fetch the associated timezone.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
datetime: The datetime in the project's local timezone.
|
||||||
|
"""
|
||||||
|
# Retrieve the project's timezone using the project ID
|
||||||
|
project = Project.objects.get(id=project_id)
|
||||||
|
project_timezone = project.timezone
|
||||||
|
if not project_timezone:
|
||||||
|
raise ValueError("Project timezone must be provided.")
|
||||||
|
|
||||||
|
# Get the timezone object for the project's timezone
|
||||||
|
local_tz = pytz.timezone(project_timezone)
|
||||||
|
|
||||||
|
# Convert the UTC datetime to the project's local timezone
|
||||||
|
if utc_datetime.tzinfo is None:
|
||||||
|
# Localize UTC datetime if it's naive (i.e., without timezone info)
|
||||||
|
utc_datetime = pytz.utc.localize(utc_datetime)
|
||||||
|
|
||||||
|
# Convert to the project's local timezone
|
||||||
|
local_datetime = utc_datetime.astimezone(local_tz)
|
||||||
|
|
||||||
|
return local_datetime
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import pytz
|
|
||||||
|
|
||||||
|
|
||||||
def user_timezone_converter(queryset, datetime_fields, user_timezone):
|
|
||||||
# Create a timezone object for the user's timezone
|
|
||||||
user_tz = pytz.timezone(user_timezone)
|
|
||||||
|
|
||||||
# Check if queryset is a dictionary (single item) or a list of dictionaries
|
|
||||||
if isinstance(queryset, dict):
|
|
||||||
queryset_values = [queryset]
|
|
||||||
else:
|
|
||||||
queryset_values = list(queryset)
|
|
||||||
|
|
||||||
# Iterate over the dictionaries in the list
|
|
||||||
for item in queryset_values:
|
|
||||||
# Iterate over the datetime fields
|
|
||||||
for field in datetime_fields:
|
|
||||||
# Convert the datetime field to the user's timezone
|
|
||||||
if field in item and item[field]:
|
|
||||||
item[field] = item[field].astimezone(user_tz)
|
|
||||||
|
|
||||||
# If queryset was a single item, return a single item
|
|
||||||
if isinstance(queryset, dict):
|
|
||||||
return queryset_values[0]
|
|
||||||
else:
|
|
||||||
return queryset_values
|
|
||||||
|
|
@ -16,7 +16,7 @@ import {
|
||||||
CustomEmojiIconPicker,
|
CustomEmojiIconPicker,
|
||||||
EmojiIconPickerTypes,
|
EmojiIconPickerTypes,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
// CustomSearchSelect,
|
CustomSearchSelect,
|
||||||
} from "@plane/ui";
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { Logo } from "@/components/common";
|
import { Logo } from "@/components/common";
|
||||||
|
|
@ -25,7 +25,7 @@ import { ImagePickerPopover } from "@/components/core";
|
||||||
import { PROJECT_UPDATED } from "@/constants/event-tracker";
|
import { PROJECT_UPDATED } from "@/constants/event-tracker";
|
||||||
import { NETWORK_CHOICES } from "@/constants/project";
|
import { NETWORK_CHOICES } from "@/constants/project";
|
||||||
// helpers
|
// helpers
|
||||||
// import { TTimezone, TIME_ZONES } from "@/constants/timezones";
|
import { TTimezone, TIME_ZONES } from "@/constants/timezones";
|
||||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||||
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
|
||||||
import { getFileURL } from "@/helpers/file.helper";
|
import { getFileURL } from "@/helpers/file.helper";
|
||||||
|
|
@ -68,20 +68,20 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||||
});
|
});
|
||||||
// derived values
|
// derived values
|
||||||
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network);
|
const currentNetwork = NETWORK_CHOICES.find((n) => n.key === project?.network);
|
||||||
// const getTimeZoneLabel = (timezone: TTimezone | undefined) => {
|
const getTimeZoneLabel = (timezone: TTimezone | undefined) => {
|
||||||
// if (!timezone) return undefined;
|
if (!timezone) return undefined;
|
||||||
// return (
|
return (
|
||||||
// <div className="flex gap-1.5">
|
<div className="flex gap-1.5">
|
||||||
// <span className="text-custom-text-400">{timezone.gmtOffset}</span>
|
<span className="text-custom-text-400">{timezone.gmtOffset}</span>
|
||||||
// <span className="text-custom-text-200">{timezone.name}</span>
|
<span className="text-custom-text-200">{timezone.name}</span>
|
||||||
// </div>
|
</div>
|
||||||
// );
|
);
|
||||||
// };
|
};
|
||||||
// const timeZoneOptions = TIME_ZONES.map((timeZone) => ({
|
const timeZoneOptions = TIME_ZONES.map((timeZone) => ({
|
||||||
// value: timeZone.value,
|
value: timeZone.value,
|
||||||
// query: timeZone.name + " " + timeZone.gmtOffset + " " + timeZone.value,
|
query: timeZone.name + " " + timeZone.gmtOffset + " " + timeZone.value,
|
||||||
// content: getTimeZoneLabel(timeZone),
|
content: getTimeZoneLabel(timeZone),
|
||||||
// }));
|
}));
|
||||||
const coverImage = watch("cover_image_url");
|
const coverImage = watch("cover_image_url");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -146,7 +146,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
|
|
||||||
logo_props: formData.logo_props,
|
logo_props: formData.logo_props,
|
||||||
// timezone: formData.timezone,
|
timezone: formData.timezone,
|
||||||
};
|
};
|
||||||
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
// if unsplash or a pre-defined image is uploaded, delete the old uploaded asset
|
||||||
if (formData.cover_image_url?.startsWith("http")) {
|
if (formData.cover_image_url?.startsWith("http")) {
|
||||||
|
|
@ -386,7 +386,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="flex flex-col gap-1 col-span-1 sm:col-span-2 xl:col-span-1">
|
<div className="flex flex-col gap-1 col-span-1 sm:col-span-2 xl:col-span-1">
|
||||||
<h4 className="text-sm">Project Timezone</h4>
|
<h4 className="text-sm">Project Timezone</h4>
|
||||||
<Controller
|
<Controller
|
||||||
name="timezone"
|
name="timezone"
|
||||||
|
|
@ -410,7 +410,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{errors.timezone && <span className="text-xs text-red-500">{errors.timezone.message}</span>}
|
{errors.timezone && <span className="text-xs text-red-500">{errors.timezone.message}</span>}
|
||||||
</div> */}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue