Skip to content

Commit

Permalink
Merge pull request #2324 from zetkin/release-241101
Browse files Browse the repository at this point in the history
241101 Release
  • Loading branch information
richardolsson authored Nov 1, 2024
2 parents 7ab2f66 + a83e218 commit 20b1c9b
Show file tree
Hide file tree
Showing 26 changed files with 400 additions and 70 deletions.
17 changes: 11 additions & 6 deletions .github/ISSUE_TEMPLATE/zui.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,19 @@ You need to be logged into a Figma account to properly view the Figma content.

## Open questions

## Possible implementations
## Workflow

Develop this using Storybook. We want all the design system components to be documented through their own Storybook stories.

For reference, you can look at the already existing ZUI components created on the `undocumented/new-design-system` branch (not ZUI components that exist on `main`, as they are not part of the new design system.)

## Git
### Git

The main git branch for the work on the new design system is `undocumented/new-design-system`. Unless otherwise instructed, do your work on a new branch branched off from this branch.

Name your branch `issue-number/zui-name`, ex: `issue-928/zui-button` for a branch where work is done that is documented in the issue with number 928, where a button component is being made.

### Storybook

Use [Storybook](https://storybook.js.org/) to develop the new design system components. If you are not familiar with working with Storybook, please ask and Ziggi or someone else will be happy to introduce you!
When you have checked out the branch `undocumented/new-design-system` (and, as always when checking out a branch just to be sure, run `yarn install`), run `yarn storybook` in the terminal. This starts Storybook locally, and should open your browser to `localhost:6006` where you see all the components. Note that you want to only look at the ones under the "New design system" headline.

### Files

Create a folder in `src/zui/components` and give it a name for your component, like `ZUIButton`. Inside that folder, create one file `index.tsx` (this is where you write your component) and one `index.stories.tsx` (this is where you write your Storybook stories). Look at the components in `src/zui/components` for inspiration/reference! Note that there are lots of components with names that start with "ZUI" outside the `src/zui/components` folder, but only the ones in `src/zui/components` are relevant as reference/inspiration for the work you will be doing.
10 changes: 10 additions & 0 deletions src/features/callAssignments/hooks/useCallAssignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CallAssignmentData } from '../apiTypes';
import { futureToObject } from 'core/caching/futures';
import { loadItemIfNecessary } from 'core/caching/cacheUtils';
import {
callAssignmentDeleted,
callAssignmentLoad,
callAssignmentLoaded,
callAssignmentUpdate,
Expand All @@ -23,6 +24,7 @@ interface UseCallAssignmentReturn {
updateTargets: (query: Partial<ZetkinQuery>) => void;
start: () => void;
updateCallAssignment: (data: Partial<ZetkinCallAssignment>) => void;
deleteAssignment: () => void;
}

export default function useCallAssignment(
Expand Down Expand Up @@ -186,8 +188,16 @@ export default function useCallAssignment(
});
};

const deleteAssignment = async () => {
await apiClient.delete(
`/api/orgs/${orgId}/call_assignments/${assignmentId}`
);
dispatch(callAssignmentDeleted(assignmentId));
};

