Skip to content

Commit

Permalink
add additional cost ui
Browse files Browse the repository at this point in the history
  • Loading branch information
tructn committed May 31, 2024
1 parent 9a962e5 commit 7ad8349
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 35 deletions.
5 changes: 5 additions & 0 deletions frontend/src/models/cost/additional-cost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface AdditionalCost {
id: number
description: string
amount: number
}
3 changes: 3 additions & 0 deletions frontend/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ export interface SportCenterModel {
name: string
location: string
}


export type { AdditionalCost } from './cost/additional-cost'
107 changes: 107 additions & 0 deletions frontend/src/screens/dashboard/additional-cost-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import {
ActionIcon,
Button,
NumberInput,
Table,
TextInput,
} from "@mantine/core";
import React, { useRef, useState } from "react";
import { FiPlusCircle, FiTrash2 } from "react-icons/fi";
import { IoSave } from "react-icons/io5";
import { AdditionalCost } from "../../models";

interface Props {
onSaveClick?: (costs: AdditionalCost[]) => void;
}

const AdditionalCostEditor: React.FC<Props> = ({ onSaveClick }) => {
const [description, setDescription] = useState("");
const [amount, setAmount] = useState(0);
const [costs, setCosts] = useState<AdditionalCost[]>([]);
const nameRef = useRef<any>(null!);

const handleRemove = (id: number) => {
setCosts(costs.filter((c) => c.id !== id));
};

const handleSubmit = (e: any) => {
e.preventDefault();

setCosts([
...costs,
{
id: costs.length + 1,
description,
amount,
},
]);

setDescription("");
setAmount(0);
nameRef.current.focus();
};

const handleSave = () => {
if (onSaveClick) {
onSaveClick(costs);
}
};

return (
<form className="flex flex-col gap-5" onSubmit={handleSubmit}>
<div className="flex items-center gap-2">
<TextInput
ref={nameRef}
placeholder="Description"
value={description}
onChange={(e) => setDescription(e.currentTarget.value)}
/>
<NumberInput
value={amount}
placeholder="Amount"
onChange={(e) => setAmount(+e)}
/>
<ActionIcon variant="light" type="submit" size="lg">
<FiPlusCircle />
</ActionIcon>
</div>
<div>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Name</Table.Th>
<Table.Th>Amount</Table.Th>
<Table.Th></Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{costs.map((item) => (
<Table.Tr key={item.id}>
<Table.Td>{item.description}</Table.Td>
<Table.Td>£{item.amount}</Table.Td>
<Table.Td align="right">
<ActionIcon
variant="outline"
color="red"
onClick={() => handleRemove(item.id)}
>
<FiTrash2 />
</ActionIcon>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</div>
<Button
onClick={handleSave}
disabled={costs.length === 0}
leftSection={<IoSave />}
>
Save changes
</Button>
</form>
);
};

export default AdditionalCostEditor;
19 changes: 13 additions & 6 deletions frontend/src/screens/dashboard/match-figure.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@ interface Prop {
label: string;
figure?: string;
icon?: React.ReactNode;
onEdit?: () => void;
onActionClick?: () => void;
actionIcon?: React.ReactNode;
}

const MatchFigure: React.FC<Prop> = ({ figure, label, icon, onEdit }) => {
const MatchFigure: React.FC<Prop> = ({
figure,
label,
icon,
onActionClick,
actionIcon,
}) => {
return (
<div className="relative flex h-28 w-full items-end justify-end gap-2 rounded bg-slate-100 p-5">
<div className="relative flex h-28 w-full items-end justify-end gap-2 rounded bg-slate-100 p-5 ring-rose-500 ring-offset-2 hover:ring-2">
<div className="absolute left-2 top-2 flex items-center gap-2">
{icon}
{label}
</div>
{onEdit && (
{onActionClick && (
<div className="absolute right-2 top-2">
<ActionIcon variant="transparent" onClick={() => onEdit()}>
<FiEdit color="black" />
<ActionIcon variant="transparent" onClick={() => onActionClick()}>
{actionIcon ? actionIcon : <FiEdit color="black" />}
</ActionIcon>
</div>
)}
Expand Down
70 changes: 46 additions & 24 deletions frontend/src/screens/dashboard/match-list-content.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import httpService from "../../common/http-service";
import MatchFigure from "./match-figure";
import React, { useMemo } from "react";
import React, { useMemo, useState } from "react";
import ToggleButton from "../../components/toggle-button";
import { Alert, Button, Modal, NumberInput } from "@mantine/core";
import { FaCashRegister } from "react-icons/fa";
import { FiDollarSign } from "react-icons/fi";
import { MatchSummaryModel, RegistrationModel } from "../../models";
import { FiDollarSign, FiTrash2 } from "react-icons/fi";
import { useDisclosure } from "@mantine/hooks";
import { useMutation } from "@tanstack/react-query";
import {
ActionIcon,
Alert,
Button,
Modal,
NumberInput,
Table,
TextInput,
} from "@mantine/core";
import {
AdditionalCost,
MatchSummaryModel,
RegistrationModel,
} from "../../models";
import {
IoBan,
IoBaseball,
Expand All @@ -20,15 +32,14 @@ import {
useMatchCostQuery,
useRegistrationsByMatchQuery,
} from "../../hooks/queries";
import { useState } from "react";
import AdditionalCostEditor from "./additional-cost-editor";

interface Prop {
match: MatchSummaryModel;
}

const MatchListContent: React.FC<Prop> = ({ match }) => {
const [currentCost, setCurrentCost] = useState(0);

const [costOpened, { open: openCost, close: closeCost }] =
useDisclosure(false);

Expand All @@ -37,11 +48,13 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {
{ open: openAdditionalCost, close: closeAdditionalCost },
] = useDisclosure(false);

const { data: registrations, refetch: refetchRegistrations } =
useRegistrationsByMatchQuery(match.matchId);
const { data: registrations, refetch: reload } = useRegistrationsByMatchQuery(
match.matchId,
);

const { data: cost, refetch: refetchCost } = useMatchCostQuery(match.matchId);
const { data: additionalCost } = useMatchAdditionalCostQuery(match.matchId);
const { data: cost, refetch: reloadCost } = useMatchCostQuery(match.matchId);
const { data: additionalCost, refetch: reloadAdditionalCost } =
useMatchAdditionalCostQuery(match.matchId);

const statPercentage = useMemo(() => {
return Math.round(
Expand Down Expand Up @@ -70,27 +83,36 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {
return totalPlayer === 0 ? 0 : total / totalPlayer;
}, [cost, additionalCost, registrations]);

const handleSaveAdditionalCosts = async (costs: AdditionalCost[]) => {
await httpService.put(
`api/v1/matches/${match.matchId}/additional-costs`,
costs,
);
closeAdditionalCost();
reloadAdditionalCost();
};

const registerMut = useMutation({
onSuccess: refetchRegistrations,
onSuccess: reload,
mutationFn: (model: RegistrationModel) =>
httpService.post("api/v1/registrations", model),
});

const unregisterMut = useMutation({
onSuccess: refetchRegistrations,
onSuccess: reload,
mutationFn: (registrationId: number) =>
httpService.del(`api/v1/registrations/${registrationId}`),
});

const paidMut = useMutation({
onSuccess: refetchRegistrations,
onSuccess: reload,
mutationFn: (registrationId: number) => {
return httpService.put(`api/v1/registrations/${registrationId}/paid`, {});
},
});

const unpaidMut = useMutation({
onSuccess: refetchRegistrations,
onSuccess: reload,
mutationFn: (registrationId: number) => {
return httpService.put(
`api/v1/registrations/${registrationId}/unpaid`,
Expand All @@ -101,7 +123,7 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {

const updateCostMut = useMutation({
onSuccess() {
refetchCost();
reloadCost();
closeCost();
},
mutationFn: (model: { matchId: number; cost: number }) => {
Expand All @@ -113,8 +135,8 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {

return (
<div className="flex flex-col gap-3">
<div className="flex gap-3">
<div className="flex w-1/2 flex-col gap-3">
<div className="grid grid-cols-1 gap-3 xl:grid-cols-2">
<div className="flex flex-col gap-3">
<div>
<Alert
variant="light"
Expand All @@ -128,7 +150,7 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {
</Alert>
</div>

<div className="grid grid-cols-4 justify-between gap-3">
<div className="grid grid-cols-2 justify-between gap-3 md:grid-cols-3 xl:grid-cols-4">
<MatchFigure
icon={<IoPersonSharp />}
label="Total players"
Expand Down Expand Up @@ -157,19 +179,19 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {
icon={<FiDollarSign />}
label="Cost"
figure={${cost ?? 0}`}
onEdit={openCost}
onActionClick={openCost}
></MatchFigure>

<MatchFigure
icon={<FaCashRegister />}
label="Addtional Cost"
label="Addtional cost"
figure={${additionalCost ?? 0}`}
onEdit={openAdditionalCost}
onActionClick={openAdditionalCost}
></MatchFigure>
</div>
</div>

<div className="flex w-1/2 flex-col">
<div className="flex flex-col">
{registrations &&
registrations.map((reg) => {
return (
Expand Down Expand Up @@ -242,9 +264,9 @@ const MatchListContent: React.FC<Prop> = ({ match }) => {
<Modal
opened={additionalCostOpened}
onClose={closeAdditionalCost}
title="Update Additional Costs"
title="Add Additional Cost"
>
<NumberInput />
<AdditionalCostEditor onSaveClick={handleSaveAdditionalCosts} />
</Modal>
</div>
);
Expand Down
14 changes: 9 additions & 5 deletions handler/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,21 +128,25 @@ func (h *MatchHandler) GetAdditionalCost(c *gin.Context) {

func (h *MatchHandler) CreateAdditionalCost(c *gin.Context) {
matchId := params.Get(c, "matchId")
dto := dto.AdditionalCostDto{}
if err := c.BindJSON(&dto); err != nil {
c.AbortWithStatus(http.StatusBadRequest)
costs := []dto.AdditionalCostDto{}
if err := c.BindJSON(&costs); err != nil {
h.logger.Debugf("error parse dto: %v", err.Error())
c.AbortWithError(http.StatusBadRequest, err)
return
}

match := domain.Match{}
if err := h.db.Find(&match, matchId).Error; err != nil {
if err := h.db.Preload("AdditionalCosts").Find(&match, matchId).Error; err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "match not found",
})
return
}

match.AddCost(dto.Description, dto.Amount)
for _, c := range costs {
match.AddCost(c.Description, c.Amount)
}

h.db.Save(&match)
c.JSON(http.StatusOK, match)
}

0 comments on commit 7ad8349

Please sign in to comment.