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

View File

@ -14,6 +14,7 @@ import {
import { DatabaseModels, EntityModels } from '../types'; import { DatabaseModels, EntityModels } from '../types';
import { useUser } from './user'; import { useUser } from './user';
import { EstimationSessionTicket } from '../types/entityModels'; import { EstimationSessionTicket } from '../types/entityModels';
import { mapDatabaseToEntity } from '../mappers/estimationSession';
interface EstimationContextType { interface EstimationContextType {
setSessionId: (sessionId: string) => void; setSessionId: (sessionId: string) => void;
@ -32,43 +33,6 @@ export const useEstimationContext = () => {
return useContext(EstimationContext); 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) => { export const EstimationContextProvider = (props: PropsWithChildren) => {
const [sessionId, setSessionId] = useState(''); const [sessionId, setSessionId] = useState('');
const [currentSessionData, setCurrentSessionData] = const [currentSessionData, setCurrentSessionData] =
@ -89,7 +53,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => {
) )
.then((payload) => { .then((payload) => {
const userId = userData?.$id ?? ''; // TODO: Not sure if this is the user id or session 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>( return client.subscribe<DatabaseModels.EstimationSession>(
@ -98,7 +62,7 @@ export const EstimationContextProvider = (props: PropsWithChildren) => {
], ],
({ payload }) => { ({ payload }) => {
const userId = userData?.$id ?? ''; // TODO: Not sure if this is the user id or session 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]); }, [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 Home from './pages/Home';
import { UserContextType, UserProvider, useUser } from './lib/context/user'; import { UserContextType, UserProvider, useUser } from './lib/context/user';
import Login from './pages/Login'; import Login from './pages/Login';
import { EstimationSessionProvider } from './lib/context/estimationSession'; import { EstimationsListContextProvider } from './lib/context/estimationsList';
import { EstimationContextProvider } from './lib/context/estimation'; import { EstimationContextProvider } from './lib/context/estimation';
import Estimation from './pages/Estimation/Estimation'; import Estimation from './pages/Estimation/Estimation';
import Header from './components/Header'; import Header from './components/Header';
@ -115,12 +115,12 @@ const InnerApp = () => {
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<StrictMode> <StrictMode>
<UserProvider> <UserProvider>
<EstimationSessionProvider> <EstimationsListContextProvider>
<EstimationContextProvider> <EstimationContextProvider>
{/* TODO: Move ctx providers to layout */} {/* TODO: Move ctx providers to layout */}
<InnerApp /> <InnerApp />
</EstimationContextProvider> </EstimationContextProvider>
</EstimationSessionProvider> </EstimationsListContextProvider>
</UserProvider> </UserProvider>
</StrictMode>, </StrictMode>,
); );

View File

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