Compare commits
5 Commits
2b42033c70
...
e489b045fc
| Author | SHA1 | Date | |
|---|---|---|---|
| e489b045fc | |||
| b00cde2a6e | |||
| bfa04936b2 | |||
| 24fd13340e | |||
| d037f1ded0 |
@@ -130,7 +130,7 @@ def row_to_my_order(row: sqlite3.Row) -> dict:
|
|||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
submission_choices = {}
|
submission_choices = {}
|
||||||
|
|
||||||
return {
|
result = {
|
||||||
"id": row["id"],
|
"id": row["id"],
|
||||||
"title": row["title"],
|
"title": row["title"],
|
||||||
"description": row["description"],
|
"description": row["description"],
|
||||||
@@ -145,3 +145,8 @@ def row_to_my_order(row: sqlite3.Row) -> dict:
|
|||||||
"paid": bool(row["paid"]) if row["paid"] is not None else False,
|
"paid": bool(row["paid"]) if row["paid"] is not None else False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "has_unpaid_submissions" in row.keys() and row["has_unpaid_submissions"] is not None:
|
||||||
|
result["has_unpaid_submissions"] = bool(row["has_unpaid_submissions"])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|||||||
@@ -674,7 +674,12 @@ def get_my_orders(
|
|||||||
uot.submission_token,
|
uot.submission_token,
|
||||||
s.choices_json,
|
s.choices_json,
|
||||||
s.accepted,
|
s.accepted,
|
||||||
s.paid
|
s.paid,
|
||||||
|
CASE
|
||||||
|
WHEN uot.admin_token IS NOT NULL THEN
|
||||||
|
(SELECT COUNT(*) FROM submissions s2 WHERE s2.group_order_id = go.id AND s2.accepted = 1 AND (s2.paid IS NULL OR s2.paid = 0))
|
||||||
|
ELSE 0
|
||||||
|
END AS has_unpaid_submissions
|
||||||
FROM user_order_tokens uot
|
FROM user_order_tokens uot
|
||||||
JOIN group_orders go ON go.id = uot.group_order_id
|
JOIN group_orders go ON go.id = uot.group_order_id
|
||||||
LEFT JOIN submissions s ON s.submission_token = uot.submission_token
|
LEFT JOIN submissions s ON s.submission_token = uot.submission_token
|
||||||
|
|||||||
+7
-2
@@ -36,15 +36,20 @@ services:
|
|||||||
container_name: lunchtime-mailpit
|
container_name: lunchtime-mailpit
|
||||||
ports:
|
ports:
|
||||||
- "8025:8025"
|
- "8025:8025"
|
||||||
|
volumes:
|
||||||
|
- mailpit-data:/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8020:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ../nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
- ../nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
- frontend
|
- frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mailpit-data:
|
||||||
@@ -6,6 +6,7 @@ import ExportSelectionModal, { type ExportSelectionState } from "../modals/Expor
|
|||||||
|
|
||||||
type MenuConfigImportExportProps = {
|
type MenuConfigImportExportProps = {
|
||||||
config?: OrderFormConfig | null;
|
config?: OrderFormConfig | null;
|
||||||
|
description?: string;
|
||||||
onImportConfig?: (config: OrderFormConfig) => void | Promise<void>;
|
onImportConfig?: (config: OrderFormConfig) => void | Promise<void>;
|
||||||
showImport?: boolean;
|
showImport?: boolean;
|
||||||
showExport?: boolean;
|
showExport?: boolean;
|
||||||
@@ -23,6 +24,7 @@ function normalizeConfig(value: unknown): OrderFormConfig {
|
|||||||
|
|
||||||
export default function MenuConfigImportExport({
|
export default function MenuConfigImportExport({
|
||||||
config,
|
config,
|
||||||
|
description,
|
||||||
onImportConfig,
|
onImportConfig,
|
||||||
showImport = true,
|
showImport = true,
|
||||||
showExport = true,
|
showExport = true,
|
||||||
@@ -98,8 +100,8 @@ export default function MenuConfigImportExport({
|
|||||||
payload.title = fileNameBase || "";
|
payload.title = fileNameBase || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exportSelection.description) {
|
if (exportSelection.description || description) {
|
||||||
payload.description = "";
|
payload.description = description || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exportSelection.menu) {
|
if (exportSelection.menu) {
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export default function ReadOnlyOrderOverviewCard({
|
|||||||
extra={
|
extra={
|
||||||
<MenuConfigImportExport
|
<MenuConfigImportExport
|
||||||
config={currentConfig}
|
config={currentConfig}
|
||||||
|
description={currentDescription}
|
||||||
showImport={false}
|
showImport={false}
|
||||||
onImportConfig={onImportConfig}
|
onImportConfig={onImportConfig}
|
||||||
fileNameBase={order.title || "order"}
|
fileNameBase={order.title || "order"}
|
||||||
|
|||||||
@@ -51,17 +51,13 @@ export default function AdminSubmissionsCard({
|
|||||||
onOpenMailto,
|
onOpenMailto,
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onSelectedRowKeysChange,
|
onSelectedRowKeysChange,
|
||||||
pagedSubmissions,
|
submissions,
|
||||||
deletingId,
|
deletingId,
|
||||||
savingStatusKey,
|
savingStatusKey,
|
||||||
onDeleteSubmission,
|
onDeleteSubmission,
|
||||||
onUpdateSubmissionStatus,
|
onUpdateSubmissionStatus,
|
||||||
totalEstimatedText,
|
totalEstimatedText,
|
||||||
selectedCount,
|
selectedCount,
|
||||||
submissionsPage,
|
|
||||||
pageSize,
|
|
||||||
totalCount,
|
|
||||||
onSubmissionsPageChange,
|
|
||||||
}: {
|
}: {
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
selectedSubmissionState: "all" | "pending" | "unpaid" | "paid";
|
selectedSubmissionState: "all" | "pending" | "unpaid" | "paid";
|
||||||
@@ -74,17 +70,13 @@ export default function AdminSubmissionsCard({
|
|||||||
onOpenMailto: () => void;
|
onOpenMailto: () => void;
|
||||||
selectedRowKeys: Array<string | number>;
|
selectedRowKeys: Array<string | number>;
|
||||||
onSelectedRowKeysChange: (keys: Array<string | number>) => void;
|
onSelectedRowKeysChange: (keys: Array<string | number>) => void;
|
||||||
pagedSubmissions: any[];
|
submissions: any[];
|
||||||
deletingId: string | number | null;
|
deletingId: string | number | null;
|
||||||
savingStatusKey: string | null;
|
savingStatusKey: string | null;
|
||||||
onDeleteSubmission: (id: string | number) => void;
|
onDeleteSubmission: (id: string | number) => void;
|
||||||
onUpdateSubmissionStatus: (submission: any, changes: { accepted?: boolean; paid?: boolean }) => void;
|
onUpdateSubmissionStatus: (submission: any, changes: { accepted?: boolean; paid?: boolean }) => void;
|
||||||
totalEstimatedText: string | null;
|
totalEstimatedText: string | null;
|
||||||
selectedCount: number;
|
selectedCount: number;
|
||||||
submissionsPage: number;
|
|
||||||
pageSize: number;
|
|
||||||
totalCount: number;
|
|
||||||
onSubmissionsPageChange: (page: number) => void;
|
|
||||||
}) {
|
}) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -224,14 +216,14 @@ export default function AdminSubmissionsCard({
|
|||||||
<Table
|
<Table
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={pagedSubmissions}
|
dataSource={submissions}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: (nextSelectedRowKeys) =>
|
onChange: (nextSelectedRowKeys) =>
|
||||||
onSelectedRowKeysChange(nextSelectedRowKeys as Array<string | number>),
|
onSelectedRowKeysChange(nextSelectedRowKeys as Array<string | number>),
|
||||||
}}
|
}}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
scroll={{ x: true }}
|
scroll={{ y: 400, x: true }}
|
||||||
/>
|
/>
|
||||||
<Flex
|
<Flex
|
||||||
justify="space-between"
|
justify="space-between"
|
||||||
@@ -249,14 +241,6 @@ export default function AdminSubmissionsCard({
|
|||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
)}
|
)}
|
||||||
<Pagination
|
|
||||||
current={submissionsPage}
|
|
||||||
pageSize={pageSize}
|
|
||||||
total={totalCount}
|
|
||||||
showSizeChanger={false}
|
|
||||||
hideOnSinglePage
|
|
||||||
onChange={onSubmissionsPageChange}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -92,33 +92,52 @@ export default function HomeOrdersTable({
|
|||||||
const isClosed = !!item.closed;
|
const isClosed = !!item.closed;
|
||||||
const closedStr = isClosed ? " Closed" : "Open";
|
const closedStr = isClosed ? " Closed" : "Open";
|
||||||
|
|
||||||
const dateStr =
|
const dateStr = item.created_at
|
||||||
item.created_at
|
? new Date(item.created_at).toLocaleString()
|
||||||
? new Date(item.created_at).toLocaleString()
|
: "Unknown creation time";
|
||||||
: "Unknown creation time";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space size={16}>
|
<Space size={16}>
|
||||||
<Icon path={mdiFood} size={1} style={getOrderIconStyle(isClosed)} />
|
<Icon
|
||||||
|
path={mdiFood}
|
||||||
|
size={1}
|
||||||
|
style={getOrderIconStyle(isClosed)}
|
||||||
|
/>
|
||||||
<Space direction="vertical" size={0}>
|
<Space direction="vertical" size={0}>
|
||||||
<Space size={8} align="center">
|
<Space size={8} align="center">
|
||||||
<Text strong>{item.title || item.id}</Text>
|
<Text strong>{item.title || item.id}</Text>
|
||||||
{item.is_owner ? <Tag color="geekblue">Owner</Tag> : null}
|
<Space size={0} align="center">
|
||||||
{state === "paid" ? <Tag color="green">Paid</Tag> : null}
|
{item.is_owner ? <Tag color="geekblue">Owner</Tag> : null}
|
||||||
{state === "unpaid" ? (
|
{item.is_owner && item.has_unpaid_submissions ? (
|
||||||
<Tag color="volcano">Unpaid</Tag>
|
<Tag color="volcano">Open payments</Tag>
|
||||||
) : null}
|
) : null}
|
||||||
{state === "pending" ? <Tag color="blue">Pending</Tag> : null}
|
{state === "paid" ? <Tag color="green">Paid</Tag> : null}
|
||||||
|
{state === "unpaid" ? (
|
||||||
|
<Tag color="volcano">Unpaid</Tag>
|
||||||
|
) : null}
|
||||||
|
{state === "pending" ? (
|
||||||
|
<Tag color="blue">Pending</Tag>
|
||||||
|
) : null}
|
||||||
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
<Text type="secondary">
|
<Text type="secondary">
|
||||||
{<Tooltip title={`Order is ${closedStr.toLocaleLowerCase()}`}>{closedStr}</Tooltip>}{" • "}
|
{
|
||||||
{<Tooltip title={`Created at: ${dateStr}`}>{dateStr}</Tooltip>}
|
<Tooltip
|
||||||
|
title={`Order is ${closedStr.toLocaleLowerCase()}`}
|
||||||
|
>
|
||||||
|
{closedStr}
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{" • "}
|
||||||
|
{
|
||||||
|
<Tooltip title={`Created at: ${dateStr}`}>
|
||||||
|
{dateStr}
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
{item.is_participant && formatted && (
|
{item.is_participant && formatted && (
|
||||||
<>
|
<>
|
||||||
{" • "}
|
{" • "}
|
||||||
<Tooltip title="Your order">
|
<Tooltip title="Your order">{formatted}</Tooltip>
|
||||||
{formatted}
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export type Order = OrderBase & {
|
|||||||
is_owner: boolean;
|
is_owner: boolean;
|
||||||
is_participant: boolean;
|
is_participant: boolean;
|
||||||
};
|
};
|
||||||
|
has_unpaid_submissions?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OrderAdminView = OrderBase & {
|
export type OrderAdminView = OrderBase & {
|
||||||
|
|||||||
@@ -59,10 +59,8 @@ export default function AdminView({ orderId }: { orderId: string }) {
|
|||||||
const [savingStatusKey, setSavingStatusKey] = useState<string | null>(null);
|
const [savingStatusKey, setSavingStatusKey] = useState<string | null>(null);
|
||||||
const [updatingOrderStatus, setUpdatingOrderStatus] = useState(false);
|
const [updatingOrderStatus, setUpdatingOrderStatus] = useState(false);
|
||||||
const [deletingOrder, setDeletingOrder] = useState(false);
|
const [deletingOrder, setDeletingOrder] = useState(false);
|
||||||
const [submissionsPage, setSubmissionsPage] = useState(1);
|
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [selectedSubmissionState, setSelectedSubmissionState] = useState<"all" | "pending" | "unpaid" | "paid">("all");
|
const [selectedSubmissionState, setSelectedSubmissionState] = useState<"all" | "pending" | "unpaid" | "paid">("all");
|
||||||
const SUBMISSIONS_PAGE_SIZE = 8;
|
|
||||||
|
|
||||||
const getSubmissionState = (submission: Submission): SubmissionStatus => {
|
const getSubmissionState = (submission: Submission): SubmissionStatus => {
|
||||||
if (submission?.paid) {
|
if (submission?.paid) {
|
||||||
@@ -263,17 +261,6 @@ export default function AdminView({ orderId }: { orderId: string }) {
|
|||||||
};
|
};
|
||||||
}, [refreshSubmissions]);
|
}, [refreshSubmissions]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxPage = Math.max(1, Math.ceil(data.submissions.length / SUBMISSIONS_PAGE_SIZE));
|
|
||||||
if (submissionsPage > maxPage) {
|
|
||||||
setSubmissionsPage(maxPage);
|
|
||||||
}
|
|
||||||
}, [data, submissionsPage]);
|
|
||||||
|
|
||||||
const filteredSubmissions = useMemo(() => {
|
const filteredSubmissions = useMemo(() => {
|
||||||
if (!data?.submissions) {
|
if (!data?.submissions) {
|
||||||
return [];
|
return [];
|
||||||
@@ -312,10 +299,6 @@ export default function AdminView({ orderId }: { orderId: string }) {
|
|||||||
setSelectedRowKeys((previous) => previous.filter((id) => visibleIds.has(id)));
|
setSelectedRowKeys((previous) => previous.filter((id) => visibleIds.has(id)));
|
||||||
}, [filteredSubmissions]);
|
}, [filteredSubmissions]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSubmissionsPage(1);
|
|
||||||
}, [searchQuery, selectedSubmissionState]);
|
|
||||||
|
|
||||||
const selected = filteredSubmissions.filter((submission: any) =>
|
const selected = filteredSubmissions.filter((submission: any) =>
|
||||||
selectedRowKeys.includes(submission.id),
|
selectedRowKeys.includes(submission.id),
|
||||||
);
|
);
|
||||||
@@ -357,10 +340,6 @@ export default function AdminView({ orderId }: { orderId: string }) {
|
|||||||
const totalEstimatedText = hasAnyEstimatedValue
|
const totalEstimatedText = hasAnyEstimatedValue
|
||||||
? formatEstimatedTotal(totalEstimatedValue)
|
? formatEstimatedTotal(totalEstimatedValue)
|
||||||
: null;
|
: null;
|
||||||
const pagedSubmissions = filteredSubmissions.slice(
|
|
||||||
(submissionsPage - 1) * SUBMISSIONS_PAGE_SIZE,
|
|
||||||
submissionsPage * SUBMISSIONS_PAGE_SIZE,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pageError || loading) {
|
if (pageError || loading) {
|
||||||
return (
|
return (
|
||||||
@@ -417,6 +396,14 @@ export default function AdminView({ orderId }: { orderId: string }) {
|
|||||||
onImportConfig={handleImportConfig}
|
onImportConfig={handleImportConfig}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AdminControlCenterCard
|
||||||
|
isClosed={!!data?.closed}
|
||||||
|
updatingOrderStatus={updatingOrderStatus}
|
||||||
|
deletingOrder={deletingOrder}
|
||||||
|
onToggleClosed={updateOrderClosedStatus}
|
||||||
|
onDeleteOrder={deleteOrder}
|
||||||
|
/>
|
||||||
|
|
||||||
<AdminSubmissionsCard
|
<AdminSubmissionsCard
|
||||||
searchQuery={searchQuery}
|
searchQuery={searchQuery}
|
||||||
selectedSubmissionState={selectedSubmissionState}
|
selectedSubmissionState={selectedSubmissionState}
|
||||||
@@ -429,25 +416,13 @@ export default function AdminView({ orderId }: { orderId: string }) {
|
|||||||
onOpenMailto={openSubmissionEmailDraft}
|
onOpenMailto={openSubmissionEmailDraft}
|
||||||
selectedRowKeys={selectedRowKeys}
|
selectedRowKeys={selectedRowKeys}
|
||||||
onSelectedRowKeysChange={setSelectedRowKeys}
|
onSelectedRowKeysChange={setSelectedRowKeys}
|
||||||
pagedSubmissions={pagedSubmissions}
|
submissions={filteredSubmissions}
|
||||||
deletingId={deletingId}
|
deletingId={deletingId}
|
||||||
savingStatusKey={savingStatusKey}
|
savingStatusKey={savingStatusKey}
|
||||||
onDeleteSubmission={deleteAsAdmin}
|
onDeleteSubmission={deleteAsAdmin}
|
||||||
onUpdateSubmissionStatus={updateSubmissionStatus}
|
onUpdateSubmissionStatus={updateSubmissionStatus}
|
||||||
totalEstimatedText={totalEstimatedText}
|
totalEstimatedText={totalEstimatedText}
|
||||||
selectedCount={selected.length}
|
selectedCount={selected.length}
|
||||||
submissionsPage={submissionsPage}
|
|
||||||
pageSize={SUBMISSIONS_PAGE_SIZE}
|
|
||||||
totalCount={filteredSubmissions.length}
|
|
||||||
onSubmissionsPageChange={setSubmissionsPage}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AdminControlCenterCard
|
|
||||||
isClosed={!!data?.closed}
|
|
||||||
updatingOrderStatus={updatingOrderStatus}
|
|
||||||
deletingOrder={deletingOrder}
|
|
||||||
onToggleClosed={updateOrderClosedStatus}
|
|
||||||
onDeleteOrder={deleteOrder}
|
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user