From 6f1a96c44b273f3dee28260715c16d408d79d38f Mon Sep 17 00:00:00 2001
From: luxiaotao1123 <t1341870251@163.com>
Date: 星期五, 27 九月 2024 10:47:36 +0800
Subject: [PATCH] #
---
zy-acs-flow/src/page/mission/MissionColumn.jsx | 75 ++++++++++
zy-acs-flow/src/page/mission/MissionListContent.jsx | 242 ++++++++++++++++++++++++++++++++++
zy-acs-flow/src/page/mission/MissionCard.jsx | 78 +++++++++++
zy-acs-flow/src/page/mission/MissionEmpty.jsx | 6
zy-acs-manager/src/main/java/com/zy/acs/manager/manager/service/impl/MissionServiceImpl.java | 2
5 files changed, 399 insertions(+), 4 deletions(-)
diff --git a/zy-acs-flow/src/page/mission/MissionCard.jsx b/zy-acs-flow/src/page/mission/MissionCard.jsx
new file mode 100644
index 0000000..961aac4
--- /dev/null
+++ b/zy-acs-flow/src/page/mission/MissionCard.jsx
@@ -0,0 +1,78 @@
+import { Draggable } from '@hello-pangea/dnd';
+import { Box, Card, Typography } from '@mui/material';
+import { ReferenceField, useRedirect } from 'react-admin';
+import { CompanyAvatar } from '../companies/CompanyAvatar';
+import { Deal } from '../types';
+
+export const MissionCard = ({ deal, index }) => {
+ if (!deal) return null;
+
+ return (
+ <Draggable draggableId={String(deal.id)} index={index}>
+ {(provided, snapshot) => (
+ <DealCardContent
+ provided={provided}
+ snapshot={snapshot}
+ deal={deal}
+ />
+ )}
+ </Draggable>
+ );
+};
+
+export const DealCardContent = ({
+ provided,
+ snapshot,
+ deal,
+}) => {
+ const redirect = useRedirect();
+ const handleClick = () => {
+ redirect(`/deals/${deal.id}/show`, undefined, undefined, undefined, {
+ _scrollToTop: false,
+ });
+ };
+
+ return (
+ <Box
+ sx={{ marginBottom: 1, cursor: 'pointer' }}
+ {...provided?.draggableProps}
+ {...provided?.dragHandleProps}
+ ref={provided?.innerRef}
+ onClick={handleClick}
+ >
+ <Card
+ style={{
+ opacity: snapshot?.isDragging ? 0.9 : 1,
+ transform: snapshot?.isDragging ? 'rotate(-2deg)' : '',
+ }}
+ elevation={snapshot?.isDragging ? 3 : 1}
+ >
+ <Box padding={1} display="flex">
+ <ReferenceField
+ source="company_id"
+ record={deal}
+ reference="companies"
+ link={false}
+ >
+ <CompanyAvatar width={20} height={20} />
+ </ReferenceField>
+ <Box sx={{ marginLeft: 1 }}>
+ <Typography variant="body2" gutterBottom>
+ {deal.name}
+ </Typography>
+ <Typography variant="caption" color="textSecondary">
+ {deal.amount.toLocaleString('en-US', {
+ notation: 'compact',
+ style: 'currency',
+ currency: 'USD',
+ currencyDisplay: 'narrowSymbol',
+ minimumSignificantDigits: 3,
+ })}
+ {deal.category ? `, ${deal.category}` : ''}
+ </Typography>
+ </Box>
+ </Box>
+ </Card>
+ </Box>
+ );
+};
diff --git a/zy-acs-flow/src/page/mission/MissionColumn.jsx b/zy-acs-flow/src/page/mission/MissionColumn.jsx
new file mode 100644
index 0000000..bdb5aad
--- /dev/null
+++ b/zy-acs-flow/src/page/mission/MissionColumn.jsx
@@ -0,0 +1,75 @@
+import { Droppable } from '@hello-pangea/dnd';
+import { Box, Stack, Typography } from '@mui/material';
+
+import { Deal } from '../types';
+import { DealCard } from './MissionCard';
+import { useConfigurationContext } from '../root/ConfigurationContext';
+import { findDealLabel } from './deal';
+
+export const MissionColumn = ({ stage, deals, }) => {
+ const totalAmount = deals.reduce((sum, deal) => sum + deal.amount, 0);
+
+ const { dealStages } = useConfigurationContext();
+ return (
+ <Box
+ sx={{
+ flex: 1,
+ paddingTop: '8px',
+ paddingBottom: '16px',
+ bgcolor: '#eaeaee',
+ '&:first-of-type': {
+ paddingLeft: '5px',
+ borderTopLeftRadius: 5,
+ },
+ '&:last-of-type': {
+ paddingRight: '5px',
+ borderTopRightRadius: 5,
+ },
+ }}
+ >
+ <Stack alignItems="center">
+ <Typography variant="subtitle1">
+ {findDealLabel(dealStages, stage)}
+ </Typography>
+ <Typography
+ variant="subtitle1"
+ color="text.secondary"
+ fontSize="small"
+ >
+ {totalAmount.toLocaleString('en-US', {
+ notation: 'compact',
+ style: 'currency',
+ currency: 'USD',
+ currencyDisplay: 'narrowSymbol',
+ minimumSignificantDigits: 3,
+ })}
+ </Typography>
+ </Stack>
+ <Droppable droppableId={stage}>
+ {(droppableProvided, snapshot) => (
+ <Box
+ ref={droppableProvided.innerRef}
+ {...droppableProvided.droppableProps}
+ className={
+ snapshot.isDraggingOver ? ' isDraggingOver' : ''
+ }
+ sx={{
+ display: 'flex',
+ flexDirection: 'column',
+ borderRadius: 1,
+ padding: '5px',
+ '&.isDraggingOver': {
+ bgcolor: '#dadadf',
+ },
+ }}
+ >
+ {deals.map((deal, index) => (
+ <DealCard key={deal.id} deal={deal} index={index} />
+ ))}
+ {droppableProvided.placeholder}
+ </Box>
+ )}
+ </Droppable>
+ </Box>
+ );
+};
diff --git a/zy-acs-flow/src/page/mission/MissionEmpty.jsx b/zy-acs-flow/src/page/mission/MissionEmpty.jsx
index 84d66eb..222ca88 100644
--- a/zy-acs-flow/src/page/mission/MissionEmpty.jsx
+++ b/zy-acs-flow/src/page/mission/MissionEmpty.jsx
@@ -8,11 +8,11 @@
export const MissionEmpty = ({ children }) => {
const location = useLocation();
- const matchCreate = matchPath('/deals/create', location.pathname);
- const appbarHeight = useAppBarHeight();
+ const matchCreate = matchPath('/mission/create', location.pathname);
+ const appBarHeight = useAppBarHeight();
// get Contact data
- const { data: contacts, isPending: contactsLoading } = useGetList<Contact>(
+ const { data: contacts, isPending: contactsLoading } = useGetList < Contact > (
'contacts',
{
pagination: { page: 1, perPage: 1 },
diff --git a/zy-acs-flow/src/page/mission/MissionListContent.jsx b/zy-acs-flow/src/page/mission/MissionListContent.jsx
new file mode 100644
index 0000000..fad9b6c
--- /dev/null
+++ b/zy-acs-flow/src/page/mission/MissionListContent.jsx
@@ -0,0 +1,242 @@
+import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd';
+import { Box } from '@mui/material';
+import isEqual from 'lodash/isEqual';
+import { useEffect, useState } from 'react';
+import { DataProvider, useDataProvider, useListContext } from 'react-admin';
+
+import { Deal } from '../types';
+import { DealColumn } from './MissionColumn';
+import { DealsByStage, getDealsByStage } from './stages';
+import { useConfigurationContext } from '../root/ConfigurationContext';
+
+export const MissionListContent = () => {
+ const { dealStages } = useConfigurationContext();
+ const { data: unorderedDeals, isPending, refetch } = useListContext();
+ const dataProvider = useDataProvider();
+
+ const [dealsByStage, setDealsByStage] = useState(
+ getDealsByStage([], dealStages)
+ );
+
+ useEffect(() => {
+ if (unorderedDeals) {
+ const newDealsByStage = getDealsByStage(unorderedDeals, dealStages);
+ if (!isEqual(newDealsByStage, dealsByStage)) {
+ setDealsByStage(newDealsByStage);
+ }
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [unorderedDeals]);
+
+ if (isPending) return null;
+
+ const onDragEnd = result => {
+ const { destination, source } = result;
+
+ if (!destination) {
+ return;
+ }
+
+ if (
+ destination.droppableId === source.droppableId &&
+ destination.index === source.index
+ ) {
+ return;
+ }
+
+ const sourceStage = source.droppableId;
+ const destinationStage = destination.droppableId;
+ const sourceDeal = dealsByStage[sourceStage][source.index];
+ const destinationDeal = dealsByStage[destinationStage][
+ destination.index
+ ] ?? {
+ stage: destinationStage,
+ index: undefined, // undefined if dropped after the last item
+ };
+
+ // compute local state change synchronously
+ setDealsByStage(
+ updateDealStageLocal(
+ sourceDeal,
+ { stage: sourceStage, index: source.index },
+ { stage: destinationStage, index: destination.index },
+ dealsByStage
+ )
+ );
+
+ // persist the changes
+ updateDealStage(sourceDeal, destinationDeal, dataProvider).then(() => {
+ refetch();
+ });
+ };
+
+ return (
+ <DragDropContext onDragEnd={onDragEnd}>
+ <Box display="flex">
+ {dealStages.map(stage => (
+ <DealColumn
+ stage={stage.value}
+ deals={dealsByStage[stage.value]}
+ key={stage.value}
+ />
+ ))}
+ </Box>
+ </DragDropContext>
+ );
+};
+
+const updateDealStageLocal = (
+ sourceDeal,
+ source,
+ destination,
+ dealsByStage
+) => {
+ if (source.stage === destination.stage) {
+ // moving deal inside the same column
+ const column = dealsByStage[source.stage];
+ column.splice(source.index, 1);
+ column.splice(destination.index ?? column.length + 1, 0, sourceDeal);
+ return {
+ ...dealsByStage,
+ [destination.stage]: column,
+ };
+ } else {
+ // moving deal across columns
+ const sourceColumn = dealsByStage[source.stage];
+ const destinationColumn = dealsByStage[destination.stage];
+ sourceColumn.splice(source.index, 1);
+ destinationColumn.splice(
+ destination.index ?? destinationColumn.length + 1,
+ 0,
+ sourceDeal
+ );
+ return {
+ ...dealsByStage,
+ [source.stage]: sourceColumn,
+ [destination.stage]: destinationColumn,
+ };
+ }
+};
+
+const updateDealStage = async (
+ source,
+ destination,
+ dataProvider
+) => {
+ if (source.stage === destination.stage) {
+ // moving deal inside the same column
+ // Fetch all the deals in this stage (because the list may be filtered, but we need to update even non-filtered deals)
+ const { data: columnDeals } = await dataProvider.getList('deals', {
+ sort: { field: 'index', order: 'ASC' },
+ pagination: { page: 1, perPage: 100 },
+ filter: { stage: source.stage },
+ });
+ const destinationIndex = destination.index ?? columnDeals.length + 1;
+
+ if (source.index > destinationIndex) {
+ // deal moved up, eg
+ // dest src
+ // <------
+ // [4, 7, 23, 5]
+ await Promise.all([
+ // for all deals between destinationIndex and source.index, increase the index
+ ...columnDeals
+ .filter(
+ deal =>
+ deal.index >= destinationIndex &&
+ deal.index < source.index
+ )
+ .map(deal =>
+ dataProvider.update('deals', {
+ id: deal.id,
+ data: { index: deal.index + 1 },
+ previousData: deal,
+ })
+ ),
+ // for the deal that was moved, update its index
+ dataProvider.update('deals', {
+ id: source.id,
+ data: { index: destinationIndex },
+ previousData: source,
+ }),
+ ]);
+ } else {
+ // deal moved down, e.g
+ // src dest
+ // ------>
+ // [4, 7, 23, 5]
+ await Promise.all([
+ // for all deals between source.index and destinationIndex, decrease the index
+ ...columnDeals
+ .filter(
+ deal =>
+ deal.index <= destinationIndex &&
+ deal.index > source.index
+ )
+ .map(deal =>
+ dataProvider.update('deals', {
+ id: deal.id,
+ data: { index: deal.index - 1 },
+ previousData: deal,
+ })
+ ),
+ // for the deal that was moved, update its index
+ dataProvider.update('deals', {
+ id: source.id,
+ data: { index: destinationIndex },
+ previousData: source,
+ }),
+ ]);
+ }
+ } else {
+ // moving deal across columns
+ // Fetch all the deals in both stages (because the list may be filtered, but we need to update even non-filtered deals)
+ const [{ data: sourceDeals }, { data: destinationDeals }] =
+ await Promise.all([
+ dataProvider.getList('deals', {
+ sort: { field: 'index', order: 'ASC' },
+ pagination: { page: 1, perPage: 100 },
+ filter: { stage: source.stage },
+ }),
+ dataProvider.getList('deals', {
+ sort: { field: 'index', order: 'ASC' },
+ pagination: { page: 1, perPage: 100 },
+ filter: { stage: destination.stage },
+ }),
+ ]);
+ const destinationIndex =
+ destination.index ?? destinationDeals.length + 1;
+
+ await Promise.all([
+ // decrease index on the deals after the source index in the source columns
+ ...sourceDeals
+ .filter(deal => deal.index > source.index)
+ .map(deal =>
+ dataProvider.update('deals', {
+ id: deal.id,
+ data: { index: deal.index - 1 },
+ previousData: deal,
+ })
+ ),
+ // increase index on the deals after the destination index in the destination columns
+ ...destinationDeals
+ .filter(deal => deal.index >= destinationIndex)
+ .map(deal =>
+ dataProvider.update('deals', {
+ id: deal.id,
+ data: { index: deal.index + 1 },
+ previousData: deal,
+ })
+ ),
+ // change the dragged deal to take the destination index and column
+ dataProvider.update('deals', {
+ id: source.id,
+ data: {
+ index: destinationIndex,
+ stage: destination.stage,
+ },
+ previousData: source,
+ }),
+ ]);
+ }
+};
diff --git a/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/service/impl/MissionServiceImpl.java b/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/service/impl/MissionServiceImpl.java
index 42aea05..7a0f298 100644
--- a/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/service/impl/MissionServiceImpl.java
+++ b/zy-acs-manager/src/main/java/com/zy/acs/manager/manager/service/impl/MissionServiceImpl.java
@@ -27,7 +27,7 @@
MissionVo vo = new MissionVo();
vo.setGroupNo(groupNo);
-
+ result.add(vo);
}
return result;
--
Gitblit v1.9.1