Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Task] Daily Plan | Drag and Drop #2743

Merged
merged 7 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions apps/web/app/helpers/drag-and-drop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { IDailyPlan, ITeamTask } from "@app/interfaces";
import { DropResult } from "react-beautiful-dnd";

export const handleDragAndDrop = (results: DropResult, plans: IDailyPlan[], setPlans: React.Dispatch<React.SetStateAction<IDailyPlan[]>>) => {
const { source, destination } = results;

if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return;

const newPlans = [...plans];

const planSourceIndex = newPlans.findIndex(plan => plan.id === source.droppableId);
const planDestinationIndex = newPlans.findIndex(plan => plan.id === destination.droppableId);

const newSourceTasks = [...newPlans[planSourceIndex].tasks!];
const newDestinationTasks = source.droppableId !== destination.droppableId
? [...newPlans[planDestinationIndex].tasks!]
: newSourceTasks;

const [deletedTask] = newSourceTasks.splice(source.index, 1);
newDestinationTasks.splice(destination.index, 0, deletedTask);

newPlans[planSourceIndex] = {
...newPlans[planSourceIndex],
tasks: newSourceTasks,
};
newPlans[planDestinationIndex] = {
...newPlans[planDestinationIndex],
tasks: newDestinationTasks,
};
setPlans(newPlans);
};


export const handleDragAndDropDailyOutstandingAll = (
results: DropResult,
tasks: ITeamTask[],
setTasks: React.Dispatch<React.SetStateAction<ITeamTask[]>>
) => {
const { source, destination } = results;

if (!destination || (source.droppableId === destination.droppableId && source.index === destination.index)) return;

const newTasks = [...tasks];
const [movedTask] = newTasks.splice(source.index, 1);
newTasks.splice(destination.index, 0, movedTask);

setTasks(newTasks);
};
1 change: 1 addition & 0 deletions apps/web/app/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './validations';
export * from './colors';
export * from './strings';
export * from './plan-day-badge';
export * from './drag-and-drop'
8 changes: 4 additions & 4 deletions apps/web/app/interfaces/IDailyPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export interface IDailyPlanBase extends IBasePerTenantAndOrganizationEntity {
status: DailyPlanStatusEnum;
}

