Skip to content

Commit

Permalink
Merge pull request #3087 from ever-co/3028-improvement-see-plan--abil…
Browse files Browse the repository at this point in the history
…ity-to-add-task-to-empty-plan

feat: user can add tasks for today / tomorrow plan from 'See plan' modal
  • Loading branch information
evereq authored Oct 1, 2024
2 parents 7190b05 + 366136e commit 46e01fd
Show file tree
Hide file tree
Showing 17 changed files with 456 additions and 174 deletions.
268 changes: 151 additions & 117 deletions apps/web/lib/features/daily-plan/add-task-estimation-hours-modal.tsx

Large diffs are not rendered by default.

111 changes: 69 additions & 42 deletions apps/web/lib/features/daily-plan/all-plans-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DailyPlanStatusEnum, IDailyPlan } from '@app/interfaces';
import moment from 'moment';
import { ValueNoneIcon } from '@radix-ui/react-icons';
import { checkPastDate } from 'lib/utils';
import { useTranslations } from 'next-intl';

interface IAllPlansModal {
closeModal: () => void;
Expand All @@ -33,15 +34,14 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
const { isOpen, closeModal } = props;
const [showCalendar, setShowCalendar] = useState(false);
const [showCustomPlan, setShowCustomPlan] = useState(false);
const [customDate, setCustomDate] = useState<Date>();
const [customDate, setCustomDate] = useState<Date>(moment().toDate());
const { myDailyPlans, pastPlans } = useDailyPlan();
const t = useTranslations();

// Utility function for checking if two dates are the same
const isSameDate = useCallback(
(date1: Date, date2: Date) =>
new Date(date1).toLocaleDateString('en') === new Date(date2).toLocaleDateString('en'),
[]
);
const isSameDate = useCallback((date1: Date | number | string, date2: Date | number | string) => {
return moment(date1).toISOString().split('T')[0] === moment(date2).toISOString().split('T')[0];
}, []);

// Memoize today, tomorrow, and future plans
const todayPlan = useMemo(
Expand All @@ -55,8 +55,12 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
);

const selectedPlan = useMemo(
() => customDate && myDailyPlans.items.find((plan) => isSameDate(plan.date, customDate)),
[isSameDate, myDailyPlans.items, customDate]
() =>
customDate &&
myDailyPlans.items.find((plan) => {
return isSameDate(plan.date.toString().split('T')[0], customDate.setHours(0, 0, 0, 0));
}),
[customDate, myDailyPlans.items, isSameDate]
);

// Handle modal close
Expand All @@ -72,6 +76,11 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)

// Handle tab switching
const handleTabClick = (tab: CalendarTab) => {
if (tab === 'Today') {
setCustomDate(moment().toDate());
} else if (tab === 'Tomorrow') {
setCustomDate(moment().add(1, 'days').toDate());
}
setSelectedTab(tab);
setShowCalendar(tab === 'Calendar');
setShowCustomPlan(false);
Expand All @@ -97,27 +106,24 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
// Set the related tab for today and tomorrow dates
const handleCalendarSelect = useCallback(() => {
if (customDate) {
if (
new Date(customDate).toLocaleDateString('en') === new Date(moment().toDate()).toLocaleDateString('en')
) {
if (isSameDate(customDate, moment().startOf('day').toDate())) {
setSelectedTab('Today');
} else if (
new Date(customDate).toLocaleDateString('en') ===
new Date(moment().add(1, 'days').toDate()).toLocaleDateString('en')
) {
setCustomDate(moment().toDate());
} else if (isSameDate(customDate, moment().add(1, 'days').startOf('day').toDate())) {
setSelectedTab('Tomorrow');
setCustomDate(moment().add(1, 'days').toDate());
} else {
setShowCalendar(false);
setShowCustomPlan(true);
}
}
}, [customDate]);
}, [customDate, isSameDate]);

