#
luxiaotao1123
2024-09-27 6f1a96c44b273f3dee28260715c16d408d79d38f
#
2个文件已修改
3个文件已添加
403 ■■■■■ 已修改文件
zy-acs-flow/src/page/mission/MissionCard.jsx 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/mission/MissionColumn.jsx 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/mission/MissionEmpty.jsx 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/mission/MissionListContent.jsx 242 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-manager/src/main/java/com/zy/acs/manager/manager/service/impl/MissionServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-flow/src/page/mission/MissionCard.jsx
New file
@@ -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>
    );
};
zy-acs-flow/src/page/mission/MissionColumn.jsx
New file
@@ -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>
    );
};
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 },
zy-acs-flow/src/page/mission/MissionListContent.jsx
New file
@@ -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,
            }),
        ]);
    }
};
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;