Skip to content

Commit

Permalink
feat(#159): make tracker cards sortable
Browse files Browse the repository at this point in the history
  • Loading branch information
Clm-Roig committed Jun 27, 2022
1 parent 2135ebd commit b0c4023
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 18 deletions.
6 changes: 4 additions & 2 deletions src/components/TrackerCard/TrackerCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Card, CardProps } from '@mui/material';
import { FC, useState } from 'react';
import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd';

import Completion from '../../models/Completion';
import Tracker from '../../models/Tracker';
Expand All @@ -8,9 +9,10 @@ import TrackerCardContent from './TrackerCardContent';
import TrackerCardHeader from './TrackerCardHeader';

interface Props extends CardProps {
dragHandleProps?: DraggableProvidedDragHandleProps;
tracker: Tracker;
}
const TrackerCard: FC<Props> = ({ tracker, ...cardProps }) => {
const TrackerCard: FC<Props> = ({ dragHandleProps, tracker, ...cardProps }) => {
const { requiredCompletions } = tracker;
const [selectedCompletions, setSelectedCompletions] = useState<Completion[]>([]);
const sxProps = { px: 1, py: 1 };
Expand All @@ -33,7 +35,7 @@ const TrackerCard: FC<Props> = ({ tracker, ...cardProps }) => {

return (
<Card {...cardProps}>
<TrackerCardHeader tracker={tracker} sx={sxProps} />
<TrackerCardHeader dragHandleProps={dragHandleProps} tracker={tracker} sx={sxProps} />
{requiredCompletions.length > 0 && (
<TrackerCardContent
sx={{ sxProps }}
Expand Down
18 changes: 16 additions & 2 deletions src/components/TrackerCard/TrackerCardHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ArchiveIcon from '@mui/icons-material/Archive';
import DeleteIcon from '@mui/icons-material/Delete';
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import EditIcon from '@mui/icons-material/Edit';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import {
Expand All @@ -10,11 +11,13 @@ import {
ListItemText,
Menu,
MenuItem,
Stack,
Typography
} from '@mui/material';
import { isAfter } from 'date-fns';
import { useSnackbar } from 'notistack';
import React, { FC, useState } from 'react';
import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd';

import { useAppDispatch } from '../../hooks/redux';
import Tracker from '../../models/Tracker';
Expand All @@ -25,10 +28,11 @@ import Emoji from '../Emoji/Emoji';
import TrackerEditDialog from '../TrackerEditDialog/TrackerEditDialog';

interface Props extends CardHeaderProps {
dragHandleProps?: DraggableProvidedDragHandleProps;
tracker: Tracker;
}

const TrackerCardHeader: FC<Props> = ({ tracker, ...cardHeaderProps }) => {
const TrackerCardHeader: FC<Props> = ({ dragHandleProps, tracker, ...cardHeaderProps }) => {
const { beginDate, frequency, id, name, remainingDays, status } = tracker;
const dispatch = useAppDispatch();
const { enqueueSnackbar } = useSnackbar();
Expand Down Expand Up @@ -82,6 +86,7 @@ const TrackerCardHeader: FC<Props> = ({ tracker, ...cardHeaderProps }) => {
<>
<CardHeader
{...cardHeaderProps}
disableTypography
action={
<>
<IconButton aria-label="tracker-settings" onClick={handleMoreActionsClick}>
Expand Down Expand Up @@ -116,7 +121,16 @@ const TrackerCardHeader: FC<Props> = ({ tracker, ...cardHeaderProps }) => {
</Menu>
</>
}
title={name}
title={
<Stack direction="row" alignItems="center" gap={1}>
{dragHandleProps && (
<span {...dragHandleProps}>
<DragIndicatorIcon sx={{ fontSize: 18 }} aria-label="tracker-drag-handle" />
</span>
)}
<Typography variant="h5">{name}</Typography>
</Stack>
}
subheader={
<>
<Typography display="block" variant="subtitle2">
Expand Down
50 changes: 44 additions & 6 deletions src/components/TrackerCardList/TrackerCardList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useAutoAnimate } from '@formkit/auto-animate/react';
import { CardProps, useTheme } from '@mui/material';
import { FC } from 'react';
import { DragDropContext, DragUpdate, Draggable, Droppable } from 'react-beautiful-dnd';

import { useAppSelector } from '../../hooks/redux';
import { useAppDispatch, useAppSelector } from '../../hooks/redux';
import Tracker from '../../models/Tracker';
import { selectThemeMode } from '../../store/theme/theme.selectors';
import { orderTracker } from '../../store/trackers/trackersSlice';
import TrackerCard from '../TrackerCard/TrackerCard';
import defaultCardProps from '../TrackerCard/defaultCardProps';

Expand All @@ -16,17 +18,53 @@ interface Props {
const TrackerCardList: FC<Props> = ({ trackers, cardProps }) => {
const themeMode = useAppSelector(selectThemeMode);
const theme = useTheme();
const dispatch = useAppDispatch();
const [animateRef] = useAutoAnimate<HTMLDivElement>();
const allCardProps = {
...defaultCardProps(themeMode, theme),
...cardProps
};

const handleOnDragUpdate = (updateResult: DragUpdate) => {
const { draggableId: draggedTrackerId, destination } = updateResult;
if (destination) {
const { index: destinationIndex } = destination;
const destinationTrackerId = trackers[destinationIndex].id;
dispatch(orderTracker({ sourceTrackerId: draggedTrackerId, destinationTrackerId }));
}
};

return (
<div ref={animateRef}>
{trackers.map((t) => (
<TrackerCard tracker={t} key={t.id} {...allCardProps} />
))}
</div>
<DragDropContext
onDragUpdate={handleOnDragUpdate}
onDragEnd={() => {
// onDragUpdate already updated the state
return;
}}>
<Droppable droppableId="trackerCardList">
{(provided) => (
<div ref={animateRef}>
<div {...provided.droppableProps} ref={provided.innerRef}>
{trackers.map((t, idx) => (
<Draggable key={t.id} draggableId={t.id} index={idx}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps}>
<TrackerCard
dragHandleProps={provided.dragHandleProps}
tracker={t}
key={t.id}
{...allCardProps}
/>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
</div>
)}
</Droppable>
</DragDropContext>
);
};

Expand Down
6 changes: 6 additions & 0 deletions src/config/CustomTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,11 @@ export const typography = {
fontFamily: ['Poppins', 'sans-serif'].join(','),
h1: {
fontSize: '4.3rem'
},
caption: {
color: grey[700]
},
subtitle2: {
color: grey[700]
}
};
6 changes: 3 additions & 3 deletions src/store/trackers/reducers/__tests__/order.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('trackers reducer', () => {
...initialState,
trackers: [testTracker1, testTracker2, testTracker3]
},
orderTracker({ trackerIdSource: testTracker1Id, trackerIdDestination: testTracker1Id })
orderTracker({ sourceTrackerId: testTracker1Id, destinationTrackerId: testTracker1Id })
);
const { trackers: trackers } = finalState;
expect(trackers[0].id).toBe(testTracker1Id);
Expand All @@ -29,7 +29,7 @@ describe('trackers reducer', () => {
...initialState,
trackers: [testTracker1, testTracker2, testTracker3]
},
orderTracker({ trackerIdSource: testTracker1Id, trackerIdDestination: testTracker3Id })
orderTracker({ sourceTrackerId: testTracker1Id, destinationTrackerId: testTracker3Id })
);
const { trackers: trackers } = finalState;
expect(trackers[0].id).toBe(testTracker2Id);
Expand All @@ -42,7 +42,7 @@ describe('trackers reducer', () => {
...initialState,
trackers: [testTracker1, testTracker2, testTracker3]
},
orderTracker({ trackerIdSource: testTracker3Id, trackerIdDestination: testTracker1Id })
orderTracker({ sourceTrackerId: testTracker3Id, destinationTrackerId: testTracker1Id })
);
const { trackers: trackers } = finalState;
expect(trackers[0].id).toBe(testTracker3Id);
Expand Down
10 changes: 5 additions & 5 deletions src/store/trackers/reducers/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { PayloadAction } from '@reduxjs/toolkit';
import TrackersState from '../TrackersState';

export type Indexes = {
trackerIdSource: string;
trackerIdDestination: string;
sourceTrackerId: string;
destinationTrackerId: string;
};

const orderTrackerReducer = (state: TrackersState, action: PayloadAction<Indexes>) => {
const { trackerIdSource, trackerIdDestination } = action.payload;
const sourceIndex = state.trackers.findIndex((t) => t.id === trackerIdSource);
const destinationIndex = state.trackers.findIndex((t) => t.id === trackerIdDestination);
const { sourceTrackerId, destinationTrackerId } = action.payload;
const sourceIndex = state.trackers.findIndex((t) => t.id === sourceTrackerId);
const destinationIndex = state.trackers.findIndex((t) => t.id === destinationTrackerId);
if (sourceIndex !== -1 && destinationIndex !== -1) {
const tracker = state.trackers.splice(sourceIndex, 1)[0];
state.trackers.splice(destinationIndex, 0, tracker);
Expand Down

0 comments on commit b0c4023

Please sign in to comment.