groups functional & bugfixes

This commit is contained in:
2024-06-15 22:36:45 +03:00
parent 480d2fe141
commit e51140583b
12 changed files with 399 additions and 77 deletions

View File

@ -76,6 +76,7 @@ class Queue(Base):
users = relationship("QueueUser", backref="queue", lazy="dynamic") users = relationship("QueueUser", backref="queue", lazy="dynamic")
logs = relationship("QueueLog", backref="queue", lazy="dynamic") logs = relationship("QueueLog", backref="queue", lazy="dynamic")
groups = relationship("QueueGroup", backref="queue", lazy="dynamic")
class QueueUser(Base): class QueueUser(Base):
@ -86,6 +87,7 @@ class QueueUser(Base):
queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id")) queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id"))
position = Column(Integer) position = Column(Integer)
passed = Column(Boolean, default=False) passed = Column(Boolean, default=False)
group_id = Column(UUID(as_uuid=True), ForeignKey("queuegroup.id"), nullable=True)
class QueueLog(Base): class QueueLog(Base):
@ -97,6 +99,17 @@ class QueueLog(Base):
created = Column(DateTime, default=datetime.datetime.utcnow) created = Column(DateTime, default=datetime.datetime.utcnow)
class QueueGroup(Base):
__tablename__ = "queuegroup"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = Column(String)
priority = Column(Integer)
queue_id = Column(UUID(as_uuid=True), ForeignKey("queues.id"))
users = relationship("QueueUser", backref="group", lazy="dynamic")
class Captcha(Base): class Captcha(Base):
__tablename__ = "captcha" __tablename__ = "captcha"

View File

