Skip to content

Commit

Permalink
Merge pull request #198 from BCSDLab/feature/make_dues
Browse files Browse the repository at this point in the history
[회비 생성] - 회비 생성 기능 리팩토링
  • Loading branch information
dooohun authored Feb 2, 2025
2 parents 03f565c + 685070c commit be19e9f
Show file tree
Hide file tree
Showing 11 changed files with 704 additions and 605 deletions.
5 changes: 4 additions & 1 deletion src/component/YearPagination/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { ArrowBackIosNewOutlined, ArrowForwardIosOutlined } from '@mui/icons-material';
import {
ArrowBackIosNewOutlined,
ArrowForwardIosOutlined,
} from '@mui/icons-material';
import { Button } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { LAST_DUES_YEAR } from 'util/constants/status';
Expand Down
1 change: 1 addition & 0 deletions src/model/member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface Member {
updatedAt: string;
isAuthed: boolean;
isDeleted: boolean;
isFeeExempt: boolean;
deleteReason: string;
birthday: string;
}
Expand Down
183 changes: 132 additions & 51 deletions src/page/DuesManagement/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import {
Button, Checkbox, FormControl, FormControlLabel, FormGroup, FormLabel, Input, Popover,
Button,
Checkbox,
FormControl,
FormControlLabel,
FormGroup,
FormLabel,
Input,
Popover,
} from '@mui/material';
import Modal from '@mui/material/Modal';
import {
Expand All @@ -17,9 +24,7 @@ import { DuesDetail } from 'model/dues/allDues';
import { STATUS, STATUS_MAPPING } from 'util/constants/status';
import { useGetTracks } from 'query/tracks';
import LoadingSpinner from 'layout/LoadingSpinner';
import {
ArrowDownward, ArrowUpward, Sort,
} from '@mui/icons-material';
import { ArrowDownward, ArrowUpward, Sort } from '@mui/icons-material';
import { useQueryParam } from 'util/hooks/useQueryParam';
import makeNumberArray from 'util/hooks/makeNumberArray';
import { useGetMembers } from 'query/members';
Expand All @@ -38,7 +43,9 @@ function DefaultTable() {
const param = useQueryParam('page');
const page = Number(param);
const currentYear = new Date().getFullYear();
const [duesYear, setDuesYear] = useState(page ? currentYear - page + 1 : currentYear);
const [duesYear, setDuesYear] = useState(
page ? currentYear - page + 1 : currentYear,
);
const [name, setName] = useState('');
const [detail, setDetail] = useState<DuesDetail>({ month: 0, status: null });
const [memoAnchorEl, setMemoAnchorEl] = useState<null | HTMLElement>(null);
Expand All @@ -55,8 +62,16 @@ function DefaultTable() {
const openSnackBar = useSnackBar();

const { data: allDues } = useGetAllDues({ year: duesYear });
const { data: members } = useGetMembers({ pageIndex: 0, pageSize: 1000, trackId: null });
const [filteredValue, setFilteredValue] = useState(allDues.dues.filter((row) => members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId)));
const { data: members } = useGetMembers({
pageIndex: 0,
pageSize: 1000,
trackId: null,
});
const [filteredValue, setFilteredValue] = useState(
allDues.dues.filter((row) => members?.content.some(
(member) => member.memberType === 'REGULAR' && member.id === row.memberId,
)),
);

const { data: tracks } = useGetTracks();
const [trackFilter, setTrackFilter] = useState(tracks.map(() => true));
Expand All @@ -65,23 +80,48 @@ function DefaultTable() {
const searchName = e.target.value;
if (searchName === '') {
if (trackFilter.every((value) => value)) {
setFilteredValue(allDues.dues.filter((row) => members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId)));
setFilteredValue(
allDues.dues.filter((row) => members?.content.some(
(member) => member.memberType === 'REGULAR' && member.id === row.memberId,
)),
);
} else {
setFilteredValue(allDues.dues.filter((row) => members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId) && trackFilter[tracks.map((track) => track.name).indexOf(row.track.name)]));
setFilteredValue(
allDues.dues.filter(
(row) => members?.content.some(
(member) => member.memberType === 'REGULAR' && member.id === row.memberId,
)
&& trackFilter[
tracks.map((track) => track.name).indexOf(row.track.name)
],
),
);
}
}
setName(searchName);
};

