diff --git a/package.json b/package.json
index 2a87682..2db2e78 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"axios": "^1.4.0",
"dayjs": "^1.11.7",
"decimal.js": "^10.4.3",
+ "html-react-parser": "^5.0.0",
"i18next-browser-languagedetector": "^7.0.1",
"lodash": "^4.17.21",
"moment": "^2.29.4",
diff --git a/src/components/AbstractTeam/AbstractTeam.tsx b/src/components/AbstractTeam/AbstractTeam.tsx
index c522e2e..1009484 100644
--- a/src/components/AbstractTeam/AbstractTeam.tsx
+++ b/src/components/AbstractTeam/AbstractTeam.tsx
@@ -3,11 +3,12 @@ import { useSelector } from "react-redux";
import { FootballPicker } from "@/lib/football-picker";
import { FootballMaxPositionsPicks, FootballPositionIds } from "@/lib/constants";
import Decimal from "decimal.js";
-import { useAddTeamMutation, useUpdateTeamSelectionMutation } from "@/services/teamsApi";
+import { useAddTeamMutation, useSubmitTransfersMutation, useUpdateTeamSelectionMutation } from "@/services/teamsApi";
import { openErrorNotification, openSuccessNotification } from "@/lib/helpers";
import { t } from "i18next";
import { useLazyGetTeamsQuery } from "@/services/usersApi";
import { useGetDeadlineInfoQuery } from "@/services/weeksApi";
+import { pick } from "lodash";
declare type AbstractTeamProps = {
matches?: any;
@@ -52,6 +53,12 @@ declare type AbstractTeamState = {
teamUser?: any
validator?: any
savingTeamPending?: any
+ visibleWeekId: number | null
+ initializedExternally: boolean
+ boosters: Boosters
+ deadlineWeekTransfers: Transfer[],
+ draftTransfers: Transfer[],
+ pastTransfers: Transfer[],
}
function playersToValidatorFormat(players: any) {
@@ -73,7 +80,9 @@ const getInitializedList = (size: number, forStarting?: boolean) => {
export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props: AbstractTeamProps, options?: Options,) => {
const [addTeam] = useAddTeamMutation();
const [updateTeamSelections, { isSuccess: updateTeamSelectionsSucces, data: updateTeamSelectionsResult }] = useUpdateTeamSelectionMutation();
+ const { data: deadlineInfo, isSuccess: deadlineInfoSuccess, isLoading: deadlineInfoLoading, isError: deadlineInfoError } = useGetDeadlineInfoQuery();
const [getTeams] = useLazyGetTeamsQuery();
+ const [submitTransfers, { isSuccess: submitTransfersSucces, data: submitTransfersResult }] = useSubmitTransfersMutation();
const application = useSelector((state: StoreState.All) => state.application);
@@ -92,6 +101,12 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
swapPlayerId: null,
swapPlayer: null,
swappedFrom: null,
+ visibleWeekId: deadlineInfoSuccess ? (options && options.mode === 'points' ? deadlineInfo.deadlineInfo.displayWeek : deadlineInfo.deadlineInfo.deadlineWeek) : 0,
+ boosters: {},
+
+ deadlineWeekTransfers: [],
+ draftTransfers: [],
+ pastTransfers: [],
initialStarting: getInitializedList(application.competition.lineupSize, true),
initialBench: getInitializedList(application.competition.benchSize),
@@ -99,6 +114,7 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
teamUser: undefined,
activePositionFilter: -1,
+ initializedExternally: false,
});
const setStarting = (starting: any[]) => {
@@ -131,10 +147,18 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
starting: any[],
bench: any[],
teamName: string,
+ captainId: number,
budget: number,
- captainId?: number,
+ leagues?: any[] | undefined,
+ visibleWeekId?: number | undefined,
+ teamPointsInfo?: any,
+ rawTransfers?: any[] | undefined,
+ deadlineWeekTransfers?: any[] | undefined,
+ pastTransfers?: any[] | undefined,
viceCaptainId?: number,
- teamUser?: any,
+ boosters?: Boosters,
+ isTeamOwner?: boolean,
+ teamUser?: any
) => {
const startingPlayersValidatorFormat = playersToValidatorFormat(starting);
const benchPlayersValidatorFormat = playersToValidatorFormat(bench);
@@ -154,10 +178,16 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
initialBench: bench,
initialStarting: starting,
initialBudget: budget,
+ visibleWeekId: visibleWeekId | state.visibleWeekId,
+ initializedExternally: true,
+ boosters: boosters || {},
+ deadlineWeekTransfers: deadlineWeekTransfers || [],
+ draftTransfers: [],
+ pastTransfers: pastTransfers || [],
});
};
- const pickPlayer = (player: Player) => {
+ const pickPlayer = (player: Player, taxForPicking?: boolean) => {
const nilPlayer: any = null;
const alreadyInTeam: Player = [].concat(state.initialStarting, state.initialBench).find((item: Player) => item && player && item.id === player.id);
@@ -257,7 +287,7 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
const captainId = state.captainId === player.id ? undefined : state.captainId;
const viceCaptainId = state.viceCaptainId === player.id ? undefined : state.captainId;
- // const removeResult = state.validator.remove(player);
+ const removeResult = state.validator.remove(player);
setState({ ...state, starting: newStarting, budget, captainId, viceCaptainId });
};
@@ -276,7 +306,7 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
.plus(player.value.toFixed(2))
.toString());
- // const removeResult = state.validator.remove(player);
+ const removeResult = state.validator.remove(player);
setState({ ...state, bench: newBench, budget });
};
@@ -503,7 +533,7 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
}
};
- const isPickable = (player: Player) => {
+ const isPickable = (player: Player, taxForPicking?: boolean, isTransferPick?: boolean) => {
const notInStarting = !state.starting.find(startingPlayer => startingPlayer && startingPlayer.id && startingPlayer.id === player.id);
const notInBench = !state.bench.find(benchPlayer => benchPlayer && benchPlayer.id && benchPlayer.id === player.id);
const affordable = player.value <= state.budget;
@@ -568,27 +598,27 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
const onTransferPlayerOut = (player: Player, extra?: boolean) => {
removePlayer(player);
- // const draftTransfers = state.draftTransfers
- // .concat([{
- // inId: null,
- // outId: player.id,
- // outPlayer: player,
- // extra,
- // // weekId: matches.info.deadlineWeek
- // }]);
- // setState({ ...state, draftTransfers });
+ const draftTransfers = state.draftTransfers
+ .concat([{
+ inId: null,
+ outId: player.id,
+ outPlayer: player,
+ extra,
+ weekId: deadlineInfo.deadlineInfo.deadlineWeek
+ }]);
+ setState(previousState => ({ ...previousState, draftTransfers }));
};
const onTransferPlayerIn = (player: Player) => {
- // const draftTransfers = ([] as Transfer[]).concat(state.draftTransfers);
- // for (let tfIdx = 0; tfIdx < draftTransfers.length; tfIdx++) {
- // if (!draftTransfers[tfIdx].inId && draftTransfers[tfIdx].outPlayer?.positionId === player.positionId) {
- // draftTransfers[tfIdx].inId = player.id;
- // draftTransfers[tfIdx].inPlayer = player;
- // break;
- // }
- // }
- // setState({ ...state, draftTransfers });
+ const draftTransfers = ([] as Transfer[]).concat(state.draftTransfers);
+ for (let tfIdx = 0; tfIdx < draftTransfers.length; tfIdx++) {
+ if (!draftTransfers[tfIdx].inId && draftTransfers[tfIdx].outPlayer?.positionId === player.positionId) {
+ draftTransfers[tfIdx].inId = player.id;
+ draftTransfers[tfIdx].inPlayer = player;
+ break;
+ }
+ }
+ setState(previousState => ({ ...previousState, draftTransfers }));
};
const onDraftTransfersClear = () => {
@@ -598,16 +628,18 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
setState({
...state,
+ draftTransfers: [],
starting: state.initialStarting,
bench: state.initialBench,
budget: state.initialBudget
});
};
const onTransfersSubmit = (teamId: number) => {
- // const transfers = state.draftTransfers
- // .map((transfer: Transfer) => pick(transfer, ["inId", "outId"]));
- // return teamsActions.submitTransfers(teamId, transfers)
- // TODO: POST team/transfers/:teamid
+ console.log("TRANSFERS SUBMITTED");
+ const transfers = state.draftTransfers
+ .map((transfer: Transfer) => pick(transfer, ["inId", "outId"]));
+
+ submitTransfers({teamId, transfers}).unwrap().then((res) => openSuccessNotification({ title: res.msg })).catch((err) => openErrorNotification({ title: t(`team.transfers.failed`) }));
};
const onTransfersReset = (teamId: number) => {
@@ -668,6 +700,12 @@ export const AbstractTeam = (Component: (props: AbstractTeamType) => any, props:
onTransfersReset={onTransfersReset}
reloadUserTeams={reloadUserTeams}
teamUser={state.teamUser}
+ visibleWeekId={state.visibleWeekId}
+ initializedExternally={state.initializedExternally}
+ boosters={state.boosters}
+ draftTransfers={state.draftTransfers}
+ deadlineWeekTransfers={state.deadlineWeekTransfers}
+ pastTransfers={state.pastTransfers}
{...props}
/>
diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx
index 8be7c60..e4bad4d 100644
--- a/src/components/Calendar/Calendar.tsx
+++ b/src/components/Calendar/Calendar.tsx
@@ -81,9 +81,7 @@ export const Calendar = (props: CalendarProps) => {
render: (homeId: any, record: any) => {
const clubBadge = `${config.API_URL}/static/badges/${record.home.externalId}.png`;
- return
+ return
;
diff --git a/src/components/Calendar/CalendarStyle.tsx b/src/components/Calendar/CalendarStyle.tsx
index c17c96b..7c7e888 100644
--- a/src/components/Calendar/CalendarStyle.tsx
+++ b/src/components/Calendar/CalendarStyle.tsx
@@ -56,7 +56,7 @@ table {
}
`;
-export const ClubDetails = styled.div`
+export const ClubDetails = styled.div<{left?: boolean}>`
display: flex;
padding: 5px;
justify-content: ${(props: any) => props.left ? "flex-end": "flex-start"};
diff --git a/src/components/ConfirmModal/ConfirmModal.tsx b/src/components/ConfirmModal/ConfirmModal.tsx
new file mode 100644
index 0000000..b0bf39e
--- /dev/null
+++ b/src/components/ConfirmModal/ConfirmModal.tsx
@@ -0,0 +1,40 @@
+import { Button } from "../UI/Button/Button"
+import { Col, Row } from "../UI/Grid/Grid"
+import { ConfirmModalStyle } from "./ConfirmModalStyle"
+import { useTranslation } from "react-i18next"
+
+declare type ConfirmModalProps = {
+ visible: boolean
+ title: string
+ text: string
+ onCancel: any
+ onConfirm: any
+}
+
+export const ConfirmModal = (props: any) => {
+ const { visible, onCancel, onConfirm, text, title } = props;
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ {text}
+
+
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/ConfirmModal/ConfirmModalStyle.tsx b/src/components/ConfirmModal/ConfirmModalStyle.tsx
new file mode 100644
index 0000000..35a1875
--- /dev/null
+++ b/src/components/ConfirmModal/ConfirmModalStyle.tsx
@@ -0,0 +1,58 @@
+import styled from "@/styles/styled-components";
+import { Modal } from "antd";
+
+export const ConfirmModalStyle = styled(Modal)`
+.ant-modal-content {
+ border-radius: 0px;
+ max-width: 575px;
+
+ .ant-modal-title {
+ font-family: "C-Bold";
+ text-transform: uppercase;
+ background: #000;
+ padding: 5px 5px 5px 36.5px;
+ }
+
+ .ant-modal-close-x {
+ width: 30px;
+ height: 30px;
+ font-size: 20px;
+ color: 84FF00;
+ line-height: 30px;
+ }
+
+ .ant-modal-header {
+ border: 0px;
+ border-radius: 0px;
+ padding: 0;
+
+ .ant-modal-title {
+ color: white;
+ p {
+ margin: 0px;
+ }
+
+ .custom-title-container {
+ text-align: right;
+
+ .anticon {
+ margin-top: 5px;
+ margin-right: 5px;
+ }
+ }
+ }
+ }
+
+ .ant-modal-footer {
+ display: none;
+ }
+}
+
+.actions {
+ text-align: right;
+ margin-top: 15px;
+ button {
+ margin: 5px;
+ }
+}
+`;
\ No newline at end of file
diff --git a/src/components/Player/Player.tsx b/src/components/Player/Player.tsx
index 95bbc9c..52a7fdd 100644
--- a/src/components/Player/Player.tsx
+++ b/src/components/Player/Player.tsx
@@ -49,6 +49,9 @@ declare type PlayerProps = {
onSwap?: any
onCaptainSelect?: any
onViceCaptainSelect?: any
+ benchPlayer?: boolean
+ showPlayerValue?: boolean
+ showPlayerValueInsteadOfPoints?: boolean
className?: string
}
@@ -76,6 +79,10 @@ export const Player = (props: PlayerProps) => {
onSwap,
onCaptainSelect,
onViceCaptainSelect,
+ showPlayerValueInsteadOfPoints,
+ showPlayerValue,
+ benchPlayer,
+ // showPlayerStatsPoints,
} = props;
const [state, setState] = useState({
portraitFace: props.portraitFace,
@@ -132,7 +139,7 @@ export const Player = (props: PlayerProps) => {
const isCaptain = useMemo(() => player && player.id && player.id === captainId, [player, captainId]);
const isViceCaptain = useMemo(() => player && player.id && player.id === viceCaptainId, [player, viceCaptainId]);
- const showPoints = true;
+ const showPoints = (player && player.points !== undefined && player.points !== null) || showPlayerValueInsteadOfPoints; //todo
const showPlayerName = !avatarOnly;
const onRemoveHandler = (e: any, player: Player) => {
@@ -189,7 +196,14 @@ export const Player = (props: PlayerProps) => {
: null
}
-
+ {
+ (
+ player && showPlayerValue &&
+
+ {(player.value) ? `€${player.value}M` : null}
+
+ ) || null
+ }
{
!player || (player && !player.id) &&
onPlaceholderClick(player) : () => { }}>
diff --git a/src/components/PlayerList/PlayerList.tsx b/src/components/PlayerList/PlayerList.tsx
index ee1a019..c543b6f 100644
--- a/src/components/PlayerList/PlayerList.tsx
+++ b/src/components/PlayerList/PlayerList.tsx
@@ -34,6 +34,9 @@ declare type PlayerListProps = {
actionLabel?: string
isLoading?: boolean
playerType: PlayerType
+ matches?: any
+ deadlineWeek?: any;
+ playerTax?: number | undefined;
}
declare type PlayerListState = {
diff --git a/src/components/PlayerList/PlayerListStyle.ts b/src/components/PlayerList/PlayerListStyle.ts
index df2ebd8..ed8de7c 100644
--- a/src/components/PlayerList/PlayerListStyle.ts
+++ b/src/components/PlayerList/PlayerListStyle.ts
@@ -114,10 +114,6 @@ export const TableStyle = styled(Table)`
border: none;
padding: 3.5px;
- &:first-child {
- padding: 2.5px 10px 0 15px;
- }
-
& p {
margin-top:0;
}
diff --git a/src/components/PlayerModal/PlayerModal.tsx b/src/components/PlayerModal/PlayerModal.tsx
index 44286c0..5500b58 100644
--- a/src/components/PlayerModal/PlayerModal.tsx
+++ b/src/components/PlayerModal/PlayerModal.tsx
@@ -107,7 +107,7 @@ export const PlayerModal = (props: PlayerModalProps) => {
{
- onCaptainSelect ?
+ props.onCaptainSelect ?
@@ -117,7 +117,7 @@ export const PlayerModal = (props: PlayerModalProps) => {
null
}
{
- onViceCaptainSelect ?
+ props.onViceCaptainSelect ?
@@ -128,7 +128,7 @@ export const PlayerModal = (props: PlayerModalProps) => {
}
{
- onSwap && isSwapAble && isSwapAble(player) && (player.id !== swapPlayerId) ?
+ props.onSwap && isSwapAble && props.isSwapAble(player) && (player.id !== swapPlayerId) ?
@@ -139,7 +139,7 @@ export const PlayerModal = (props: PlayerModalProps) => {
}
{
- onSwap && (player.id === swapPlayerId) ?
+ props.onSwap && (player.id === swapPlayerId) ?
diff --git a/src/components/Stats/TransfersOverview.tsx b/src/components/Stats/TransfersOverview.tsx
new file mode 100644
index 0000000..62a9140
--- /dev/null
+++ b/src/components/Stats/TransfersOverview.tsx
@@ -0,0 +1,33 @@
+import { StatsStyle } from "@/components/Stats/StatsStyle"
+import { Col, Row } from "../UI/Grid/Grid"
+import { useTranslation } from "react-i18next"
+
+type TransfersOverviewProps = {
+ budget: number
+ totalPlayers: number
+ totalPlayersSelected: number
+ remainingFreeTransfers: number
+ minusPoints: number
+}
+
+export const TransfersOverview = (props: TransfersOverviewProps) => {
+ const {t} = useTranslation();
+ return (
+
+
+
+ {props.budget.toFixed(2)}M
+ {t('TransfersPage.overviewBudget')}
+
+
+ {props.totalPlayersSelected}/{props.totalPlayers}
+ {t('TransfersPage.overviewPlayers')}
+
+
+ {props.remainingFreeTransfers < 0 ? 0 : props.remainingFreeTransfers}
+ {t('TransfersPage.overviewTransfers')}
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Team/Team.tsx b/src/components/Team/Team.tsx
index a63eec6..ce87120 100644
--- a/src/components/Team/Team.tsx
+++ b/src/components/Team/Team.tsx
@@ -94,11 +94,11 @@ export const Team = (props: TeamProps) => {
viceCaptainId={viceCaptainId}
player={player}
// showPlayerStatsPoints={showPlayerStatsPoints}
- // showPlayerValue={showPlayerValue}
+ showPlayerValue={showPlayerValue}
showCaptainBadge={showCaptainBadge}
// type={playerType}
onRemove={onRemove}
- // showPlayerValueInsteadOfPoints={showPlayerValueInsteadOfPoints}
+ showPlayerValueInsteadOfPoints={showPlayerValueInsteadOfPoints}
onSwap={onSwap}
isSwapable={isSwapAble}
onCaptainSelect={onCaptainSelect}
diff --git a/src/components/TransfersList/TransfersList.tsx b/src/components/TransfersList/TransfersList.tsx
new file mode 100644
index 0000000..79263f4
--- /dev/null
+++ b/src/components/TransfersList/TransfersList.tsx
@@ -0,0 +1,88 @@
+import { useTranslation } from "react-i18next"
+import { ContainerStyle, TableStyle } from "../PlayerList/PlayerListStyle"
+import React from "react"
+
+type TransfersListProps = {
+ data: any
+ size: number
+ isLoading?: boolean
+ showHeader?: boolean
+ showWeek?: boolean
+ tax?: number | undefined
+}
+
+export const TransfersList = (props: TransfersListProps) => {
+ const { t } = useTranslation();
+
+ const {data,size, isLoading, showHeader, showWeek} = props;
+ const columns = [
+ {
+ title: '#',
+ key: 'number',
+ dataIndex: 'number',
+ width: '20%',
+ render: (txt: string, rec: any, idx: number) => {
+ return
{idx + 1};
+ },
+ },
+ {
+ title: t('team.transferOut'),
+ key: 'outPlayer',
+ dataIndex: 'outPlayer',
+ width: showWeek ? '30%' : '40%',
+ render: (txt: string, rec: any, idx: number) => {
+ const playerName = (rec.outPlayer && `${rec.outPlayer.short}`) || '';
+ return (
+
+
+ {playerName}
+
+
+ );
+ },
+ },
+ {
+ title: t('team.transferIn'),
+ key: 'inPlayer',
+ dataIndex: 'inPlayer',
+ width: showWeek ? '30%' : '40%',
+ render: (txt: string, rec: any, idx: number) => {
+ const playerName = (rec.inPlayer && `${rec.inPlayer.short}`) || '';
+ return (
+
+
+ {playerName}
+
+
+ );
+ },
+ },
+ ];
+
+ if(showWeek) {
+ columns.push({
+ key: 'weeId',
+ title: t('general.footballWeek'),
+ width: '20%',
+ dataIndex: 'weekId',
+ render: (text: string, team: any) => {
+ return
{text};
+ }
+ })
+ };
+
+ return (
+
+ size ? { pageSize: size } : false}
+ rowKey={(rec: any, idx: number) => `record-${idx+1}`}
+ rowClassName={(rec: object, idx: number) => idx%2 ? 'ant-table-row--odd' : 'ant-table-row--even'}
+ />
+
+ );
+}
\ No newline at end of file
diff --git a/src/lib/helpers.tsx b/src/lib/helpers.tsx
index ac778ea..5896aa0 100644
--- a/src/lib/helpers.tsx
+++ b/src/lib/helpers.tsx
@@ -71,6 +71,37 @@ export const getPlayerPositionHexColor = (player: any, theme: any) => {
}
};
+
+export const selectionPlayerSellValue = (player: any) => {
+ const current = Object.assign({}, player);
+ const currentValue = current.value;
+ const selectionValue = player.selection.value;
+ const diff = currentValue - selectionValue;
+ let playerSellValue = null;
+
+ if(selectionValue >= currentValue) {
+ playerSellValue = currentValue;
+ } else {
+ // const profit = roundDownDecimal()
+ const profit = diff / 2;
+ playerSellValue = selectionValue + profit;
+ }
+ return parseFloat((playerSellValue).toFixed(2));
+}
+
+export const roundNextHalf = (number: number) => {
+ const integerPart = Math.floor(number);
+ const decimalPart = number - integerPart;
+
+ if (decimalPart === 0) {
+ return integerPart;
+ } else if (decimalPart <= 0.5) {
+ return integerPart + 0.5;
+ } else {
+ return integerPart + 1;
+ }
+}
+
export const firstLetterUppercased = (string: string) => {
return string.charAt(0).toUpperCase();
};
diff --git a/src/pages/Team/Team.tsx b/src/pages/Team/Team.tsx
index 9824fb8..a38d3b9 100644
--- a/src/pages/Team/Team.tsx
+++ b/src/pages/Team/Team.tsx
@@ -51,16 +51,16 @@ export const _Team = (props: AbstractTeamType) => {
}
const playerProps = ["id", "name", "short", "positionId", "clubId", "value", "ban", "injury", "form", "forename", "surname", "points", "portraitUrl", "externalId"];
const selectionProps: any[] = [];
- const starting = teamResult.players.filter((p: Player) => p.selection?.starting === 1)
- .map((p: Player) => {
+ const starting = teamResult.players.filter((p: any) => p.selection.starting === 1)
+ .map((p: any) => {
const displayWeekMatches: any[] = matches.filter(
(match: Match) => match.weekId === weekId && ([match.homeId, match.awayId].includes(p.clubId))
);
return Object.assign({ inStarting: true, upcomingMatches: displayWeekMatches }, pick(p, playerProps), pick(p.selection, selectionProps));
});
- const bench = teamResult.players.filter((p: Player) => p.selection?.starting === 0)
- .map((p: Player) => {
+ const bench = teamResult.players.filter((p: any) => p.selection.starting === 0)
+ .map((p: any) => {
const displayWeekMatches: any[] = matches.filter(
(match: Match) => match.weekId === weekId && ([match.homeId, match.awayId].includes(p.clubId))
);
@@ -68,10 +68,10 @@ export const _Team = (props: AbstractTeamType) => {
});
const teamName = teamResult.team?.name;
- const captainPlayer = teamResult.players.find((p: Player) => p && p.selection && p.selection.captain === 1);
+ const captainPlayer = teamResult.players.find((p: any) => p && p.selection && p.selection.captain === 1);
const captainId = captainPlayer && captainPlayer.id;
- const viceCaptainPlayer = teamResult.players.find((p: Player) => p && p.selection && p.selection.captain === 2);
+ const viceCaptainPlayer = teamResult.players.find((p: any) => p && p.selection && p.selection.captain === 2);
const viceCaptainId = viceCaptainPlayer && viceCaptainPlayer.id;
const budget = teamResult.players.reduce((acc: any, player: Player) => acc - player.value, application.competition.budget);
@@ -79,7 +79,14 @@ export const _Team = (props: AbstractTeamType) => {
const isTeamOwner = !!(teamResult.team?.userId === user?.id);
- props.initTeamState(starting, bench, teamName, budget, captainId, viceCaptainId, true);
+ const boosters = {
+ freeHit: teamResult.team.freeHit,
+ bank: teamResult.team.bank,
+ tripleCaptain: teamResult.team.tripleCaptain,
+ wildCard: teamResult.team.wildCard
+ };
+
+ props.initTeamState(starting, bench, teamName, captainId, budget, undefined, undefined, undefined, [], [], [], viceCaptainId, boosters);
};
const startingByPositions = useMemo(() => startingListToPositionsList(props.starting, application.competition.lineupPositionRows), [props.starting]);
diff --git a/src/pages/Transfers/Transfers.tsx b/src/pages/Transfers/Transfers.tsx
new file mode 100644
index 0000000..02d9e7a
--- /dev/null
+++ b/src/pages/Transfers/Transfers.tsx
@@ -0,0 +1,364 @@
+import { AbstractTeam } from "@/components/AbstractTeam/AbstractTeam"
+import { Block } from "@/components/Block/Block";
+import { Team } from "@/components/Team/Team";
+import { Col, Row } from "@/components/UI/Grid/Grid";
+import { roundNextHalf, selectionPlayerSellValue, startingListToPositionsList } from "@/lib/helpers";
+import { useAppSelector } from "@/reducers";
+import { useGetTeamQuery } from "@/services/teamsApi";
+import { useGetDeadlineInfoQuery } from "@/services/weeksApi";
+import Title from "antd/es/typography/Title";
+import { pick } from "lodash";
+import React, { useMemo } from "react";
+import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useSelector } from "react-redux";
+import { Navigate, useParams } from "react-router-dom";
+
+import teamBackground from "./../../assets/img/fpl-pitch-no-boarding.svg";
+import { PlayerType } from "@/types/PlayerTypes";
+import { theme } from "@/styles/theme";
+import { useGetClubsQuery } from "@/services/clubsApi";
+import { TransfersList } from "@/components/TransfersList/TransfersList";
+import { Button } from "@/components/UI/Button/Button";
+import Icon from "@ant-design/icons/lib/components/Icon";
+import { CloseCircleFilled, SaveFilled } from "@ant-design/icons";
+import { Element } from "react-scroll";
+import { PlayerList } from "@/components/PlayerList/PlayerList";
+import { useGetMatchesQuery } from "@/services/matchesApi";
+import { useGetPlayersQuery } from "@/services/playersApi";
+import { notification } from "antd";
+import { TransfersOverview } from "@/components/Stats/TransfersOverview";
+import { ConfirmModal } from "@/components/ConfirmModal/ConfirmModal";
+
+type TransfersProps = {
+
+};
+
+type TransfersState = {
+ notFound: boolean
+ performingTransfer: boolean
+ performingTransferReset: boolean
+ transferConfirmModalVisible: boolean
+};
+
+const _Transfers = (props: AbstractTeamType) => {
+ const [state, setState] = useState
({
+ notFound: false,
+ performingTransfer: false,
+ performingTransferReset: false,
+ transferConfirmModalVisible: false,
+ });
+ const application = useSelector((state: StoreState.All) => state.application);
+ const { user, teams } = useAppSelector((state) => state.userState);
+
+ const { id } = useParams();
+ const { t } = useTranslation();
+
+ const { data: teamResult, isLoading: teamLoading, isError: teamError, isSuccess: teamSuccess } = useGetTeamQuery(+(id || 0));
+ const { data: deadlineInfo, isSuccess: deadlineInfoSuccess, isLoading: deadlineInfoLoading, isError: deadlineInfoError } = useGetDeadlineInfoQuery();
+ const { data: clubs, isLoading: clubsLoading, isError: clubsError, isSuccess: clubsSucces } = useGetClubsQuery();
+ const { data: matches, isLoading: matchesLoading, isError: matchesError, isSuccess: matchesSuccess } = useGetMatchesQuery();
+ const { data: players, isLoading: playersLoading, isError: playersError, isSuccess: playersSuccess } = useGetPlayersQuery();
+
+ useEffect(() => {
+ if (teamSuccess) {
+ const playerProps =
+ ['id', 'name', 'short', 'positionId', 'clubId', 'value', 'ban', 'injury', 'form', 'forename', 'surname', 'portraitUrl'];
+ const selectionProps: any[] = [];
+
+ const starting = teamResult.players
+ .filter((player: any) => player.selection.starting === 1)
+ .map((player: any) => {
+ const tfValue = selectionPlayerSellValue(player);
+ return Object.assign({ inStarting: true }, pick(player, playerProps), pick(player.selection, selectionProps), { value: tfValue });
+ });
+ const bench = teamResult.players
+ .filter((player: any) => player.selection.starting === 0)
+ .map((player: any) => {
+ const tfValue = selectionPlayerSellValue(player);
+ return Object.assign({ inStarting: false }, pick(player, playerProps), pick(player.selection, selectionProps), { value: tfValue });
+ });
+
+ const teamName = teamResult.team.name;
+
+ const captainPlayer = teamResult.players.find((player: any) => player.selection.captain === 1);
+ const captainId = captainPlayer && captainPlayer.id;
+ const viceCaptainPlayer = teamResult.players.find((player: any) => player.selection.captain === 2);
+ const viceCaptainId = (viceCaptainPlayer && viceCaptainPlayer.id) || null;
+
+ const pastTransfers = !teamResult.transfers ? ([] as Transfer[]) :
+ teamResult.transfers
+ .filter((tf: any) => deadlineInfo.deadlineInfo.deadlineWeek && (tf.weekId < deadlineInfo.deadlineInfo.deadlineWeek))
+ .map((tf: any) => ({ inId: tf.inId, outId: tf.outId, weekId: tf.weekId }));
+ const deadlineWeekTransfers = !teamResult ? ([] as Transfer[]) :
+ teamResult.transfers
+ .filter((tf: any) => deadlineInfo.deadlineInfo.deadlineWeek && (tf.weekId === deadlineInfo.deadlineInfo.deadlineWeek))
+ .map((tf: any) => ({ inId: tf.inId, outId: tf.outId, weekId: tf.weekId, extra: tf.extra }));
+
+ const getPlayersValueWithTransfers = (players: any) => {
+ const playersValue = players
+ .reduce((acc: any, player: any) => {
+ const wasTfed = teamResult.transfers.find((tf: any) => tf.inId === player.id);
+ const playerValue = wasTfed ?
+ roundNextHalf(player.value + (player.value * (application.competition.transferTaxPercentage || 0) / 100)) :
+ player.value;
+ return acc + playerValue;
+ }, 0);
+ return application.competition.budget - playersValue;
+ };
+
+ const budget = teamResult.team.budget !== null ?
+ teamResult.team.budget : getPlayersValueWithTransfers(teamResult.players);
+
+
+ const boosters = {
+ freeHit: teamResult.team.freeHit,
+ bank: teamResult.team.bank,
+ tripleCaptain: teamResult.team.tripleCaptain,
+ wildCard: teamResult.team.wildCard
+ };
+
+ props.initTeamState(starting, bench, teamName, captainId, budget, undefined, undefined, undefined, teamResult.transfers, deadlineWeekTransfers, pastTransfers, viceCaptainId, boosters);
+ }
+ if (teamError) {
+ console.error(teamError);
+ setState({ ...state, notFound: true });
+ }
+ }, [teamResult]);
+
+ const formatTransfers = (tf: any) => {
+ const inPIDmeta = tf && (tf.inId && Object.keys(tf.inId).length);
+ const outPIDmeta = tf && (tf.outId && Object.keys(tf.outId).length);
+
+ const inPlayer = players && players.find((player: any) => player.id === tf.inId);
+ const outPlayer = players && players.find((player: any) => player.id === tf.outId);
+ return {
+ inPlayer: inPIDmeta ? tf.inId : inPlayer,
+ outPlayer: outPIDmeta ? tf.outId : outPlayer,
+ weekId: tf && tf.weekId,
+ inId: inPIDmeta ? tf.inId.id : tf.inId,
+ outId: outPIDmeta ? tf.outId.id : tf.outId,
+ };
+ };
+
+ const showTransferConfirmModal = () => {
+ setState({ ...state, transferConfirmModalVisible: true });
+ };
+
+ const onTransferConfirmCancel = () => {
+ setState({ ...state, transferConfirmModalVisible: false });
+ };
+
+ const onTransferConfirmAccept = () => {
+ console.log("TRANSFERS ACCEPTED");
+ props.onTransfersSubmit(+(id || 0));
+ setState({ ...state, transferConfirmModalVisible: false });
+ }
+
+ const isNotExtraTransfer = (transfer: any) => {
+ return !transfer.extra;
+ };
+
+ const playerIsExtra = (player: Player) => {
+
+ }
+
+ const onPlayerOut = (player: Player, isLineup: boolean, extraOutTransfer: boolean, clubsWithExtraPlayers: any[]) => {
+ const clubsWithExtraPlayersIds = clubsWithExtraPlayers.map((club: any) => club.id);
+ if (extraOutTransfer && !clubsWithExtraPlayersIds.includes(player.clubId)) {
+ const clubNames = clubsWithExtraPlayers.map((club => club.name)).join(',');
+ notification.warning({ message: `Je team is momenteel ongeldig. Je kan enkel een speler van ${clubNames} transfereren.` })
+ return;
+ }
+ if (isLineup) {
+ props.removeStartingPlayer(player);
+ } else {
+ props.removeBenchPlayer(player);
+ }
+
+ const playerExtra = playerIsExtra(player);
+ props.onTransferPlayerOut(player, false);
+ }
+
+ const onPlayerIn = (player: Player) => {
+ props.pickPlayer(player, false);
+ props.onTransferPlayerIn(player);
+ }
+
+ const {
+ starting, bench, boosters, initializedExternally, captainId,
+ viceCaptainId, deadlineWeekTransfers, draftTransfers, activePositionFilter,
+ pastTransfers, budget
+ } = props;
+
+ const pastTransfersFormatted = pastTransfers
+ .map(formatTransfers);
+ const deadlineWeekTransfersFormatted = deadlineWeekTransfers
+ .concat(draftTransfers)
+ .map(formatTransfers);
+ const canSaveDraftTransfers = draftTransfers
+ .filter(draftTf => !!draftTf.inId && !!draftTf.outId)
+ .length === draftTransfers.length;
+ const deadlineWeekTransfersFormattedWithoutExtra = deadlineWeekTransfers
+ .concat(draftTransfers)
+ .filter(isNotExtraTransfer)
+ .map(formatTransfers);
+
+ const team = useMemo(() => teamResult && teamResult.team, [teamResult]);
+ const notTeamOwner = useMemo(() => team && team.userId && user && (team.userId !== user.id), [team, user]);
+ const gameStarted = useMemo(() => deadlineInfo && deadlineInfo.deadlineInfo && deadlineInfo.deadlineInfo.deadlineWeek && deadlineInfo.deadlineInfo.deadlineWeek > application.competition.officialStartWeek, [deadlineInfo]);
+ const deadlineWeek = useMemo(() => deadlineInfo && deadlineInfo.deadlineInfo && deadlineInfo.deadlineInfo.deadlineWeek, [deadlineInfo])
+ const enabledWildOrFreeHit = useMemo(() => boosters.wildCard === deadlineWeek || boosters.freeHit === deadlineWeek, [boosters]);
+ const startingByPositions = useMemo(() => startingListToPositionsList([].concat(starting as any, bench as any), [2, 5, 5, 3]), [starting, bench]);
+ const remainingTransfers = useMemo(() => {
+ let remainingTransfers = null;
+ if (application.competition.weeklyTransfers) {
+ remainingTransfers = application.competition.transfersAllowed - deadlineWeekTransfersFormattedWithoutExtra.length;
+ } else if (application.competition.transferCanBeSaved) {
+ remainingTransfers = application.competition.transfersAllowed * (deadlineWeek - 1) - (deadlineWeekTransfersFormattedWithoutExtra.length + pastTransfersFormatted.length);
+ } else {
+ remainingTransfers = application.competition.transfersAllowed - (deadlineWeekTransfersFormattedWithoutExtra.length + pastTransfersFormatted.length);
+ }
+ return remainingTransfers;
+ }, [application, deadlineWeekTransfersFormattedWithoutExtra, pastTransfersFormatted, deadlineWeek]);
+ const canTransferOut = useMemo(() => remainingTransfers > 0, [remainingTransfers]);
+
+ const startingPicked = useMemo(() => starting.filter(player => player && player.id), [starting]);
+ const benchPicked = useMemo(() => bench.filter(player => player && player.id), [bench]);
+
+ return (
+ (clubs && teamResult && matches && players && deadlineInfo) && (
+
+ {(notTeamOwner || state.notFound) &&
+
+ }
+ {(team && deadlineInfo.deadlineInfo.deadlineWeek && (!gameStarted || enabledWildOrFreeHit)) &&
+
+ }
+ {
+ (initializedExternally &&
+
+
+
+ {t('transfersPage.transfersBlockTitle')}
+
+
+
{`${t('general.footballWeek')} ${deadlineWeek}`}
+
+
+
+ {
+ (draftTransfers && draftTransfers.length && canSaveDraftTransfers && team &&
+
+
+
+ ) || null
+ }
+ {
+ (draftTransfers && draftTransfers.length &&
+
+
+ ) || null
+ }
+
+
+
+
+
+
+
+ {t('general.footballLineup')}
+ props.onTransferPlayerOut(player))}
+ onPlaceholderClick={null}
+ actionLessPlayerIds={null}
+ playerPointsColor={"#000"}
+ playerPointsBgColor="#84FF00"
+ assetsCdn={""}
+ />
+
+
+
+
+ {t('general.footballAllPlayers')}
+
+ props.isPickAble(player, false, true)}
+ playerType={PlayerType.SoccerShirt}
+ actionLabel={t('transferPage.transferButtonLabel')}
+ data={players}
+ playerTax={application.competition.transferTaxPercentage}
+ onPick={onPlayerIn}
+ action
+ showHeader={false}
+ size={10}
+ />
+
+
+
+
+ ) || null
+ }
+ onTransferConfirmCancel()}
+ onConfirm={(e: any) => onTransferConfirmAccept()}
+ title={t('transfersPage.transferConfirmTitle')}
+ text={t('transfersPage.transferConfirmMessage')}
+ />
+
+ )
+ )
+
+};
+
+export const TransfersPage = () => AbstractTeam(_Transfers, {});
\ No newline at end of file
diff --git a/src/routes.tsx b/src/routes.tsx
index 3960d92..f5c0c6e 100644
--- a/src/routes.tsx
+++ b/src/routes.tsx
@@ -20,6 +20,7 @@ import { Profile } from "./pages/Profile/Profile";
import { Deadlines } from "./pages/Deadlines/Deadlines";
import { Stats } from "./pages/Stats/Stats";
import { Footer } from "./components/Footer/Footer";
+import { TransfersPage } from "./pages/Transfers/Transfers";
const Layout = ({children}: any) => {
return (
@@ -62,7 +63,7 @@ export const router = createBrowserRouter([
},
{
path: "/transfers/:id", //todo
- element:
+ element:
},
{
path: "/edit/:id", //todo
diff --git a/src/services/teamsApi.ts b/src/services/teamsApi.ts
index cb2d7e0..d9a60ca 100644
--- a/src/services/teamsApi.ts
+++ b/src/services/teamsApi.ts
@@ -5,17 +5,17 @@ import { url } from "inspector";
export const teamsApi = createApi({
reducerPath: "teamsApi",
- tagTypes: ["userTeams"],
+ tagTypes: ["userTeam"],
baseQuery: fetchBaseQuery({ baseUrl: `${config.API_URL}/teams`, credentials: "include" }),
endpoints: (builder) => ({
- getTeam: builder.query<{team:Team, players:Player[]}, number>({
+ getTeam: builder.query<{ team: Team, players: Player[], transfers: Transfer[] }, number>({
query: (teamId) => `${teamId}`,
- providesTags: ["userTeams"],
+ providesTags: ["userTeam"],
}),
- addTeam: builder.mutation<{team:Team}, object>({
- invalidatesTags: ["userTeams"],
+ addTeam: builder.mutation<{ team: Team }, object>({
+ invalidatesTags: ["userTeam"],
query: (data) => ({
url: "add",
method: "POST",
@@ -23,15 +23,24 @@ export const teamsApi = createApi({
}),
}),
- updateTeamSelection: builder.mutation<{msg: string}, {teamId:number, bench: number[], starting: number[], teamName: string}>({
- invalidatesTags: ["userTeams"],
- query: ({teamId, ...data}) => ({
+ updateTeamSelection: builder.mutation<{ msg: string }, { teamId: number, bench: number[], starting: number[], teamName: string }>({
+ invalidatesTags: ["userTeam"],
+ query: ({ teamId, ...data }) => ({
url: `${teamId}/selections`,
method: "POST",
body: data
}),
}),
+ submitTransfers: builder.mutation<{ msg: string, team: Team }, { teamId: number, transfers: Transfer[] }>({
+ invalidatesTags: ["userTeam"],
+ query: ({ teamId, ...data }) => ({
+ url: `${teamId}/transfers`,
+ method: "POST",
+ body: data,
+ })
+ }),
+
// updatePlayer: builder.mutation & Pick>({
// query: ({ id, ...put }) => ({
// url: `${id}`,
@@ -43,4 +52,4 @@ export const teamsApi = createApi({
})
});
-export const { useGetTeamQuery, useLazyGetTeamQuery, useAddTeamMutation, useUpdateTeamSelectionMutation } = teamsApi;
\ No newline at end of file
+export const { useGetTeamQuery, useLazyGetTeamQuery, useAddTeamMutation, useUpdateTeamSelectionMutation, useSubmitTransfersMutation } = teamsApi;
\ No newline at end of file
diff --git a/src/services/usersApi.ts b/src/services/usersApi.ts
index 590c5f2..9b66e45 100644
--- a/src/services/usersApi.ts
+++ b/src/services/usersApi.ts
@@ -4,7 +4,7 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const usersApi = createApi({
reducerPath: "usersApi",
- tagTypes: ["userTeams"],
+ tagTypes: ["userTeam"],
baseQuery: fetchBaseQuery({ baseUrl: `${config.API_URL}/user/`, credentials: "include" }),
endpoints: (builder) => ({
getProfile: builder.query({
@@ -19,7 +19,7 @@ export const usersApi = createApi({
getTeams: builder.query<{ teams: Team[], user: User }, void>({
query: () => "teams",
- providesTags: ["userTeams"],
+ providesTags: ["userTeam"],
async onQueryStarted(args, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled;
diff --git a/src/types/component_types.tsx b/src/types/component_types.tsx
index 866b44e..60618ba 100644
--- a/src/types/component_types.tsx
+++ b/src/types/component_types.tsx
@@ -5,7 +5,7 @@ declare type AbstractTeamType = {
setTeamName: (name: string) => void,
setCaptainId: (captainId: number) => void,
setActivePositionFilter: (positionId: number) => void,
- isPickAble: (player: Player) => any,
+ isPickAble: (player: Player, taxForPicking?: boolean, isTransferPick?: boolean) => any,
isSwapAble: (player: Player) => any,
onTeamSave: () => Promise,
onTeamReset: (team: any) => Promise,
@@ -15,7 +15,7 @@ declare type AbstractTeamType = {
removeBenchPlayer: (player: Player) => void,
removeStartingPlayer: (player: Player) => void,
removePlayer: (player: Player) => void,
- pickPlayer: (player: Player) => void,
+ pickPlayer: (player: Player, taxForPicking?: boolean) => void,
activePositionFilter?: any,
starting?: any[],
bench?: any[],
@@ -26,7 +26,7 @@ declare type AbstractTeamType = {
swapPlayerId?: any,
swappedFrom?: string | null,
teamNameChanged?: boolean,
- initTeamState: (starting: any[], bench: any[], teamName: string, budget: number, captainId?: number, viceCaptainId?: number, teamUser?: any) => void,
+ initTeamState: (starting: any[], bench: any[], teamName: string, captainId: number, budget: number, leagues?: any[] | undefined, visibleWeekId?: number | undefined, teamPointsInfo?: any, rawTransfers?: any[] | undefined, deadlineWeekTransfers?: any[] | undefined, pastTransfers?: any[] | undefined, viceCaptainId?: number, boosters?: Boosters, isTeamOwner?: boolean, teamUser?: any) => void,
resetTeamName: () => void,
onTeamNameUpdate: (teamId: number) => void,
onTeamEdit: (team: any) => void,
@@ -41,5 +41,10 @@ declare type AbstractTeamType = {
reloadUserTeams: () => void,
teamUser?: any,
savingTeamPending?: any,
- // visibleWeekId: number | null,
+ visibleWeekId: number | null,
+ boosters: Boosters,
+ initializedExternally: boolean,
+ deadlineWeekTransfers: Transfer[] | null,
+ draftTransfers: Transfer[] | null,
+ pastTransfers: Transfer[] | null,
}
\ No newline at end of file
diff --git a/src/types/index.tsx b/src/types/index.tsx
index a9f8019..bbfe323 100644
--- a/src/types/index.tsx
+++ b/src/types/index.tsx
@@ -47,7 +47,7 @@ type Player = {
pointsOverview: boolean
stats?: Statistic[]
- selection?: PlayerSelection
+ selections?: PlayerSelection[]
upcomingMatches: Match[]
}
@@ -97,6 +97,18 @@ type Team = {
name: string
userId: number
weekId: number
+ budget: number
+ freeHit: number
+ bank: number
+ tripleCaptain: number
+ wildCard: number
+}
+
+type Boosters = {
+ freeHit?: number
+ bank?: number
+ tripleCaptain?: number
+ wildCard?: number
}
type User = {
@@ -180,19 +192,19 @@ type MatchEvent = {
}
-// type Transfer = {
-// id?: number
-// datetime?: string
-// weekId?: number
-// inValue?: number
-// outValue?: number
-// inPlayer?: Player
-// outPlayer?: Player
-// extra?: boolean
-// teamId?: number
-// inId?: number | null
-// outId?: number | null
-// }
+type Transfer = {
+ id?: number
+ datetime?: string
+ weekId?: number
+ inValue?: number
+ outValue?: number
+ inPlayer?: Player
+ outPlayer?: Player
+ extra?: boolean
+ teamId?: number
+ inId?: number | null
+ outId?: number | null
+}
// type MatchState = {
// id: number