const createEmptyPlan = useCallback(async () => {
try {
await createDailyPlan({
workTimePlanned: 0,
date: moment(customDate).toDate(),
date: new Date(moment(customDate).format('YYYY-MM-DD')),
status: DailyPlanStatusEnum.OPEN,
tenantId: user?.tenantId ?? '',
employeeId: user?.employee.id,
Expand Down Expand Up @@ -145,17 +151,19 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
<span className="rotate-180">
<ChevronRightIcon className="w-4 h-4 stroke-[#B1AEBC]" />
</span>
<span>Back</span>
<span>{t('common.BACK')}</span>
</button>
</Tooltip>
)}

<Text.Heading as="h3" className="uppercase text-center">
{selectedTab == 'Calendar'
? showCustomPlan && selectedPlan
? `PLAN FOR ${new Date(selectedPlan.date).toLocaleDateString('en-GB')}`
: `PLANS`
: `${selectedTab}'S PLAN`}
? t('common.plan.FOR_DATE', {
date: new Date(selectedPlan.date).toLocaleDateString('en-GB')
})
: t('common.plan.PLURAL')
: `${selectedTab === 'Today' ? t('common.plan.FOR_TODAY') : selectedTab === 'Tomorrow' ? t('common.plan.FOR_TOMORROW') : ''}`}
</Text.Heading>
</div>
<div className="w-full h-12 flex items-center">
Expand All @@ -166,19 +174,25 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
className={`flex justify-center gap-4 items-center hover:text-primary cursor-pointer ${selectedTab === tab ? 'text-primary font-medium' : ''}`}
onClick={() => handleTabClick(tab)}
>
<span>{tab}</span>
<span>
{tab === 'Today'
? t('common.TODAY')
: tab === 'Tomorrow'
? t('common.TOMORROW')
: t('common.CALENDAR')}
</span>
{index + 1 < tabs.length && <VerticalSeparator className="w-full" />}
</li>
))}
</ul>
</div>