export class IRemoveTaskFromManyPlans {
export interface IRemoveTaskFromManyPlans {
employeeId?: IEmployee['id'];
plansIds?: IDailyPlan['id'][];
organizationId?: IOrganization['id']
organizationId?: IOrganization['id'];
}

export interface IDailyPlan extends IDailyPlanBase, IRelationnalEmployee {
Expand All @@ -23,11 +23,11 @@ export interface ICreateDailyPlan extends IDailyPlanBase, IRelationnalEmployee {
taskId?: ITeamTask['id'];
}

export interface IUpdateDailyPlan extends Partial<IDailyPlanBase>, Pick<ICreateDailyPlan, 'employeeId'> { }
export interface IUpdateDailyPlan extends Partial<IDailyPlanBase>, Pick<ICreateDailyPlan, 'employeeId'> {}

export interface IDailyPlanTasksUpdate
extends Pick<ICreateDailyPlan, 'taskId' | 'employeeId'>,
IBasePerTenantAndOrganizationEntity { }
IBasePerTenantAndOrganizationEntity {}

export enum DailyPlanStatusEnum {
OPEN = 'open',
Expand Down
240 changes: 138 additions & 102 deletions apps/web/lib/features/task/daily-plan/future-tasks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { formatDayPlanDate, tomorrowDate } from '@app/helpers';
import { formatDayPlanDate, handleDragAndDrop, tomorrowDate } from '@app/helpers';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@components/ui/accordion';
import { EmptyPlans, PlanHeader } from 'lib/features/user-profile-plans';
import { TaskCard } from '../task-card';
Expand All @@ -13,123 +13,159 @@ import { HorizontalSeparator } from 'lib/components';
import { useState } from 'react';
import { AlertPopup } from 'lib/components';
import { useFilterDateRange } from '@app/hooks/useFilterDateRange';
import { IDailyPlan } from '@app/interfaces';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';

export function FutureTasks({ profile }: { profile: any }) {
const { deleteDailyPlan, deleteDailyPlanLoading, futurePlans } = useDailyPlan();
const canSeeActivity = useCanSeeActivityScreen();
const [popupOpen, setPopupOpen] = useState(false);
const { filteredFuturePlanData: filteredFuturePlanData } = useFilterDateRange(futurePlans, 'future');
const [currentDeleteIndex, setCurrentDeleteIndex] = useState(0);
const [futureDailyPlanTasks, setFutureDailyPlanTasks] = useState<IDailyPlan[]>(filteredFuturePlanData);

const view = useRecoilValue(dailyPlanViewHeaderTabs);

return (
<div className="flex flex-col gap-6">
{filteredFuturePlanData.length > 0 ? (
<Accordion
type="multiple"
className="text-sm"
defaultValue={[tomorrowDate.toISOString().split('T')[0]]}
{futureDailyPlanTasks?.length > 0 ? (
<DragDropContext
onDragEnd={(result) => handleDragAndDrop(result, futureDailyPlanTasks, setFutureDailyPlanTasks)}
>
{filteredFuturePlanData.map((plan, index) => (
<AccordionItem
value={plan.date.toString().split('T')[0]}
key={plan.id}
className="dark:border-slate-600 !border-none"
>
<AccordionTrigger className="!min-w-full text-start hover:no-underline">
<div className="flex items-center justify-between gap-3 w-full">
<div className="text-lg min-w-max">
{formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length})
<Accordion
type="multiple"
className="text-sm"
defaultValue={[tomorrowDate.toISOString().split('T')[0]]}
>
{futureDailyPlanTasks.map((plan, index) => (
<AccordionItem
value={plan.date.toString().split('T')[0]}
key={plan.id}
className="dark:border-slate-600 !border-none"
>
<AccordionTrigger className="!min-w-full text-start hover:no-underline">
<div className="flex items-center justify-between gap-3 w-full">
<div className="text-lg min-w-max">
{formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length})
</div>
<HorizontalSeparator />
</div>
<HorizontalSeparator />
</div>
</AccordionTrigger>
<AccordionContent className="bg-light--theme border-none dark:bg-dark--theme">
{/* Plan header */}
<PlanHeader plan={plan} planMode="Outstanding" />

{/* Plan tasks list */}
<ul
className={clsxm(
view === 'CARDS' && 'flex-col',
view === 'TABLE' && 'flex-wrap',
'flex gap-2 pb-[1.5rem]',
view === 'BLOCKS' && 'overflow-x-scroll'
)}
>
{plan.tasks?.map((task) =>
view === 'CARDS' ? (
<TaskCard
key={`${task.id}${plan.id}`}
isAuthUser={true}
activeAuthTask={true}
viewType={'dailyplan'}
task={task}
profile={profile}
type="HORIZONTAL"
taskBadgeClassName={`rounded-sm`}
taskTitleClassName="mt-[0.0625rem]"
plan={plan}
planMode="Future Tasks"
/>
) : (
<TaskBlockCard key={task.id} task={task} />
)
)}
</ul>

{/* Delete Plan */}
{canSeeActivity ? (
<div className="flex justify-end">
<AlertPopup
open={currentDeleteIndex === index && popupOpen}
buttonOpen={
//button open popup
<Button
onClick={() => {
setPopupOpen(prev => !prev)
setCurrentDeleteIndex(index)
}}
variant="outline"
className="px-4 py-2 text-sm font-medium text-red-600 border border-red-600 rounded-md bg-light--theme-light dark:!bg-dark--theme-light"
>
Delete this plan
</Button>

}
>
{/*button confirm*/}
<Button
disabled={deleteDailyPlanLoading}
onClick={() => {
deleteDailyPlan(plan.id ?? '')
}}
variant="destructive"
className="flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700 disabled:bg-red-400"
>
{deleteDailyPlanLoading && (
<ReloadIcon className="animate-spin mr-2 h-4 w-4" />
</AccordionTrigger>
<AccordionContent className="bg-light--theme border-none dark:bg-dark--theme">
<PlanHeader plan={plan} planMode="Outstanding" />
<Droppable droppableId={plan.id as string} key={plan.id} type="task">
{(provided) => (
<ul
ref={provided.innerRef}
{...provided.droppableProps}
className={clsxm(
view === 'CARDS' && 'flex-col',
view === 'TABLE' && 'flex-wrap',
'flex gap-2 pb-[1.5rem]',
view === 'BLOCKS' && 'overflow-x-scroll'
)}
Delete
</Button>
{/*button cancel*/}
<Button
onClick={() => setPopupOpen(false)}
variant="outline"
className="px-4 py-2 text-sm font-medium text-red-600 border border-red-600 rounded-md bg-light--theme-light dark:!bg-dark--theme-light"
>
Cancel
</Button>
</AlertPopup>
</div>
) : (
<></>
)}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
{plan.tasks?.map((task, index) =>
view === 'CARDS' ? (
<Draggable key={task.id} draggableId={task.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
marginBottom: 8
}}
>
<TaskCard
key={`${task.id}${plan.id}`}
isAuthUser={true}
activeAuthTask={true}
viewType={'dailyplan'}
task={task}
profile={profile}
type="HORIZONTAL"
taskBadgeClassName={`rounded-sm`}
taskTitleClassName="mt-[0.0625rem]"
plan={plan}
planMode="Future Tasks"
/>
</div>
)}
</Draggable>
) : (
<Draggable key={task.id} draggableId={task.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{
...provided.draggableProps.style,
marginBottom: 8
}}
>
<TaskBlockCard task={task} />
</div>
)}
</Draggable>
)
)}
<>{provided.placeholder}</>
{canSeeActivity ? (
<div className="flex justify-end">
<AlertPopup
open={currentDeleteIndex === index && popupOpen}
buttonOpen={
//button open popup
<Button
onClick={() => {
setPopupOpen((prev) => !prev);
setCurrentDeleteIndex(index);
}}
variant="outline"
className="px-4 py-2 text-sm font-medium text-red-600 border border-red-600 rounded-md bg-light--theme-light dark:!bg-dark--theme-light"
>
Delete this plan
</Button>
}
>
{/*button confirm*/}
<Button
disabled={deleteDailyPlanLoading}
onClick={() => {
deleteDailyPlan(plan.id ?? '');
}}
variant="destructive"
className="flex items-center justify-center px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-md hover:bg-red-700 disabled:bg-red-400"
>
{deleteDailyPlanLoading && (
<ReloadIcon className="animate-spin mr-2 h-4 w-4" />
)}
Delete
</Button>
{/*button cancel*/}
<Button
onClick={() => setPopupOpen(false)}
variant="outline"
className="px-4 py-2 text-sm font-medium text-red-600 border border-red-600 rounded-md bg-light--theme-light dark:!bg-dark--theme-light"
>
Cancel
</Button>
</AlertPopup>
</div>
) : (
<></>
)}
</ul>
)}
</Droppable>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</DragDropContext>
) : (
<EmptyPlans planMode="Past Tasks" />
)}
Expand Down
Loading
Loading