Allow user to set estimate after vote

This commit is contained in:
Pijus Kamandulis 2024-10-16 00:06:30 +03:00
parent 06d70bbd70
commit 36cef1ab04
9 changed files with 156 additions and 18 deletions

View File

@ -141,14 +141,28 @@ export const EstimationContextProvider = (props: PropsWithChildren) => {
);
};
const updateTicket = async ({ id, name, content }: EditTicketRequest) => {
const updateTicket = async ({
id,
name,
content,
estimate,
}: EditTicketRequest) => {
const editedTicket = currentSessionData?.tickets.find((x) => x.id === id);
if (!editedTicket) {
return;
}
if (name !== undefined) {
editedTicket.name = name;
}
if (content !== undefined) {
editedTicket.content = content;
}
if (estimate !== undefined) {
editedTicket.estimate = estimate;
}
const newTicketsValue = currentSessionData?.tickets.map((x) =>
JSON.stringify(x),

View File

@ -12,6 +12,7 @@ interface EstimationSessionTicket {
id: string;
name: string;
content: string;
estimate?: string;
}
interface SessionState {

View File

@ -1,9 +1,10 @@
interface CreateTicketRequest {
name: string;
content: string;
estimate?: string;
}
interface EditTicketRequest extends CreateTicketRequest {
interface EditTicketRequest extends Partial<CreateTicketRequest> {
id: string;
}

View File

@ -43,10 +43,12 @@ const authenticatedRoute = createRoute({
},
component: () => {
return (
<>
<div className="flex h-screen flex-col">
<Header />
<div className="flex-grow">
<Outlet />
</>
</div>
</div>
);
},
});

View File

@ -4,10 +4,11 @@ import { getRouteApi } from '@tanstack/react-router';
import TaskSidebar from './components/TaskSidebar';
import VoteSelection from './components/VoteSelection';
import VoteList from './components/VoteList';
import { Button, ButtonColor, Drawer } from '../../components';
import { Drawer } from '../../components';
import EditTicketForm from './components/EditTicketForm';
import PlayerList from './components/PlayerList';
import HtmlEmbed from '../../components/HtmlEmbed';
import EstimationResult from './components/EstimationResult';
const fibonacciSequence = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 100];
@ -44,7 +45,7 @@ const Estimation: React.FC = () => {
} = estimationState;
return (
<div className="flex h-screen">
<div className="flex h-full">
<TaskSidebar
className="w-64 overflow-y-scroll bg-gray-50 p-4 dark:bg-nero-800"
tickets={tickets}
@ -76,14 +77,20 @@ const Estimation: React.FC = () => {
<VoteList className="mt-6" revealed={revealed} votes={votes} />
<div className="mt-4">
<Button
color={ButtonColor.Error}
onClick={() => setRevealed(true)}
>
Reveal Votes
</Button>
</div>
<EstimationResult
className="mt-6"
revealed={revealed}
votes={votes}
setRevealed={setRevealed}
onSetEstimate={async (estimate: string) => {
await updateTicket({
id: currentTicket.id,
estimate,
});
await setActiveTicket(currentTicket.id);
}}
/>
</>
) : (
<p>Select a task to see the details and estimate.</p>

View File

@ -6,6 +6,7 @@ import * as yup from 'yup';
interface EditTicketFormData {
name: string;
estimate?: string;
content: string;
}
@ -21,6 +22,7 @@ const EditTicketForm: React.FC<EditTicketFormProps> = ({
const form = useForm({
defaultValues: initialData ?? {
name: '',
estimate: '',
content: '',
},
onSubmit: async ({ value }) => {
@ -58,6 +60,18 @@ const EditTicketForm: React.FC<EditTicketFormProps> = ({
/>
)}
</form.Field>
<form.Field name="estimate">
{(field) => (
<Input
label="Estimate"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
errors={field.state.meta.errors}
/>
)}
</form.Field>
<form.Field name="content">
{(field) => (
<RichEditor

View File

@ -0,0 +1,96 @@
import { useForm } from '@tanstack/react-form';
import { Button, ButtonColor, Input } from '../../../components';
import { PlayerVote } from '../../../lib/types/entityModels';
import { yupValidator } from '@tanstack/yup-form-adapter';
import * as yup from 'yup';
interface VoteListProps {
className?: string;
votes: PlayerVote[];
revealed: boolean;
setRevealed: (revealed: boolean) => Promise<void>;
onSetEstimate: (estimate: string) => Promise<void>;
}
const EstimationResult: React.FC<VoteListProps> = ({
className,
revealed,
votes,
setRevealed,
onSetEstimate,
}) => {
const form = useForm({
defaultValues: {
estimate: '',
},
onSubmit: async ({ value }) => {
await onSetEstimate(value.estimate);
},
validators: {
onChange: yup.object({
estimate: yup.string().label('Estimate').max(10).required(),
}),
},
validatorAdapter: yupValidator(),
});
if (!revealed) {
return votes.length > 0 ? (
<div className="mt-4">
<Button color={ButtonColor.Error} onClick={() => setRevealed(true)}>
Reveal Votes
</Button>
</div>
) : null;
}
const numericVotes = votes
.map((vote) => parseFloat(vote.estimate))
.filter((vote) => !isNaN(vote));
const averageVote =
numericVotes.length > 0
? numericVotes.reduce((sum, vote) => sum + vote, 0) / numericVotes.length
: null;
return (
<div className={className}>
{averageVote !== null && (
<div className="mb-4 text-lg font-semibold">
Average Vote: {averageVote.toFixed(2)}
</div>
)}
<form
className="flex items-end gap-4"
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<form.Field name="estimate">
{(field) => (
<Input
label="Estimate"
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
errors={field.state.meta.errors}
/>
)}
</form.Field>
<form.Subscribe selector={(state) => [state.canSubmit]}>
{([canSubmit]) => (
<Button type="submit" disabled={!canSubmit}>
Save
</Button>
)}
</form.Subscribe>
</form>
</div>
);
};
export default EstimationResult;

View File

@ -25,6 +25,7 @@ const TaskSidebar: React.FC<TaskSidebarProps> = ({
<Card
key={item.id}
title={item.name}
description={item.estimate && `Estimate: ${item.estimate}`}
onClick={() => onSelectTicket(item)}
onEdit={() => onEditTicket(item.id)}
/>

View File

@ -10,7 +10,9 @@ interface VoteListProps {
const VoteList: React.FC<VoteListProps> = ({ className, votes, revealed }) => {
return (
<div className={className}>
{votes.length > 0 && (
<h2 className="mb-4 text-xl font-bold">Player Votes</h2>
)}
<GridList
colNum={5}
itemComponent={({ item }, idx) => (