const handleNameSearchClick = () => {
if (filteredValue.some((row) => row.name.includes(name))) {
setFilteredValue(allDues.dues.filter((row) => row.name.includes(name) && members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId)));
setFilteredValue(
allDues.dues.filter(
(row) => row.name.includes(name)
&& members?.content.some(
(member) => member.memberType === 'REGULAR' && member.id === row.memberId,
),
),
);
} else {
openSnackBar({ type: 'error', message: '해당 이름을 가진 회원이 없습니다.' });
openSnackBar({
type: 'error',
message: '해당 이름을 가진 회원이 없습니다.',
});
}
};

const handleNameSearchKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleNameSearchKeyDown = (
e: React.KeyboardEvent<HTMLInputElement>,
) => {
if (e.key === 'Enter') {
handleNameSearchClick();
}
Expand All @@ -93,8 +133,16 @@ function DefaultTable() {
setTrackFilter((prevTrack) => {
const updatedTrack = [...prevTrack];
updatedTrack[trackIndex] = !updatedTrack[trackIndex];
setFilteredValue(allDues.dues.filter((row) => updatedTrack[tracks.map((track) => track.name).indexOf(row.track.name)]
&& members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId)));
setFilteredValue(
allDues.dues.filter(
(row) => updatedTrack[
tracks.map((track) => track.name).indexOf(row.track.name)
]
&& members?.content.some(
(member) => member.memberType === 'REGULAR' && member.id === row.memberId,
),
),
);
return updatedTrack;
});
};
Expand Down Expand Up @@ -133,20 +181,31 @@ function DefaultTable() {
setSortAnchorEl((prev) => ({ ...prev, unpaidCount: null }));
};

