Cleanup estimation sessions list context

This commit is contained in:
Pijus Kamandulis 2024-10-12 11:45:23 +03:00
parent e34c8524b7
commit f4d3005acd
7 changed files with 162 additions and 309 deletions

View File

@ -1,5 +1,5 @@
import { useForm } from '@tanstack/react-form';
import { useEstimationSessions } from '../../lib/context/estimationSession';
import { useEstimationsList } from '../../lib/context/estimationsList';
import { useUser } from '../../lib/context/user';
import Input from '../Input';
import Button from '../Button';
@ -12,13 +12,13 @@ const CreateEstimationSessionForm: React.FC<
CreateEstimationSessionFormProps
> = ({ onCreated }) => {
const user = useUser();
const estimationSessions = useEstimationSessions();
const estimationsList = useEstimationsList();
const form = useForm({
defaultValues: {
name: '',
},
onSubmit: async ({ value }) => {
await estimationSessions?.add({
await estimationsList?.add({
name: value.name,
userId: user.current?.$id,
});

View File

@ -14,6 +14,7 @@ import {
import { DatabaseModels, EntityModels } from '../types';
import { useUser } from './user';
import { EstimationSessionTicket } from '../types/entityModels';
import { mapDatabaseToEntity } from '../mappers/estimationSession';
interface EstimationContextType {
setSessionId: (sessionId: string) => void;
@ -32,43 +33,6 @@ export const useEstimationContext = () => {
return useContext(EstimationContext);
};
const mapEstimationSession = (
data: DatabaseModels.EstimationSession,
{ userId }: { userId?: string },
) => {
const sessionState: EntityModels.SessionState = data.sessionState
? JSON.parse(data.sessionState)
: {
votes: [],
};
const tickets = data.tickets
? data.tickets.map<EntityModels.EstimationSessionTicket>((ticket) =>
JSON.parse(ticket),
)
: [];
const result: EntityModels.EstimationSession = {
id: data.$id,
name: data.name,
userId: data.userId,
tickets,
sessionState: {
...sessionState,
currentPlayerVote: sessionState.votes.find((x) => x.userId === userId)
?.estimate,
currentTicket: tickets.find((x) => x.id === sessionState.currentTicketId),
},
};
console.log({
result,
userId,
});
return result;
};
export const EstimationContextProvider = (props: PropsWithChildren) => {
const [sessionId, setSessionId] = useState('');
const [currentSessionData, setCurrentSessionData] =
@ -89,7 +53,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => {
)
.then((payload) => {
const userId = userData?.$id ?? ''; // TODO: Not sure if this is the user id or session
setCurrentSessionData(mapEstimationSession(payload, { userId }));
setCurrentSessionData(mapDatabaseToEntity(payload, { userId }));
});
return client.subscribe<DatabaseModels.EstimationSession>(
@ -98,7 +62,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => {
],
({ payload }) => {
const userId = userData?.$id ?? ''; // TODO: Not sure if this is the user id or session
setCurrentSessionData(mapEstimationSession(payload, { userId }));
setCurrentSessionData(mapDatabaseToEntity(payload, { userId }));
},
);
}, [sessionId, userData]);

View File

@ -1,258 +0,0 @@
import {
createContext,
PropsWithChildren,
useContext,
useEffect,
useState,
} from 'react';
import {
client,
DATABASE_ID,
databases,
ESTIMATION_SESSION_COLLECTION_ID,
} from '../appwrite';
import { ID, Models, Query } from 'appwrite';
interface EstimationSessionType extends Models.Document {
userId: string;
name: string;
tickets: string[];
sessionState: string;
}
interface EstimationSessionTicket {
id: string;
name: string;
}
interface SessionStateType {
currentTicketId: string;
votesRevealed: boolean;
votes: {
userId: string;
estimate: number;
}[];
}
interface EstimationSessionsContextType {
current: EstimationSessionType[];
add: (
estimationSession: Omit<EstimationSessionType, keyof Models.Document>,
) => Promise<void>;
remove: (id: string) => Promise<void>;
addTicket: (
sessionId: string,
ticket: Omit<EstimationSessionTicket, 'id'>,
) => Promise<void>;
getTickets: (sessionId: string) => EstimationSessionTicket[];
selectTicket: (sessionId: string, ticketId: string) => Promise<void>;
getState: (sessionId: string) => SessionStateType;
voteEstimate: (
sessionId: string,
ticketId: string,
estimate: number,
userId: string,
) => Promise<void>;
revealVotes: (sessionId: string) => Promise<void>;
}
const EstimationSessionsContext = createContext<
EstimationSessionsContextType | undefined
>(undefined);
export function useEstimationSessions() {
return useContext(EstimationSessionsContext);
}
export function EstimationSessionProvider(props: PropsWithChildren) {
const [estimationSessions, setEstimationSessions] = useState<
EstimationSessionType[]
>([]);
const add = async (
estimationSession: Omit<EstimationSessionType, keyof Models.Document>,
) => {
const response = await databases.createDocument<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
ID.unique(),
estimationSession,
);
setEstimationSessions((estimationSessions) =>
[response, ...estimationSessions].slice(0, 10),
);
};
const remove = async (id: string) => {
await databases.deleteDocument(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
id,
);
setEstimationSessions((estimationSessions) =>
estimationSessions.filter(
(estimationSession) => estimationSession.$id !== id,
),
);
await init();
};
const addTicket = async (
sessionId: string,
ticket: Omit<EstimationSessionTicket, 'id'>,
) => {
const currentSession = estimationSessions.find((x) => x.$id === sessionId);
const response = await databases.updateDocument<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
sessionId,
{
tickets: currentSession?.tickets.concat([
JSON.stringify({
...ticket,
id: crypto.randomUUID(),
}),
]),
},
);
setEstimationSessions((estimationSessions) =>
estimationSessions
.filter((x) => x.$id != sessionId)
.concat([response])
.slice(0, 10),
);
};
const getTickets = (sessionId: string) => {
return (
estimationSessions
.find((x) => x.$id === sessionId)
?.tickets.map<EstimationSessionTicket>((x) => JSON.parse(x)) ?? []
);
};
const selectTicket = async (sessionId: string, ticketId: string) => {
const response = await databases.updateDocument<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
sessionId,
{
sessionState: JSON.stringify({
currentTicketId: ticketId,
}),
},
);
setEstimationSessions((estimationSessions) =>
estimationSessions
.filter((x) => x.$id != sessionId)
.concat([response])
.slice(0, 10),
);
};
const getState = (sessionId: string): SessionStateType => {
return JSON.parse(
estimationSessions.find((x) => x.$id === sessionId)?.sessionState ?? '{}',
);
};
const voteEstimate = async (
sessionId: string,
ticketId: string,
estimate: number,
userId: string,
) => {
const currentState = getState(sessionId);
const newVotes = (currentState.votes ?? [])
.filter((x) => x.userId !== userId)
.concat([
{
estimate: estimate,
userId: userId,
},
]);
const response = await databases.updateDocument<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
sessionId,
{
sessionState: JSON.stringify({
currentTicketId: ticketId,
votes: newVotes,
}),
},
);
setEstimationSessions((estimationSessions) =>
estimationSessions
.filter((x) => x.$id != sessionId)
.concat([response])
.slice(0, 10),
);
};
const revealVotes = async (sessionId: string) => {
const currentState = getState(sessionId);
const response = await databases.updateDocument<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
sessionId,
{
sessionState: JSON.stringify({
...currentState,
VotesRevealed: true,
}),
},
);
setEstimationSessions((estimationSessions) =>
estimationSessions
.filter((x) => x.$id != sessionId)
.concat([response])
.slice(0, 10),
);
};
const init = async () => {
const response = await databases.listDocuments<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
[Query.orderDesc('$createdAt'), Query.limit(10)],
);
setEstimationSessions(response.documents);
};
useEffect(() => {
init();
return client.subscribe<EstimationSessionType>(
[
`databases.${DATABASE_ID}.collections.${ESTIMATION_SESSION_COLLECTION_ID}.documents`,
],
(payload) => {
setEstimationSessions((estimationSessions) =>
estimationSessions
.filter((x) => x.$id != payload.payload.$id)
.concat([payload.payload])
.slice(0, 10),
);
},
);
}, []);
return (
<EstimationSessionsContext.Provider
value={{
current: estimationSessions,
add,
remove,
addTicket,
getTickets,
selectTicket,
getState,
voteEstimate,
revealVotes,
}}
>
{props.children}
</EstimationSessionsContext.Provider>
);
}

View File

@ -0,0 +1,114 @@
import {
createContext,
PropsWithChildren,
useContext,
useEffect,
useState,
} from 'react';
import {
client,
DATABASE_ID,
databases,
ESTIMATION_SESSION_COLLECTION_ID,
} from '../appwrite';
import { ID, Models, Query } from 'appwrite';
import { EntityModels } from '../types';
import { mapDatabaseToEntity } from '../mappers/estimationSession';
interface EstimationSessionType extends Models.Document {
userId: string;
name: string;
tickets: string[];
sessionState: string;
}
interface EstimationsListContextType {
current: EntityModels.EstimationSession[];
add: (
estimationSession: Omit<EstimationSessionType, keyof Models.Document>,
) => Promise<void>;
remove: (id: string) => Promise<void>;
}
const EstimationsListContext = createContext<
EstimationsListContextType | undefined
>(undefined);
export function useEstimationsList() {
return useContext(EstimationsListContext);
}
export function EstimationsListContextProvider(props: PropsWithChildren) {
const [estimationSessions, setEstimationSessions] = useState<
EntityModels.EstimationSession[]
>([]);
const add = async (
estimationSession: Omit<EstimationSessionType, keyof Models.Document>,
) => {
const response = await databases.createDocument<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
ID.unique(),
estimationSession,
);
setEstimationSessions((estimationSessions) =>
[mapDatabaseToEntity(response, {}), ...estimationSessions].slice(0, 10),
);
};
const remove = async (id: string) => {
await databases.deleteDocument(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
id,
);
setEstimationSessions((estimationSessions) =>
estimationSessions.filter(
(estimationSession) => estimationSession.id !== id,
),
);
await init();
};
const init = async () => {
const response = await databases.listDocuments<EstimationSessionType>(
DATABASE_ID,
ESTIMATION_SESSION_COLLECTION_ID,
[Query.orderDesc('$createdAt'), Query.limit(10)],
);
setEstimationSessions(
response.documents.map((document) => mapDatabaseToEntity(document, {})),
);
};
useEffect(() => {
init();
return client.subscribe<EstimationSessionType>(
[
`databases.${DATABASE_ID}.collections.${ESTIMATION_SESSION_COLLECTION_ID}.documents`,
],
(payload) => {
setEstimationSessions((estimationSessions) =>
estimationSessions
.filter((x) => x.id != payload.payload.$id)
.concat([mapDatabaseToEntity(payload.payload, {})])
.slice(0, 10),
);
},
);
}, []);
return (
<EstimationsListContext.Provider
value={{
current: estimationSessions,
add,
remove,
}}
>
{props.children}
</EstimationsListContext.Provider>
);
}

