diff --git a/backend/plugins/operation_api/src/modules/cycle/utils.ts b/backend/plugins/operation_api/src/modules/cycle/utils.ts
index f68a15c996..906eb75044 100644
--- a/backend/plugins/operation_api/src/modules/cycle/utils.ts
+++ b/backend/plugins/operation_api/src/modules/cycle/utils.ts
@@ -1,6 +1,7 @@
import { fillMissingDays } from '@/project/utils/charUtils';
import { STATUS_TYPES } from '@/status/constants/types';
-import { differenceInCalendarDays, startOfDay } from 'date-fns';
+import { sendTRPCMessage } from 'erxes-api-shared/utils';
+import { tz } from 'moment-timezone';
import { Types } from 'mongoose';
import { IModels } from '~/connectionResolvers';
@@ -130,6 +131,17 @@ export const getCycleProgressChart = async (
return [];
}
+ const timezone = await sendTRPCMessage({
+ pluginName: 'core',
+ method: 'query',
+ module: 'configs',
+ action: 'getConfig',
+ input: {
+ code: 'TIMEZONE',
+ },
+ defaultValue: 'UTC',
+ });
+
const [totalScopeResult] = await models.Task.aggregate([
{
$match: { ...filter },
@@ -177,10 +189,10 @@ export const getCycleProgressChart = async (
{
$addFields: {
dayDate: {
- $dateFromParts: {
- year: { $year: '$statusChangedDate' },
- month: { $month: '$statusChangedDate' },
- day: { $dayOfMonth: '$statusChangedDate' },
+ $dateToString: {
+ format: '%Y-%m-%d',
+ date: '$statusChangedDate',
+ timezone,
},
},
isStarted: { $eq: ['$statusType', STATUS_TYPES.STARTED] },
@@ -210,7 +222,7 @@ export const getCycleProgressChart = async (
{
$project: {
_id: 0,
- date: { $dateToString: { format: '%Y-%m-%d', date: '$_id' } },
+ date: '$_id',
started: 1,
completed: 1,
},
@@ -238,16 +250,12 @@ export const getCycleProgressChart = async (
chartData: [],
};
- const start = startOfDay(new Date(cycle.startDate));
- const end = startOfDay(new Date(cycle.endDate));
+ const start = tz(new Date(cycle.startDate), timezone);
+ const end = tz(new Date(cycle.endDate), timezone);
- const days = differenceInCalendarDays(end, start) + 1;
+ const days = end.diff(start, 'days') + 1;
- chartData.chartData = fillMissingDays(
- chartDataAggregation,
- cycle.startDate,
- days,
- );
+ chartData.chartData = fillMissingDays(chartDataAggregation, start, days);
return chartData;
};
diff --git a/backend/plugins/operation_api/src/modules/project/utils/charUtils.ts b/backend/plugins/operation_api/src/modules/project/utils/charUtils.ts
index a47296212f..0f1b1da1ab 100644
--- a/backend/plugins/operation_api/src/modules/project/utils/charUtils.ts
+++ b/backend/plugins/operation_api/src/modules/project/utils/charUtils.ts
@@ -1,4 +1,5 @@
import { addDays, differenceInCalendarDays, format } from 'date-fns';
+import { Moment } from 'moment-timezone';
export const fillUntilTargetDate = (
data: { date: string; started: number; completed: number }[],
@@ -24,7 +25,7 @@ export const fillUntilTargetDate = (
export const fillMissingDays = (
data: { date: string; started: number; completed: number }[],
- baseDate: Date,
+ baseDate: Moment,
totalDays = 7,
) => {
const filledData: { date: string; started: number; completed: number }[] = [];
@@ -32,8 +33,8 @@ export const fillMissingDays = (
const mapDateToData = new Map(data.map((item) => [item.date, item]));
for (let i = 0; i < totalDays; i++) {
- const date = addDays(baseDate, i);
- const key = format(date, 'yyyy-MM-dd');
+ const date = baseDate.clone().add(i, 'days');
+ const key = date.format('YYYY-MM-DD');
const item = mapDateToData.get(key);
if (item) {
diff --git a/backend/plugins/operation_api/src/worker/dailyCheckCycles.ts b/backend/plugins/operation_api/src/worker/dailyCheckCycles.ts
index a0eaaa8d09..ffb0da47c2 100644
--- a/backend/plugins/operation_api/src/worker/dailyCheckCycles.ts
+++ b/backend/plugins/operation_api/src/worker/dailyCheckCycles.ts
@@ -1,10 +1,11 @@
import { Job } from 'bullmq';
-import { endOfDay } from 'date-fns'; // эсвэл өөр utility
import {
getEnv,
getSaasOrganizations,
+ sendTRPCMessage,
sendWorkerQueue,
} from 'erxes-api-shared/utils';
+import { tz } from 'moment-timezone';
import { generateModels } from '~/connectionResolvers';
export const dailyCheckCycles = async () => {
@@ -14,35 +15,51 @@ export const dailyCheckCycles = async () => {
const orgs = await getSaasOrganizations();
for (const org of orgs) {
- if (org.enabledcycles) {
- sendWorkerQueue('operations', 'checkCycle').add('checkCycle', {
- subdomain: org.subdomain,
- });
- }
+ sendWorkerQueue('operations', 'checkCycle').add('checkCycle', {
+ subdomain: org.subdomain,
+ timezone: org.timezone,
+ });
}
return 'success';
} else {
+ const timezone = await sendTRPCMessage({
+ pluginName: 'core',
+ method: 'query',
+ module: 'configs',
+ action: 'getConfig',
+ input: {
+ code: 'TIMEZONE',
+ },
+ defaultValue: 'UTC',
+ });
+
sendWorkerQueue('operations', 'checkCycle').add('checkCycle', {
subdomain: 'os',
+ timezone,
});
return 'success';
}
};
export const checkCycle = async (job: Job) => {
- const { subdomain } = job?.data ?? {};
+ const { subdomain, timezone = 'UTC' } = job?.data ?? {};
+
+ const tzToday = tz(new Date(), timezone);
+
+ if (tzToday.hour() !== 0) {
+ return;
+ }
const models = await generateModels(subdomain);
- const today = new Date();
+ const utcStart = tzToday.startOf('day').toDate();
+ const utcEnd = tzToday.endOf('day').toDate();
const endCycles = await models.Cycle.find({
isActive: true,
isCompleted: false,
- endDate: {
- $lte: endOfDay(today),
- },
+ endDate: { $gte: utcStart, $lte: utcEnd },
});
if (endCycles?.length) {
@@ -54,9 +71,7 @@ export const checkCycle = async (job: Job) => {
const startCycles = await models.Cycle.find({
isActive: false,
isCompleted: false,
- startDate: {
- $lte: endOfDay(today),
- },
+ startDate: { $gte: utcStart, $lte: utcEnd },
});
if (startCycles?.length) {
diff --git a/backend/plugins/operation_api/src/worker/index.ts b/backend/plugins/operation_api/src/worker/index.ts
index 239fa85ffc..e426175130 100644
--- a/backend/plugins/operation_api/src/worker/index.ts
+++ b/backend/plugins/operation_api/src/worker/index.ts
@@ -10,7 +10,7 @@ export const initMQWorkers = async (redis: any) => {
await myQueue.upsertJobScheduler(
'operations-daily-cycles-check',
{
- pattern: '0 0 * * *',
+ pattern: '0 * * * *',
tz: 'UTC',
},
{
diff --git a/frontend/core-ui/src/modules/app/components/SettingsRoutes.tsx b/frontend/core-ui/src/modules/app/components/SettingsRoutes.tsx
index 5bcb8d7d95..995f3e5a69 100644
--- a/frontend/core-ui/src/modules/app/components/SettingsRoutes.tsx
+++ b/frontend/core-ui/src/modules/app/components/SettingsRoutes.tsx
@@ -32,11 +32,11 @@ const SettingsMailConfig = lazy(() =>
default: module.MailConfigPage,
})),
);
-// const GeneralSettings = lazy(() =>
-// import('~/pages/settings/workspace/GeneralSettingsPage').then((module) => ({
-// default: module.GeneralSettingsPage,
-// })),
-// );
+const GeneralSettings = lazy(() =>
+ import('~/pages/settings/workspace/GeneralSettingsPage').then((module) => ({
+ default: module.GeneralSettingsPage,
+ })),
+);
const TeamMemberSettings = lazy(() =>
import('~/pages/settings/workspace/TeamMemberPage').then((module) => ({
default: module.TeamMemberPage,
@@ -123,10 +123,10 @@ export function SettingsRoutes() {
path={SettingsWorkspacePath.MailConfig}
element={}
/>
- {/* }
- /> */}
+ />
}
diff --git a/frontend/core-ui/src/modules/settings/constants/data.ts b/frontend/core-ui/src/modules/settings/constants/data.ts
index fa0ca57122..0967362026 100644
--- a/frontend/core-ui/src/modules/settings/constants/data.ts
+++ b/frontend/core-ui/src/modules/settings/constants/data.ts
@@ -4,6 +4,7 @@ import {
TSettingPath,
} from '@/types/paths/SettingsPath';
import {
+ IconAdjustmentsAlt,
IconChessKnight,
IconFile,
IconMail,
@@ -120,6 +121,11 @@ export const SETTINGS_PATH_DATA: { [key: string]: TSettingPath[] } = {
// },
],
nav: [
+ {
+ name: 'General',
+ icon: IconAdjustmentsAlt,
+ path: SettingsWorkspacePath.General,
+ },
{
name: 'Team member',
icon: IconUsersGroup,
@@ -130,11 +136,6 @@ export const SETTINGS_PATH_DATA: { [key: string]: TSettingPath[] } = {
icon: IconUserCog,
path: SettingsWorkspacePath.Permissions,
},
- // {
- // name: 'General',
- // icon: IconAdjustmentsAlt,
- // path: SettingsWorkspacePath.General,
- // },
{
name: 'File upload',
icon: IconFile,
diff --git a/frontend/core-ui/src/modules/settings/general/components/GeneralSettings.tsx b/frontend/core-ui/src/modules/settings/general/components/GeneralSettings.tsx
index 3390db129b..adbc100e49 100644
--- a/frontend/core-ui/src/modules/settings/general/components/GeneralSettings.tsx
+++ b/frontend/core-ui/src/modules/settings/general/components/GeneralSettings.tsx
@@ -1,16 +1,17 @@
-import { useEffect } from 'react';
-import { useSwitchLanguage } from '~/i18n';
-import { SubmitHandler } from 'react-hook-form';
-import { Button, Form, Spinner, useToast } from 'erxes-ui';
import { useConfig } from '@/settings/file-upload/hook/useConfigs';
-import { SelectCurrency } from '@/settings/general/components/SelectCurrency';
-import { TGeneralSettingsProps } from '@/settings/general/types';
import { TConfig } from '@/settings/file-upload/types';
import { GeneralSettingsSkeleton } from '@/settings/general/components/GeneralSettingsSkeleton';
-import { useGeneralSettingsForms } from '@/settings/general/hooks/useGeneralSettingsForms';
import SelectControl from '@/settings/general/components/SelectControl';
-import { LANGUAGES } from '@/settings/general/constants/data';
+import { SelectCurrency } from '@/settings/general/components/SelectCurrency';
import { SelectMainCurrency } from '@/settings/general/components/SelectMainCurrency';
+import { SelectTimezone } from '@/settings/general/components/SelectTimezone';
+import { LANGUAGES } from '@/settings/general/constants/data';
+import { useGeneralSettingsForms } from '@/settings/general/hooks/useGeneralSettingsForms';
+import { TGeneralSettingsProps } from '@/settings/general/types';
+import { Button, Form, Spinner, useToast } from 'erxes-ui';
+import { useEffect } from 'react';
+import { SubmitHandler } from 'react-hook-form';
+import { useSwitchLanguage } from '~/i18n';
const GeneralSettings = () => {
const { languages } = useSwitchLanguage();
@@ -23,19 +24,22 @@ const GeneralSettings = () => {
const { configs, updateConfig, loading, isLoading } = useConfig();
const updateCurrency = (data: TGeneralSettingsProps) => {
- const updatedConfigs = configs.reduce(
- (acc: Record, config: TConfig) => {
- const key = config.code as keyof TGeneralSettingsProps;
- acc[config.code] = key in data ? data[key] : config.value;
+ const updatedConfigs = {
+ // start with all existing configs
+ ...configs.reduce((acc: Record, config: TConfig) => {
+ acc[config.code] = config.value;
return acc;
- },
- {} as Record,
- );
+ }, {} as Record),
+ // override/add with new data
+ ...data,
+ };
+
updateConfig(updatedConfigs);
};
const submitHandler: SubmitHandler = (data) => {
updateCurrency(data);
+
handleLanguage(data.languageCode).then(() => {
toast({
title: 'Updated successfully',
@@ -52,8 +56,13 @@ const GeneralSettings = () => {
const mainCurrency = configs?.find(
(data: any) => data.code === 'mainCurrency',
);
+
+ const timezone = configs?.find((data: any) => data.code === 'TIMEZONE');
+
methods.setValue('dealCurrency', currencies?.value);
methods.setValue('mainCurrency', mainCurrency?.value);
+
+ timezone && methods.setValue('TIMEZONE', timezone?.value);
}
}, [configs, methods]);
@@ -78,6 +87,7 @@ const GeneralSettings = () => {
/>
+