@ -96,17 +96,21 @@ def get_current_user(
def get_current_user_or_none( def get_current_user_or_none(
token: Annotated[str, Depends(oauth2_scheme)],
db: Annotated[Session, Depends(get_db)], db: Annotated[Session, Depends(get_db)],
authorization: Annotated[Union[str, None], Header()] = None,
) -> Union[schemas.UserInDB, None]: ) -> Union[schemas.UserInDB, None]:
try: try:
payload = jwt.decode( if authorization:
token, jwt_config.SECRET_KEY, algorithms=[jwt_config.ALGORITHM] token = authorization.split()[1]
) payload = jwt.decode(
username: str = payload.get("sub") token, jwt_config.SECRET_KEY, algorithms=[jwt_config.ALGORITHM]
if username is None: )
raise credentials_exception username: str = payload.get("sub")
token_data = schemas.TokenData(username=username) if username is None:
raise credentials_exception
token_data = schemas.TokenData(username=username)
else:
return None
except JWTError: except JWTError:
return None return None
user = get_user_by_username(db, username=token_data.username) user = get_user_by_username(db, username=token_data.username)

View File

@ -4,10 +4,23 @@ from uuid import UUID
from ..auth import schemas as auth_schemas from ..auth import schemas as auth_schemas
class QueueGroup(BaseModel):
name: str
priority: int
class QueueGroupDetail(QueueGroup):
id: UUID
class Config:
from_attributes = True
class QueueUser(BaseModel): class QueueUser(BaseModel):
id: UUID id: UUID
position: int position: int
passed: bool passed: bool
group_id: UUID | None = None
user: auth_schemas.AnonUser user: auth_schemas.AnonUser
class Config: class Config:
@ -23,6 +36,7 @@ class ParticipantInfo(BaseModel):
class Queue(BaseModel): class Queue(BaseModel):
name: str name: str
description: Union[str, None] = None description: Union[str, None] = None
groups: List[QueueGroup] | None = None
class QueueInList(Queue): class QueueInList(Queue):
@ -32,8 +46,10 @@ class QueueInList(Queue):
from_attributes = True from_attributes = True
class QueueInDb(Queue): class QueueInDb(BaseModel):
id: UUID id: UUID
name: str
description: Union[str, None] = None
class Config: class Config:
from_attributes = True from_attributes = True
@ -44,6 +60,7 @@ class QueueDetail(Queue):
status: str status: str
owner_id: UUID owner_id: UUID
participants: ParticipantInfo participants: ParticipantInfo
groups: List[QueueGroupDetail] | None
class ActionResult(BaseModel): class ActionResult(BaseModel):
@ -52,3 +69,7 @@ class ActionResult(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
class JoinRequest(BaseModel):
group_id: UUID | None = None

View File

@ -1,6 +1,7 @@
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from typing import Annotated from typing import Annotated
from sqlalchemy.orm import Session from sqlalchemy.orm import Session, joinedload
from sqlalchemy import func
from uuid import UUID from uuid import UUID
import redis import redis
import asyncio import asyncio
@ -49,6 +50,14 @@ def create_queue(
) )
db.add(q) db.add(q)
db.commit() db.commit()
if new_queue.groups:
db.add_all(
instances=[
models.QueueGroup(name=qg.name, priority=qg.priority, queue_id=q.id)
for qg in new_queue.groups
]
)
db.commit()
return schemas.QueueInDb.model_validate(q) return schemas.QueueInDb.model_validate(q)
@ -64,6 +73,7 @@ def get_detailed_queue(
description=q.description, description=q.description,
status=q.status, status=q.status,
owner_id=q.owner_id, owner_id=q.owner_id,
groups=q.groups.order_by(models.QueueGroup.priority.asc()),
participants=schemas.ParticipantInfo( participants=schemas.ParticipantInfo(
total=q.users.count(), total=q.users.count(),
remaining=q.users.filter(models.QueueUser.passed == False).count(), remaining=q.users.filter(models.QueueUser.passed == False).count(),
@ -80,6 +90,7 @@ def get_detailed_queue(
async def join_queue( async def join_queue(
queue_id: UUID, queue_id: UUID,
join_request: schemas.JoinRequest | None,
client: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)], client: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)],
db: Annotated[Session, Depends(get_db)], db: Annotated[Session, Depends(get_db)],
r: Annotated[redis.client.Redis, Depends(get_redis)], r: Annotated[redis.client.Redis, Depends(get_redis)],
@ -90,10 +101,18 @@ async def join_queue(
last_qu = q.users.order_by(models.QueueUser.position.desc()).first() last_qu = q.users.order_by(models.QueueUser.position.desc()).first()
position = last_qu.position + 1 if last_qu else 0 position = last_qu.position + 1 if last_qu else 0
new_qu = models.QueueUser( new_qu = models.QueueUser(
user_id=client.id, queue_id=q.id, position=position user_id=client.id,
queue_id=q.id,
position=position,
group_id=(
join_request.group_id
if join_request and join_request.group_id
else None
),
) )
db.add(new_qu) db.add(new_qu)
db.commit() db.commit()
await rebuild_queue(queue=q, db=db)
await r.publish(str(queue_id), "updated") await r.publish(str(queue_id), "updated")
return new_qu return new_qu
raise HTTPException( raise HTTPException(
@ -129,7 +148,7 @@ async def get_queue_owner(
async def verify_queue_owner( async def verify_queue_owner(
queue_owner: Annotated[auth_schemas.UserInDB, Depends(get_queue_owner)], queue_owner: Annotated[auth_schemas.UserInDB, Depends(get_queue_owner)],
current_user: Annotated[ current_user: Annotated[
auth_schemas.UserInDB, Depends(auth_services.get_current_user) auth_schemas.UserInDB, Depends(auth_services.get_current_user_or_none)
], ],
) -> bool: ) -> bool:
return queue_owner.id == current_user.id if queue_owner and current_user else False return queue_owner.id == current_user.id if queue_owner and current_user else False
@ -139,11 +158,32 @@ async def rebuild_queue(
queue: Annotated[models.Queue, Depends(get_queue_by_id)], queue: Annotated[models.Queue, Depends(get_queue_by_id)],
db: Annotated[Session, Depends(get_db)], db: Annotated[Session, Depends(get_db)],
): ):
for i, qu in enumerate( query = (
queue.users.filter(models.QueueUser.passed == False).order_by( db.query(models.QueueUser)
models.QueueUser.position.asc() .join(
models.QueueGroup,
models.QueueUser.group_id == models.QueueGroup.id,
isouter=True,
) )
): .filter(models.QueueUser.passed == False, models.QueueUser.queue_id == queue.id)
.order_by(
func.coalesce(models.QueueGroup.priority, 0).asc(),
models.QueueUser.position.asc(),
)
.options(joinedload(models.QueueUser.group))
)
queueusers = query.all()
first_qu_found_and_queue_in_process = False
if queue.status == "active":
for i, qu in enumerate(queueusers):
if qu.position == 0:
first_qu_found_and_queue_in_process = True
del queueusers[i]
break
for i, qu in enumerate(queueusers):
if first_qu_found_and_queue_in_process:
setattr(qu, "position", i + 1)
continue
setattr(qu, "position", i) setattr(qu, "position", i)
db.commit() db.commit()

View File

@ -1,6 +1,6 @@
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import "../styles.css"; import "../styles.css";
import { Button, Input, Spin } from "antd"; import { Button, Input, Select, Spin } from "antd";
import { import {
ArrowLeftOutlined, ArrowLeftOutlined,
FileTextOutlined, FileTextOutlined,
@ -30,9 +30,10 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
const [joinQueue, { isLoading }] = useJoinQueueMutation(); const [joinQueue, { isLoading }] = useJoinQueueMutation();
const [patchAnon] = usePatchAnonMutation(); const [patchAnon] = usePatchAnonMutation();
const [newName, setNewName] = useState(""); const [newName, setNewName] = useState("");
const [selectedGroup, setSelectedGroup] = useState<string>();
const onJoinButtonClick = () => { const onJoinButtonClick = () => {
joinQueue(props.id) joinQueue({ queueId: props.id, data: { group_id: selectedGroup } })
.unwrap() .unwrap()
.then(() => navigate(`/queue/${props.id}`)) .then(() => navigate(`/queue/${props.id}`))
.then(() => refetch()) .then(() => refetch())
@ -63,20 +64,20 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
</> </>
) : ( ) : (
<div className="queue-info"> <div className="queue-info">
<Title level={3} style={{ textAlign: "left" }}>
{data?.name}
</Title>
<p>
<FileTextOutlined />
{" "}
{data?.description}
</p>
<p>
<UserOutlined />
{" "}
{data?.participants?.remaining} / {data?.participants?.total}
</p>
<Spin spinning={isLoading}> <Spin spinning={isLoading}>
<Title level={3} style={{ textAlign: "left" }}>
{data?.name}
</Title>
<p>
<FileTextOutlined />
{" "}
{data?.description}
</p>
<p>
<UserOutlined />
{" "}
{data?.participants?.remaining} / {data?.participants?.total}
</p>
<Title level={4}>{tr("Update your name")}</Title> <Title level={4}>{tr("Update your name")}</Title>
<div style={{ display: "flex", flexFlow: "row", width: "30vw" }}> <div style={{ display: "flex", flexFlow: "row", width: "30vw" }}>
<Input <Input
@ -88,6 +89,22 @@ const ApproveQueueJoinCard = (props: { id: string }): JSX.Element => {
{tr("Update")} {tr("Update")}
</Button> </Button>
</div> </div>
{data?.groups.length !== undefined && data?.groups.length > 0 && (
<>
<Title level={4}>{tr("Select group in queue")}</Title>
<Select
options={data?.groups.map((obj) => ({
value: obj.id,
label: obj.name,
}))}
placeholder={tr("Select group...")}
value={selectedGroup}
style={{ width: "30vw", marginTop: "1rem" }}
onChange={setSelectedGroup}
/>
</>
)}
<Button <Button
style={{ width: "100%", marginTop: "2rem" }} style={{ width: "100%", marginTop: "2rem" }}
type="primary" type="primary"

View File

@ -1,16 +1,23 @@
import React, { useContext } from "react"; import React, { useContext, useReducer, useState } from "react";
import { import {
CreateQueue,
CreateQueueRequest, CreateQueueRequest,
Queue, Queue,
useCreateQueueMutation, useCreateQueueMutation,
} from "../../slice/QueueApi"; } from "../../slice/QueueApi";
import "../styles.css"; import "../styles.css";
import { Button, Form, Input, Spin } from "antd"; import { Button, Form, Input, List, Spin, Tooltip, Typography } from "antd";
import Title from "antd/es/typography/Title"; import Title from "antd/es/typography/Title";
import tr from "../../config/translation"; import tr from "../../config/translation";
import { MessageContext } from "../../App"; import { MessageContext } from "../../App";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { PlusCircleOutlined } from "@ant-design/icons"; import {
DeleteOutlined,
DownOutlined,
PlusCircleOutlined,
QuestionCircleOutlined,
UpOutlined,
} from "@ant-design/icons";
const CreateQueueCard = (): JSX.Element => { const CreateQueueCard = (): JSX.Element => {
const messageApi = useContext(MessageContext); const messageApi = useContext(MessageContext);
@ -19,14 +26,95 @@ const CreateQueueCard = (): JSX.Element => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [createQueue, { isLoading }] = useCreateQueueMutation(); const [createQueue, { isLoading }] = useCreateQueueMutation();
const submit = (formData: CreateQueueRequest) => { const [newGroupValue, setNewGroupValue] = useState("");
createQueue(formData) const [groupsList, setGroupsList] = useState<[{ name: string; id: string }]>([
{ name: tr("Default"), id: "default" },
]);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
const pushGroupUp = (element: { name: string; id: string }) => {
const index = groupsList.indexOf(element);
if (index > 0) {
const newArr = groupsList;
const temp = newArr[index - 1];
newArr[index - 1] = element;
newArr[index] = temp;
setGroupsList(newArr);
forceUpdate();
}
};
const pushGroupDown = (element: { name: string; id: string }) => {
const index = groupsList.indexOf(element);
if (index < groupsList.length - 1) {
const newArr = groupsList;
const temp = newArr[index + 1];
newArr[index + 1] = element;
newArr[index] = temp;
setGroupsList(newArr);
forceUpdate();
}
};
const deleteGroup = (element: { name: string; id: string }) => {
if (groupsList.includes(element)) {
const newArr = groupsList;
newArr.splice(groupsList.indexOf(element), 1);
setGroupsList(newArr);
forceUpdate();
}
};
type OrderedGroup = { name: string; priority: number };
const getOrderedGroups = () => {
const divId = groupsList.findIndex((ele) => ele.id === "default");
if (divId || divId === 0) {
const arr1: OrderedGroup[] = [];
groupsList
.slice(0, divId)
.reverse()
.forEach((ele, i) => arr1.push({ name: ele.name, priority: -(i + 1) }));
const arr2: OrderedGroup[] = [];
groupsList
.slice(divId + 1)
.forEach((ele, i) => arr2.push({ name: ele.name, priority: i + 1 }));
return [...arr1, ...arr2];
}
return [];
};
getOrderedGroups();
const submit = (formData: CreateQueue) => {
createQueue({ ...formData, groups: getOrderedGroups() })
.unwrap() .unwrap()
.then((data: Queue) => navigate(`/queue/${data.id}`)) .then((data: Queue) => navigate(`/queue/${data.id}`))
.then(() => messageApi.success(tr("Queue created"))) .then(() => messageApi.success(tr("Queue created")))
.catch(() => messageApi.error(tr("Failed to create queue"))); .catch(() => messageApi.error(tr("Failed to create queue")));
}; };
const addGroupToList = () => {
if (newGroupValue) {
const tmp = groupsList;
if (tmp) {
tmp.push({
name: newGroupValue,
id: tmp.length.toString(),
});
setGroupsList(tmp);
} else {
setGroupsList((v) => [
{
name: newGroupValue,
id: v.length.toString(),
},
]);
}
setNewGroupValue("");
}
};
return ( return (
<div className="card"> <div className="card">
<Spin spinning={isLoading}> <Spin spinning={isLoading}>
@ -55,6 +143,77 @@ const CreateQueueCard = (): JSX.Element => {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item>
<List
dataSource={groupsList}
header={
<div style={{ display: "flex", gap: "1rem" }}>
<Title level={4}>
{tr("Queue groups") + " " + tr("(optional)")}
</Title>
<Tooltip
title={tr(
"Queue's groups function allows you to define groups inside this particular queue. Groups have name and priority. Every participant selects theirs group for this queue, and their position is calculated by group's priority. Groups higher in the list will have higher priority."
)}
>
<QuestionCircleOutlined />
</Tooltip>
</div>
}
footer={
<div style={{ display: "flex", gap: "1rem" }}>
<Input
placeholder={tr("Group's name")}
value={newGroupValue}
onChange={(e) => setNewGroupValue(e.target.value)}
onPressEnter={addGroupToList}
/>
<Button onClick={addGroupToList}>{tr("Add group")}</Button>
</div>
}
renderItem={(item, index) => (
<List.Item
style={{
display: "flex",
alignContent: "space-between",
gap: "1rem",
}}
>
<div
style={{
display: "flex",
alignContent: "space-between",
gap: "1rem",
}}
>
<Typography.Text>#{index + 1}</Typography.Text>
<Typography.Text>{item.name}</Typography.Text>
</div>
{item.id !== "default" && (
<div style={{ display: "flex" }}>
<Button
onClick={() => pushGroupUp(item)}
icon={<UpOutlined />}
disabled={groupsList.indexOf(item) === 0}
/>
<Button
icon={<DownOutlined />}
onClick={() => pushGroupDown(item)}
disabled={
groupsList.indexOf(item) === groupsList.length - 1
}
/>
<Button
icon={<DeleteOutlined />}
onClick={() => deleteGroup(item)}
/>
</div>
)}
</List.Item>
)}
></List>
</Form.Item>
<Button <Button
style={{ width: "100%" }} style={{ width: "100%" }}
icon={<PlusCircleOutlined />} icon={<PlusCircleOutlined />}

View File

@ -182,7 +182,14 @@ const QueueCard = (): JSX.Element => {
{tr("Queue participants")} {tr("Queue participants")}
</Title> </Title>
{data?.participants.users_list.map((v) => { {data?.participants.users_list.map((v) => {
return <AnonUserCard key={v.id} queueUser={v} queue={data} />; return (
<AnonUserCard
key={v.id}
queueUser={v}
queue={data}
refetch={refetch}
/>
);
})} })}
</div> </div>
</Spin> </Spin>

View File

@ -122,4 +122,5 @@
border-radius: 10px; border-radius: 10px;
padding: 1rem; padding: 1rem;
border: 2px solid #00d8a4; border: 2px solid #00d8a4;
justify-content: space-between;
} }

View File

@ -25,6 +25,7 @@ const getProfileText = (name: string): string => {
const AnonUserCard = (props: { const AnonUserCard = (props: {
queueUser: QueueUser; queueUser: QueueUser;
queue: QueueDetail | undefined; queue: QueueDetail | undefined;
refetch: () => void;
}): JSX.Element => { }): JSX.Element => {
const messageApi = useContext(MessageContext); const messageApi = useContext(MessageContext);
@ -35,47 +36,49 @@ const AnonUserCard = (props: {
if (props.queue) { if (props.queue) {
passAction(props.queue.id) passAction(props.queue.id)
.unwrap() .unwrap()
.then(() => messageApi.success(tr("You passed"))) .then(props.refetch)
.catch(() => messageApi.error(tr("Failed to pass"))); .then(() => messageApi.success(tr("You left the queue")))
.catch(() => messageApi.error(tr("Failed to left")));
} }
}; };
return ( return (
<div className="anon-card"> <div className="anon-card">
<span style={{ marginRight: "1rem" }}>#{props.queueUser.position}</span>
<div <div
className="anon-circle" style={{
style={{ background: UUIDToColor(props.queueUser.user.id) }} display: "flex",
gap: "0.5rem",
alignItems: "center",
flexFlow: "row wrap",
}}
> >
{props.queueUser.user.name <span style={{ marginRight: "1rem" }}>#{props.queueUser.position}</span>
? getProfileText(props.queueUser.user.name) <div
: props.queueUser.id.substring(0, 2)} className="anon-circle"
</div> style={{ background: UUIDToColor(props.queueUser.user.id) }}
<p color="white" style={{ marginLeft: "10px" }}>
{props.queueUser.user.name
? props.queueUser.user.name
: tr("Anonymous") + " #" + props.queueUser.id.substring(0, 4)}
</p>
{props.queueUser && clientId === props.queueUser.user.id && (
<span
style={{
background: "#00d8a4",
marginLeft: "1rem",
marginRight: "1rem",
marginBottom: "0",
borderRadius: "5px",
padding: "2px",
}}
> >
{tr("YOU")} {props.queueUser.user.name
</span> ? getProfileText(props.queueUser.user.name)
)} : props.queueUser.id.substring(0, 2)}
{props.queueUser && </div>
clientId === props.queueUser.user.id && <p color="white" style={{ marginLeft: "10px" }}>
props.queueUser.position === 0 && {props.queueUser.user.name
props.queue?.status === "active" && ( ? props.queueUser.user.name
: tr("Anonymous") + " #" + props.queueUser.id.substring(0, 4)}
</p>
{props.queueUser.group_id && (
<p color="white" style={{ marginLeft: "10px" }}>
{tr("Group") +
": " +
props.queue?.groups.find(
(ele) => ele.id === props.queueUser.group_id
)?.name}
</p>
)}
{props.queueUser && clientId === props.queueUser.user.id && (
<span <span
style={{ style={{
background: "#00d8a4",
marginLeft: "1rem", marginLeft: "1rem",
marginRight: "1rem", marginRight: "1rem",
marginBottom: "0", marginBottom: "0",
@ -83,16 +86,43 @@ const AnonUserCard = (props: {
padding: "2px", padding: "2px",
}} }}
> >
{tr("It is your turn!")} {tr("YOU")}
</span> </span>
)} )}
{props.queueUser && {props.queueUser &&
clientId === props.queueUser.user.id && clientId === props.queueUser.user.id &&
(props.queueUser.position === 0 && props.queue?.status === "active" ? ( props.queueUser.position === 0 &&
<Button onClick={() => passQueue()}>{tr("Pass")}</Button> props.queue?.status === "active" && (
) : ( <span
<Button onClick={() => passQueue()}>{tr("Leave")}</Button> style={{
))} marginLeft: "1rem",
marginRight: "1rem",
marginBottom: "0",
borderRadius: "5px",
padding: "2px",
}}
>
{tr("It is your turn!")}
</span>
)}
</div>
<div
style={{
display: "flex",
gap: "0.5rem",
alignItems: "center",
flexFlow: "row wrap",
}}
>
{props.queueUser &&
clientId === props.queueUser.user.id &&
(props.queueUser.position === 0 &&
props.queue?.status === "active" ? (
<Button onClick={() => passQueue()}>{tr("Pass")}</Button>
) : (
<Button onClick={() => passQueue()}>{tr("Leave")}</Button>
))}
</div>
</div> </div>
); );
}; };

View File

@ -17,6 +17,9 @@ export const darkTheme: ThemeConfig = {
activeBorderColor: "#001529", activeBorderColor: "#001529",
colorTextPlaceholder: "grey", colorTextPlaceholder: "grey",
}, },
Select: {
colorTextPlaceholder: "grey",
},
}, },
}; };

View File

@ -30,6 +30,7 @@ export type QueueUser = {
position: number; position: number;
passed: boolean; passed: boolean;
user: AnonUser; user: AnonUser;
group_id: string | undefined;
}; };
export type AnonUser = { export type AnonUser = {

View File

@ -3,9 +3,26 @@ import { baseUrl } from "../config/baseUrl";
import { RootState } from "../config/store"; import { RootState } from "../config/store";
import { QueueUser } from "./AuthApi"; import { QueueUser } from "./AuthApi";
type OrderedGroup = {
name: string;
priority: number;
};
type ResponseGroup = {
id: string;
name: string;
priority: number;
};
export type CreateQueueRequest = { export type CreateQueueRequest = {
name: string; name: string;
description: string | null; description: string | null;
groups: OrderedGroup[];
};
export type CreateQueue = {
name: string;
description: string | null;
}; };
export type Queue = { export type Queue = {
@ -20,6 +37,7 @@ export type QueueDetail = {
description: string | null; description: string | null;
status: string; status: string;
owner_id: string; owner_id: string;
groups: ResponseGroup[];
participants: { participants: {
total: number; total: number;
remaining: number; remaining: number;
@ -27,6 +45,10 @@ export type QueueDetail = {
}; };
}; };
export type JoinRequest = {
group_id: string | undefined;
};
export const QueueApi = createApi({ export const QueueApi = createApi({
reducerPath: "QueueApi", reducerPath: "QueueApi",
baseQuery: fetchBaseQuery({ baseQuery: fetchBaseQuery({
@ -54,7 +76,11 @@ export const QueueApi = createApi({
query: (queueId: string | undefined) => `/${queueId}`, query: (queueId: string | undefined) => `/${queueId}`,
}), }),
joinQueue: builder.mutation({ joinQueue: builder.mutation({
query: (queueId: string) => ({ url: `/${queueId}/join`, method: "POST" }), query: (args: { queueId: string; data: JoinRequest }) => ({
url: `/${args.queueId}/join`,
method: "POST",
body: args.data,
}),
}), }),
createQueue: builder.mutation({ createQueue: builder.mutation({
query: (data: CreateQueueRequest) => ({ query: (data: CreateQueueRequest) => ({