return {
...futureToObject(callAssignmentFuture),
deleteAssignment,
end,
isTargeted,
start,
Expand Down
2 changes: 2 additions & 0 deletions src/features/callAssignments/l10n/messageIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { m, makeMessages } from 'core/i18n';

export default makeMessages('feat.callAssignments', {
actions: {
delete: m('Delete'),
end: m('End assignment'),
start: m('Start assignment'),
warning: m<{ title: string }>('"{title}" will be deleted.'),
},
blocked: {
callBackLater: m('Asked us to call back later'),
Expand Down
30 changes: 29 additions & 1 deletion src/features/callAssignments/layout/CallAssignmentLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useContext } from 'react';
import { Box, Button, Typography } from '@mui/material';
import { Headset, People } from '@mui/icons-material';
import { Delete, Headset, People } from '@mui/icons-material';
import { useRouter } from 'next/router';

import CallAssignmentStatusChip from '../components/CallAssignmentStatusChip';
import getCallAssignmentUrl from '../utils/getCallAssignmentUrl';
Expand All @@ -16,6 +18,7 @@ import { Msg, useMessages } from 'core/i18n';
import useCallAssignmentState, {
CallAssignmentState,
} from '../hooks/useCallAssignmentState';
import { ZUIConfirmDialogContext } from 'zui/ZUIConfirmDialogProvider';

interface CallAssignmentLayoutProps {
children: React.ReactNode;
Expand All @@ -26,17 +29,27 @@ const CallAssignmentLayout: React.FC<CallAssignmentLayoutProps> = ({
}) => {
const messages = useMessages(messageIds);
const { orgId, callAssId } = useNumericRouteParams();
const router = useRouter();
const { showConfirmDialog } = useContext(ZUIConfirmDialogContext);

const {
data: callAssignment,
end,
start,
updateCallAssignment,
deleteAssignment,
} = useCallAssignment(orgId, callAssId);
const { statsFuture } = useCallAssignmentStats(orgId, callAssId);
const { filteredCallersFuture } = useCallers(orgId, callAssId);
const state = useCallAssignmentState(orgId, callAssId);

const handleDelete = () => {
deleteAssignment();
router.push(
`/organize/${orgId}/projects/${callAssignment?.campaign?.id || ''} `
);
};

if (!callAssignment) {
return null;
}
Expand Down Expand Up @@ -66,6 +79,21 @@ const CallAssignmentLayout: React.FC<CallAssignmentLayoutProps> = ({
/>
}
defaultTab="/"
ellipsisMenuItems={[
{
label: messages.actions.delete(),
onSelect: () => {
showConfirmDialog({
onSubmit: handleDelete,
title: messages.actions.delete(),
warningText: messages.actions.warning({
title: callAssignment.title,
}),
});
},
startIcon: <Delete />,
},
]}
subtitle={
<Box alignItems="center" display="flex">
<Box marginRight={1}>
Expand Down
8 changes: 8 additions & 0 deletions src/features/callAssignments/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ const callAssignmentsSlice = createSlice({
]);
}
},
callAssignmentDeleted: (state, action: PayloadAction<number>) => {
const id = action.payload;
const item = state.assignmentList.items.find((item) => item.id == id);
if (item) {
item.deleted;
}
},
callAssignmentLoad: (state, action: PayloadAction<number>) => {
const id = action.payload;
const item = state.assignmentList.items.find((item) => item.id == id);
Expand Down Expand Up @@ -291,6 +298,7 @@ export default callAssignmentsSlice;
export const {
callAssignmentCreate,
callAssignmentCreated,
callAssignmentDeleted,
callAssignmentLoad,
callAssignmentLoaded,
callAssignmentUpdate,
Expand Down
4 changes: 2 additions & 2 deletions src/features/duplicates/components/DuplicateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Box, Button, Paper, Typography } from '@mui/material';
import { FC, useContext, useState } from 'react';

import theme from 'theme';
import ConfigureModal from './ConfigureModal';
import PotentialDuplicateModal from './PotentialDuplicateModal';
import messageIds from '../l10n/messageIds';
import { PotentialDuplicate } from '../store';
import useDuplicatesMutations from '../hooks/useDuplicatesMutations';
Expand Down Expand Up @@ -64,7 +64,7 @@ const DuplicateCard: FC<DuplicateCardProps> = ({ cluster }) => {
</Box>
</Box>
</Paper>
<ConfigureModal
<PotentialDuplicateModal
onClose={() => setOpenModal(false)}
open={openModal}
potentialDuplicate={cluster}
Expand Down
33 changes: 33 additions & 0 deletions src/features/duplicates/components/ManualMergingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { FC } from 'react';
import React from 'react';

import MergeModal from './MergeModal';
import { ZetkinPerson } from 'utils/types/zetkin';
import useMergePersons from '../hooks/useMergePersons';
import { useNumericRouteParams } from 'core/hooks';

interface Props {
initialPersons: ZetkinPerson[];
onClose: () => void;
open: boolean;
}

const ManualMergingModal: FC<Props> = ({ initialPersons, open, onClose }) => {
const { orgId } = useNumericRouteParams();
const mergePersons = useMergePersons(orgId);

return (
<MergeModal
initiallyShowManualSearch
onClose={onClose}
onMerge={(personIds, overrides) => {
mergePersons(personIds, overrides);
onClose();
}}
open={open}
persons={initialPersons}
/>
);
};

export default ManualMergingModal;
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,40 @@ import React, { useEffect } from 'react';
import theme from 'theme';
import FieldSettings from './FieldSettings';
import messageIds from '../l10n/messageIds';
import { PotentialDuplicate } from '../store';
import PotentialDuplicatesLists from './PotentialDuplicatesLists';
import useDuplicatesMutations from '../hooks/useDuplicatesMutations';
import useFieldSettings from '../hooks/useFieldSettings';
import { useMessages } from 'core/i18n';
import { useNumericRouteParams } from 'core/hooks';
import { ZetkinPerson } from 'utils/types/zetkin';

interface ConfigureModalProps {
potentialDuplicate: PotentialDuplicate;
type Props = {
initiallyShowManualSearch?: boolean;
onClose: () => void;
onMerge: (personIds: number[], overrides: Partial<ZetkinPerson>) => void;
open: boolean;
}
persons: ZetkinPerson[];
};

const ConfigureModal: FC<ConfigureModalProps> = ({
potentialDuplicate,
const MergeModal: FC<Props> = ({
initiallyShowManualSearch = false,
open,
onClose,
onMerge,
persons,
}) => {
const { orgId } = useNumericRouteParams();
const fullScreen = useMediaQuery(theme.breakpoints.down('md'));
const messages = useMessages(messageIds);
const { mergeDuplicate } = useDuplicatesMutations(orgId);
const [additionalPeople, setAdditionalPeople] = useState<ZetkinPerson[]>([]);

const [selectedIds, setSelectedIds] = useState<number[]>(
potentialDuplicate?.duplicates.map((person) => person.id) ?? []
persons.map((person) => person.id) ?? []
);

const peopleToMerge = potentialDuplicate?.duplicates.filter((person) =>
selectedIds.includes(person.id)
);
const peopleToMerge = [
...persons.filter((person) => selectedIds.includes(person.id)),
...additionalPeople,
];

const peopleNotToMerge = potentialDuplicate?.duplicates.filter(
const peopleNotToMerge = persons.filter(
(person) => !selectedIds.includes(person.id)
);

Expand All @@ -55,28 +56,45 @@ const ConfigureModal: FC<ConfigureModalProps> = ({
const [overrides, setOverrides] = useState(initialOverrides);

useEffect(() => {
setSelectedIds(
potentialDuplicate?.duplicates.map((person) => person.id) ?? []
);
setSelectedIds(persons.map((person) => person.id) ?? []);
}, [open]);

return (
<Dialog fullScreen={fullScreen} maxWidth={'lg'} open={open}>
<Dialog fullScreen={fullScreen} fullWidth maxWidth="lg" open={open}>
<DialogTitle sx={{ paddingLeft: 2 }} variant="h5">
{messages.modal.title()}
</DialogTitle>
<Box display="flex" flexGrow={1} overflow="hidden">
<Box paddingX={2} sx={{ overflowY: 'auto' }} width="50%">
<PotentialDuplicatesLists
initiallyShowManualSearch={initiallyShowManualSearch}
onDeselect={(person: ZetkinPerson) => {
const filteredIds = selectedIds.filter(
(item) => item !== person.id
const isPredefined = persons.some(
(predefinedPerson) => predefinedPerson.id == person.id
);
setSelectedIds(filteredIds);

if (isPredefined) {
const filteredIds = selectedIds.filter(
(item) => item !== person.id
);
setSelectedIds(filteredIds);
} else {
const filteredAdditionals = additionalPeople.filter(
(item) => item.id != person.id
);
setAdditionalPeople(filteredAdditionals);
}
}}
onSelect={(person: ZetkinPerson) => {
const selectedIdsUpdated = [...selectedIds, person.id];
setSelectedIds(selectedIdsUpdated);
const isPredefined = persons.some(
(predefinedPerson) => predefinedPerson.id == person.id
);
if (isPredefined) {
const selectedIdsUpdated = [...selectedIds, person.id];
setSelectedIds(selectedIdsUpdated);
} else {
setAdditionalPeople([...additionalPeople, person]);
}
}}
peopleNotToMerge={peopleNotToMerge}
peopleToMerge={peopleToMerge}
Expand Down Expand Up @@ -106,14 +124,27 @@ const ConfigureModal: FC<ConfigureModalProps> = ({
</Box>
</Box>
<DialogActions sx={{ p: 2 }}>
<Button onClick={() => onClose()} variant="text">
<Button
onClick={() => {
setAdditionalPeople([]);
onClose();
}}
variant="text"
>
{messages.modal.cancelButton()}
</Button>
<Button
disabled={selectedIds.length > 1 ? false : true}
onClick={() =>
mergeDuplicate(potentialDuplicate.id, selectedIds, overrides)
disabled={
additionalPeople.length + selectedIds.length > 1 ? false : true
}
onClick={() => {
const idSet = new Set([
...selectedIds,
...additionalPeople.map((person) => person.id),
]);
onMerge(Array.from(idSet), overrides);
setAdditionalPeople([]);
}}
variant="contained"
>
{messages.modal.mergeButton()}
Expand All @@ -123,4 +154,4 @@ const ConfigureModal: FC<ConfigureModalProps> = ({
);
};

export default ConfigureModal;
export default MergeModal;
35 changes: 35 additions & 0 deletions src/features/duplicates/components/PotentialDuplicateModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { FC } from 'react';
import React from 'react';

import { PotentialDuplicate } from '../store';
import useDuplicatesMutations from '../hooks/useDuplicatesMutations';
import { useNumericRouteParams } from 'core/hooks';
import MergeModal from './MergeModal';

interface Props {
onClose: () => void;
open: boolean;
potentialDuplicate: PotentialDuplicate;
}

const PotentialDuplicateModal: FC<Props> = ({
potentialDuplicate,
open,
onClose,
}) => {
const { orgId } = useNumericRouteParams();
const { mergeDuplicate } = useDuplicatesMutations(orgId);

return (
<MergeModal
onClose={onClose}
onMerge={(personIds, overrides) => {
mergeDuplicate(potentialDuplicate.id, personIds, overrides);
}}
open={open}
persons={potentialDuplicate.duplicates}
/>
);
};

export default PotentialDuplicateModal;
Loading

0 comments on commit 20b1c9b

Please sign in to comment.