const handleMemoClick = (e: React.MouseEvent<HTMLTableCellElement>, dueDetail: DuesDetail) => {
const handleMemoClick = (
e: React.MouseEvent<HTMLTableCellElement>,
dueDetail: DuesDetail,
) => {
if (dueDetail.status === 'NOT_PAID' || dueDetail.status === 'SKIP') {
setMemoAnchorEl(e.currentTarget);
if (dueDetail.memo) {
setDetail(dueDetail);
} else {
setDetail({ month: dueDetail.month, status: dueDetail.status, memo: '사유 없음' });
setDetail({
month: dueDetail.month,
status: dueDetail.status,
memo: '사유 없음',
});
}
}
};

useEffect(() => {
if (allDues.dues) {
setFilteredValue(allDues.dues.filter((row) => members?.content.some((member) => member.memberType === 'REGULAR' && member.id === row.memberId)));
setFilteredValue(
allDues.dues.filter((row) => members?.content.some(
(member) => member.memberType === 'REGULAR' && member.id === row.memberId,
)),
);
}
}, [allDues.dues, members?.content]);

Expand Down Expand Up @@ -182,25 +241,30 @@ function DefaultTable() {
>
<Sort css={S.sortLogo} />
</Button>
<Modal
open={isFilterModalOpen}
onClose={closeFilterModal}
>
<Modal open={isFilterModalOpen} onClose={closeFilterModal}>
<div css={S.filterModalContainer}>
<h2>
트랙 선택
</h2>
<h2>트랙 선택</h2>
<div css={S.filterModalContent}>
<FormControl css={S.checkboxFieldset} component="fieldset" variant="standard">
<FormLabel component="legend">원하는 트랙을 선택하세요.</FormLabel>
<FormControl
css={S.checkboxFieldset}
component="fieldset"
variant="standard"
>
<FormLabel component="legend">
원하는 트랙을 선택하세요.
</FormLabel>
<FormGroup>
{tracks.map((track, index) => {
return (
<FormControlLabel
key={track.id}
control={
<Checkbox checked={trackFilter[index]} onChange={handleTrackFilterChange} name={track.name} />
}
control={(
<Checkbox
checked={trackFilter[index]}
onChange={handleTrackFilterChange}
name={track.name}
/>
)}
label={track.name}
/>
);
Expand All @@ -216,7 +280,10 @@ function DefaultTable() {
<span>미납 횟수</span>
<Button
type="button"
onClick={(e) => setSortAnchorEl((prev) => ({ ...prev, unpaidCount: e.currentTarget }))}
onClick={(e) => setSortAnchorEl((prev) => ({
...prev,
unpaidCount: e.currentTarget,
}))}
css={S.filterModalButton}
>
<Sort css={S.sortLogo} />
Expand All @@ -225,22 +292,31 @@ function DefaultTable() {
id="simple-popover"
open={Boolean(sortAnchorEl.unpaidCount)}
anchorEl={sortAnchorEl.unpaidCount}
onClose={() => setSortAnchorEl((prev) => ({ ...prev, unpaidCount: null }))}
onClose={() => setSortAnchorEl((prev) => ({
...prev,
unpaidCount: null,
}))}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<div css={S.sortPopover}>
<h3>
미납 횟수순 정렬
</h3>
<h3>미납 횟수순 정렬</h3>
<div css={S.sortPopoverButtonGroup}>
<Button variant="contained" color="primary" onClick={sortInAscendingOrderByUnpaidCount}>
<Button
variant="contained"
color="primary"
onClick={sortInAscendingOrderByUnpaidCount}
>
<ArrowUpward />
오름차순
</Button>
<Button variant="contained" color="primary" onClick={sortInDescendingOrderByUnpaidCount}>
<Button
variant="contained"
color="primary"
onClick={sortInDescendingOrderByUnpaidCount}
>
<ArrowDownward />
내림차순
</Button>
Expand All @@ -253,7 +329,10 @@ function DefaultTable() {
<span>이름</span>
<Button
type="button"
onClick={(e) => setSortAnchorEl((prev) => ({ ...prev, name: e.currentTarget }))}
onClick={(e) => setSortAnchorEl((prev) => ({
...prev,
name: e.currentTarget,
}))}
css={S.filterModalButton}
>
<Sort css={S.sortLogo} />
Expand All @@ -269,15 +348,21 @@ function DefaultTable() {
}}
>
<div css={S.sortPopover}>
<h3>
이름순 정렬
</h3>
<h3>이름순 정렬</h3>
<div css={S.sortPopoverButtonGroup}>
<Button variant="contained" color="primary" onClick={sortInAscendingOrderByName}>
<Button
variant="contained"
color="primary"
onClick={sortInAscendingOrderByName}
>
<ArrowUpward />
오름차순
</Button>
<Button variant="contained" color="primary" onClick={sortInDescendingOrderByName}>
<Button
variant="contained"
color="primary"
onClick={sortInDescendingOrderByName}
>
<ArrowDownward />
내림차순
</Button>
Expand All @@ -297,9 +382,7 @@ function DefaultTable() {
<TableBody>
{filteredValue.map((row) => (
<TableRow key={row.memberId}>
<TableCell css={S.tableBodyCell}>
{row.track.name}
</TableCell>
<TableCell css={S.tableBodyCell}>{row.track.name}</TableCell>
<TableCell css={S.tableBodyCell}>{row.unpaidCount}</TableCell>
<TableCell css={S.tableBodyCell}>{row.name}</TableCell>
{row.detail.map((dueDetail) => (
Expand All @@ -308,7 +391,9 @@ function DefaultTable() {
onClick={(e) => handleMemoClick(e, dueDetail)}
key={dueDetail.month}
>
{dueDetail.status !== null ? STATUS_MAPPING[dueDetail.status] : '-'}
{dueDetail.status !== null
? STATUS_MAPPING[dueDetail.status]
: '-'}
</TableCell>
))}
</TableRow>
Expand Down Expand Up @@ -346,11 +431,7 @@ export default function DuesManagement() {
<div css={S.container}>
<div css={S.mainContent}>
<Suspense fallback={<LoadingSpinner />}>
{isMobile ? (
<PersonalDues />
) : (
<DefaultTable />
)}
{isMobile ? <PersonalDues /> : <DefaultTable />}
</Suspense>
</div>
</div>
Expand Down
Loading

0 comments on commit be19e9f

Please sign in to comment.