<div className="w-full flex flex-col items-center justify-center h-[34rem]">
<div className="w-full flex flex-col items-center h-[34rem]">
{selectedTab === 'Calendar' && showCalendar ? (
<div className="w-full h-full flex-col flex items-center justify-between">
<div className="w-full grow">
<div className="w-full h-full flex flex-col gap-4 items-center justify-center">
<p className=" text-sm font-medium">Select a date to be able to see a plan</p>
<p className=" text-sm font-medium">{t('common.plan.CHOOSE_DATE')}</p>
<div className="p-3 border flex items-center justify-center rounded-md">
<FuturePlansCalendar
selectedPlan={customDate}
Expand All @@ -194,41 +208,52 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
<Button
variant="outline"
type="submit"
className="py-3 px-5 rounded-md font-light text-md dark:text-white dark:bg-slate-700 dark:border-slate-600"
className="py-3 px-5 min-w-[8rem] rounded-md font-light text-md dark:text-white dark:bg-slate-700 dark:border-slate-600"
onClick={handleCloseModal}
>
Cancel
{t('common.CANCEL')}
</Button>
<Button
disabled={!customDate || createDailyPlanLoading}
variant="default"
type="submit"
className={clsxm('py-3 px-5 rounded-md font-light text-md dark:text-white')}
className={clsxm(
'py-3 min-w-[8rem] px-5 rounded-md font-light text-md dark:text-white'
)}
onClick={selectedPlan ? handleCalendarSelect : createEmptyPlan}
>
{selectedPlan ? (
'Select'
t('common.SELECT')
) : createDailyPlanLoading ? (
<SpinnerLoader variant="light" size={20} />
) : (
'Add plan'
t('common.plan.ADD_PLAN')
)}
</Button>
</div>
</div>
) : (
<>
{plan ? (
{selectedPlan ? (
<AddTasksEstimationHoursModal
plan={plan}
tasks={plan.tasks ?? []}
tasks={plan?.tasks ?? []}
isRenderedInSoftFlow={false}
isOpen={isOpen}
closeModal={handleCloseModal}
selectedDate={customDate}
/>
) : customDate ? (
<AddTasksEstimationHoursModal
tasks={[]}
isRenderedInSoftFlow={false}
isOpen={isOpen}
closeModal={handleCloseModal}
selectedDate={customDate}
/>
) : (
<div className="flex justify-center items-center h-full">
<NoData component={<ValueNoneIcon />} text="Plan not found " />
<NoData component={<ValueNoneIcon />} text={t('common.plan.PLAN_NOT_FOUND')} />
</div>
)}
</>
Expand All @@ -247,7 +272,7 @@ export const AllPlansModal = memo(function AllPlansModal(props: IAllPlansModal)
*/

interface ICalendarProps {
setSelectedPlan: Dispatch<SetStateAction<Date | undefined>>;
setSelectedPlan: Dispatch<SetStateAction<Date>>;
selectedPlan: Date | undefined;
plans: IDailyPlan[];
pastPlans: IDailyPlan[];
Expand All @@ -257,7 +282,7 @@ interface ICalendarProps {
* The component that handles the selection of a plan
*
* @param {Object} props - The props object
* @param {Dispatch<SetStateAction<IDailyPlan>>} props.setSelectedFuturePlan - A function that set the selected plan
* @param {Dispatch<SetStateAction<IDailyPlan>>} props.setSelectedPlan - A function that set the selected plan
* @param {IDailyPlan} props.selectedPlan - The selected plan
* @param {IDailyPlan[]} props.plans - Available plans
* @param {IDailyPlan[]} props.pastPlans - Past plans
Expand Down Expand Up @@ -285,10 +310,12 @@ const FuturePlansCalendar = memo(function FuturePlansCalendar(props: ICalendarPr
const isDateUnplanned = useCallback(
(dateToCheck: Date) => {
return !plans
.map((plan) => new Date(plan.date))
.some(
(date) => new Date(date).toLocaleDateString('en') == new Date(dateToCheck).toLocaleDateString('en')
);
.map((plan) => {
return moment(plan.date.toString().split('T')[0]).toISOString().split('T')[0];
})
.some((date) => {
return date === moment(dateToCheck).toISOString().split('T')[0];
});
},
[plans]
);
Expand All @@ -297,19 +324,19 @@ const FuturePlansCalendar = memo(function FuturePlansCalendar(props: ICalendarPr
<Calendar
mode="single"
captionLayout="dropdown"
selected={selectedPlan ? new Date(selectedPlan) : undefined}
selected={selectedPlan ? selectedPlan : undefined}
onSelect={(date) => {
if (date) {
setSelectedPlan(date);
setSelectedPlan(moment(date).toDate());
}
}}
initialFocus
disabled={(date) => {
return checkPastDate(date) && isDateUnplanned(date);
}}
modifiers={{
booked: sortedPlans?.map((plan) => new Date(plan.date)),
pastDay: pastPlans?.map((plan) => new Date(plan.date))
booked: sortedPlans?.map((plan) => moment.utc(plan.date.toString().split('T')[0]).toDate()),
pastDay: pastPlans?.map((plan) => moment.utc(plan.date.toString().split('T')[0]).toDate())
}}
modifiersClassNames={{
booked: clsxm(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function UserTeamCardHeader() {
</div>
</div>
<div className="w-4 self-stretch border-l-[0.125rem] border-l-transparent " />
<div className="2xl:w-48 3xl:w-[12rem] w-1/5 lg:px-4 !pl-6 lg:!pl-8 flex flex-col items-center text-center justify-center">
<div className="2xl:w-48 3xl:w-[12rem] capitalize w-1/5 lg:px-4 !pl-6 lg:!pl-8 flex flex-col items-center text-center justify-center">
<Tooltip label={t('task.taskTableHead.WORKED_ON_TASK_HEADER_TOOLTIP')}>
{t('dailyPlan.TASK_TIME')}
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ function DropdownMenu({ edition, memberInfo }: Props) {
'font-normal whitespace-nowrap text-sm hover:font-semibold hover:transition-all'
)}
>
See Plan
{t('common.plan.SEE_PLANS')}
</button>
</ul>
</ul>
Expand Down
19 changes: 18 additions & 1 deletion apps/web/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,23 @@
"SKIP_ADD_LATER": "أضف لاحقًا",
"DAILYPLAN": "مخطط",
"INVITATION_SENT": "تم إرسال دعوة بنجاح",
"INVITATION_SENT_TO_USER": "تم إرسال دعوة فريق إلى {email}."
"INVITATION_SENT_TO_USER": "تم إرسال دعوة فريق إلى {email}.",
"CALENDAR": "تقويم",
"SELECT": "اختر",
"SAVE_CHANGES": "حفظ التغييرات",
"plan": {
"SINGULAR": "خطة",
"PLURAL": "خطط",
"CHOOSE_DATE": "اختر تاريخًا لتتمكن من رؤية خطة",
"PLAN_NOT_FOUND": "لم يتم العثور على خطة",
"FOR_DATE": "خطة لِـ {date}",
"FOR_TODAY": "خطة اليوم",
"FOR_TOMORROW": "خطة الغد",
"EDIT_PLAN": "تعديل الخطة",
"TRACKED_TIME": "الوقت المتعقب",
"SEE_PLANS": "عرض الخطط",
"ADD_PLAN": "إضافة خطة"
}
},
"hotkeys": {
"HELP": "مساعدة",
Expand Down Expand Up @@ -537,6 +553,7 @@
"WORK_TIME_PLANNED_PLACEHOLDER": "وقت العمل المخطط له اليوم",
"TASKS_WITH_NO_ESTIMATIONS": "تاكس مع عدم وجود تقديرات زمنية",
"START_WORKING_BUTTON": "ابدأ العمل",
"TIMER_RUNNING": "المؤقت قيد التشغيل بالفعل",
"WARNING_PLAN_ESTIMATION": "رجى تصحيح ساعات العمل المخطط لها أو إعادة تقدير المهمة (المهام)"
}
},
Expand Down
19 changes: 18 additions & 1 deletion apps/web/locales/bg.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,23 @@
"SKIP_ADD_LATER": "Добави по-късно",
"DAILYPLAN": "Планирано",
"INVITATION_SENT": "Покана е изпратена успешно",
"INVITATION_SENT_TO_USER": "Покана е изпратена на {email}."
"INVITATION_SENT_TO_USER": "Покана е изпратена на {email}.",
"CALENDAR": "Календар",
"SELECT": "Изберете",
"SAVE_CHANGES": "Запази промените",
"plan": {
"SINGULAR": "План",
"PLURAL": "Планове",
"CHOOSE_DATE": "Изберете дата, за да видите план",
"PLAN_NOT_FOUND": "Не е намерен план",
"FOR_DATE": "ПЛАН ЗА {date}",
"FOR_TODAY": "Днешният план",
"FOR_TOMORROW": "Утрешният план",
"EDIT_PLAN": "Редактиране на план",
"TRACKED_TIME": "Проследено време",
"SEE_PLANS": "Виж планове",
"ADD_PLAN": "Добави план"
}
},
"hotkeys": {
"HELP": "Помощ",
Expand Down Expand Up @@ -537,6 +553,7 @@
"WORK_TIME_PLANNED_PLACEHOLDER": "Планирано работно време за днес",
"TASKS_WITH_NO_ESTIMATIONS": "Такси без оценки на времето",
"START_WORKING_BUTTON": "Започнете работа",
"TIMER_RUNNING": "Таймерът вече работи",
"WARNING_PLAN_ESTIMATION": "Моля, коригирайте планираните работни часове или преоценете задачата(ите) "
}
},
Expand Down
19 changes: 18 additions & 1 deletion apps/web/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,23 @@
"SKIP_ADD_LATER": "Später hinzufügen",
"DAILYPLAN": "Plan dzienny",
"INVITATION_SENT": "Einladung erfolgreich gesendet",
"INVITATION_SENT_TO_USER": "Eine Teameinladung wurde an {email} gesendet."
"INVITATION_SENT_TO_USER": "Eine Teameinladung wurde an {email} gesendet.",
"CALENDAR": "Kalender",
"SELECT": "Auswählen",
"SAVE_CHANGES": "Änderungen speichern",
"plan": {
"SINGULAR": "Plan",
"PLURAL": "Pläne",
"CHOOSE_DATE": "Wählen Sie ein Datum, um einen Plan zu sehen",
"PLAN_NOT_FOUND": "Plan nicht gefunden",
"FOR_DATE": "PLAN FÜR {date}",
"FOR_TODAY": "Heutiger Plan",
"FOR_TOMORROW": "Morgenplan",
"EDIT_PLAN": "Plan bearbeiten",
"TRACKED_TIME": "Verfolgte Zeit",
"SEE_PLANS": "Pläne anzeigen",
"ADD_PLAN": "Plan hinzufügen"
}
},
"hotkeys": {
"HELP": "Hilfe",
Expand Down Expand Up @@ -537,6 +553,7 @@
"WORK_TIME_PLANNED_PLACEHOLDER": "Für heute geplante Arbeitszeit",
"TASKS_WITH_NO_ESTIMATIONS": "Taks ohne Zeitvorgaben",
"START_WORKING_BUTTON": "Mit der Arbeit beginnen",
"TIMER_RUNNING": "Der Timer läuft bereits",
"WARNING_PLAN_ESTIMATION": "Bitte korrigieren Sie die geplanten Arbeitsstunden oder schätzen Sie die Aufgabe(n) neu ein."
}
},
Expand Down
Loading

0 comments on commit 46e01fd

Please sign in to comment.