View File

@ -0,0 +1,33 @@
import { DatabaseModels, EntityModels } from '../types';
export const mapDatabaseToEntity = (
data: DatabaseModels.EstimationSession,
{ userId }: { userId?: string },
) => {
const sessionState: EntityModels.SessionState = data.sessionState
? JSON.parse(data.sessionState)
: {
votes: [],
};
const tickets = data.tickets
? data.tickets.map<EntityModels.EstimationSessionTicket>((ticket) =>
JSON.parse(ticket),
)
: [];
const result: EntityModels.EstimationSession = {
id: data.$id,
name: data.name,
userId: data.userId,
tickets,
sessionState: {
...sessionState,
currentPlayerVote: sessionState.votes.find((x) => x.userId === userId)
?.estimate,
currentTicket: tickets.find((x) => x.id === sessionState.currentTicketId),
},
};
return result;
};

View File

@ -12,7 +12,7 @@ import {
import Home from './pages/Home';
import { UserContextType, UserProvider, useUser } from './lib/context/user';
import Login from './pages/Login';
import { EstimationSessionProvider } from './lib/context/estimationSession';
import { EstimationsListContextProvider } from './lib/context/estimationsList';
import { EstimationContextProvider } from './lib/context/estimation';
import Estimation from './pages/Estimation/Estimation';
import Header from './components/Header';
@ -115,12 +115,12 @@ const InnerApp = () => {
createRoot(document.getElementById('root')!).render(
<StrictMode>
<UserProvider>
<EstimationSessionProvider>
<EstimationsListContextProvider>
<EstimationContextProvider>
{/* TODO: Move ctx providers to layout */}
<InnerApp />
</EstimationContextProvider>
</EstimationSessionProvider>
</EstimationsListContextProvider>
</UserProvider>
</StrictMode>,
);

View File

@ -1,7 +1,7 @@
import './Home.css';
import { getRouteApi, Link } from '@tanstack/react-router';
import { useUser } from '../lib/context/user';
import { useEstimationSessions } from '../lib/context/estimationSession';
import { useEstimationsList } from '../lib/context/estimationsList';
import {
Card,
CreateEstimationSessionForm,
@ -15,7 +15,7 @@ const route = getRouteApi('/_authenticated/');
function Home() {
const user = useUser();
const navigate = route.useNavigate();
const estimationSessions = useEstimationSessions();
const estimationsList = useEstimationsList();
const [isDrawerOpen, setDrawerOpen] = useState(false);
return (
@ -33,16 +33,16 @@ function Home() {
<p>Estimation sessions</p>
<GridList
colNum={2}
items={estimationSessions?.current ?? []}
items={estimationsList?.current ?? []}
itemComponent={({ item }) => (
<Card
key={item.$id}
key={item.id}
title={item.name}
description={item.$id}
description={item.id}
onClick={() => {
navigate({
to: '/estimate/session/$sessionId',
params: { sessionId: item.$id },
params: { sessionId: item.id },
});
}}
/>