feat: nový stav "Doručeno" pro objednávky
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 26s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 1m43s
CI / Build and push Docker image (push) Successful in 48s
CI / Notify (push) Successful in 1s
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 26s
CI / Build server (push) Successful in 28s
CI / Build client (push) Successful in 42s
CI / Playwright E2E tests (push) Successful in 1m43s
CI / Build and push Docker image (push) Successful in 48s
CI / Notify (push) Successful in 1s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Modal, Button, Form, Table, Alert } from "react-bootstrap";
|
||||
import { generateQr, OrderGroup, OrderGroupMember, QrRecipient } from "../../../../types";
|
||||
import { generateQr, GroupState, OrderGroup, OrderGroupMember, QrRecipient } from "../../../../types";
|
||||
import { sanitizeQrMessage } from "../../Utils";
|
||||
import { computeFeeShare, computeMemberTotal, countActiveMembers, isActiveMember } from "../../utils/groupFees";
|
||||
|
||||
@@ -102,6 +102,8 @@ export default function PayForGroupModal({ isOpen, onClose, onSuccess, group, pa
|
||||
};
|
||||
|
||||
const hasFees = totalFees > 0;
|
||||
// Generování QR je poslední krok objednávky — server skupinu zároveň překlopí do stavu Doručeno
|
||||
const willBeMarkedDelivered = group.state !== GroupState.DELIVERED;
|
||||
|
||||
return (
|
||||
<Modal show={isOpen} onHide={onClose} size="lg">
|
||||
@@ -115,6 +117,12 @@ export default function PayForGroupModal({ isOpen, onClose, onSuccess, group, pa
|
||||
</Alert>
|
||||
) : (
|
||||
<>
|
||||
{willBeMarkedDelivered && (
|
||||
<Alert variant="info">
|
||||
Objednávka není ve stavu <strong>Doručeno</strong> — vygenerováním QR kódů se do tohoto stavu automaticky přepne.
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<p>Zaplatili jste za skupinu. Vyberte, komu vygenerovat QR kód k úhradě.</p>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Alert, Badge, Button, Card, Form, Modal, OverlayTrigger, Table, Tooltip } from 'react-bootstrap';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faTrashCan } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faBasketShopping, faChevronLeft, faChevronRight, faCircleCheck, faClockRotateLeft, faGear, faLock, faLockOpen, faPen, faSearch, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBasketShopping, faChevronLeft, faChevronRight, faCircleCheck, faClockRotateLeft, faGear, faLock, faLockOpen, faPen, faSearch, faTruck, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import DatePicker, { registerLocale } from 'react-datepicker';
|
||||
import { cs } from 'date-fns/locale';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
@@ -102,11 +102,17 @@ function isoToDate(iso?: string): Date | null {
|
||||
return iso ? new Date(`${iso}T00:00:00`) : null;
|
||||
}
|
||||
|
||||
/** Stavy, ve kterých je objednávka již odeslána do podniku (objednáno/doručeno) — skupina se nesmí upravovat. */
|
||||
function isSubmittedState(state: GroupState): boolean {
|
||||
return state === GroupState.ORDERED || state === GroupState.DELIVERED;
|
||||
}
|
||||
|
||||
function stateBadge(state: GroupState) {
|
||||
const map: Record<GroupState, { bg: string; label: string }> = {
|
||||
[GroupState.OPEN]: { bg: 'success', label: 'Otevřeno' },
|
||||
[GroupState.LOCKED]: { bg: 'warning', label: 'Uzamčeno' },
|
||||
[GroupState.ORDERED]: { bg: 'secondary', label: 'Objednáno' },
|
||||
[GroupState.DELIVERED]: { bg: 'info', label: 'Doručeno' },
|
||||
};
|
||||
const { bg, label } = map[state] ?? { bg: 'light', label: state };
|
||||
return <Badge bg={bg}>{label}</Badge>;
|
||||
@@ -281,6 +287,12 @@ export default function OrderGroupsPage() {
|
||||
const handleRevertOrdered = (group: OrderGroup) =>
|
||||
refresh(() => setGroupState({ body: { id: group.id, state: GroupState.LOCKED } }));
|
||||
|
||||
const handleSetDelivered = (group: OrderGroup) =>
|
||||
refresh(() => setGroupState({ body: { id: group.id, state: GroupState.DELIVERED } }));
|
||||
|
||||
const handleRevertDelivered = (group: OrderGroup) =>
|
||||
refresh(() => setGroupState({ body: { id: group.id, state: GroupState.ORDERED } }));
|
||||
|
||||
const handleDelete = (groupId: string) =>
|
||||
refresh(() => deleteGroup({ body: { id: groupId } }));
|
||||
|
||||
@@ -346,7 +358,7 @@ export default function OrderGroupsPage() {
|
||||
// Historie (jiný než aktuální den) je vždy read-only.
|
||||
const canEditMember = (group: OrderGroup, targetLogin: string) => {
|
||||
if (selectedDate) return false;
|
||||
if (group.state === GroupState.ORDERED) return false;
|
||||
if (isSubmittedState(group.state)) return false;
|
||||
if (auth?.login === group.creatorLogin) return true;
|
||||
if (auth?.login === targetLogin && group.state === GroupState.OPEN) return true;
|
||||
return false;
|
||||
@@ -354,7 +366,7 @@ export default function OrderGroupsPage() {
|
||||
|
||||
const canManageMembers = (group: OrderGroup) => {
|
||||
if (selectedDate) return false;
|
||||
if (group.state === GroupState.ORDERED) return false;
|
||||
if (isSubmittedState(group.state)) return false;
|
||||
if (auth?.login === group.creatorLogin) return true;
|
||||
return group.state === GroupState.OPEN;
|
||||
};
|
||||
@@ -520,6 +532,9 @@ export default function OrderGroupsPage() {
|
||||
const isCreator = login === group.creatorLogin;
|
||||
const isMember = login in group.members;
|
||||
const isOrdered = group.state === GroupState.ORDERED;
|
||||
const isDelivered = group.state === GroupState.DELIVERED;
|
||||
// Objednáno i Doručeno = objednávka je odeslána do podniku, skupina se už needituje
|
||||
const isSubmitted = isOrdered || isDelivered;
|
||||
const isLocked = group.state === GroupState.LOCKED;
|
||||
const memberEntries = Object.entries(group.members) as [string, OrderGroupMember][];
|
||||
const editingTimes = group.id in editTimes;
|
||||
@@ -556,7 +571,7 @@ export default function OrderGroupsPage() {
|
||||
<small className="text-muted">zakladatel: {group.creatorLogin}</small>
|
||||
</div>
|
||||
<div className="d-flex gap-2">
|
||||
{!isReadOnly && isCreator && !isOrdered && (
|
||||
{!isReadOnly && isCreator && !isSubmitted && (
|
||||
<>
|
||||
<Button variant="outline-info" size="sm" onClick={() => setFeesModal(group)} title="Upravit poplatky a slevu">
|
||||
Poplatky
|
||||
@@ -574,20 +589,32 @@ export default function OrderGroupsPage() {
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!isReadOnly && isCreator && isOrdered && (
|
||||
{!isReadOnly && isCreator && isSubmitted && (
|
||||
<>
|
||||
{isOrdered && (
|
||||
<Button variant="outline-success" size="sm" onClick={() => handleSetDelivered(group)} title="Označit objednávku jako doručenou">
|
||||
<FontAwesomeIcon icon={faTruck} className="me-1" />
|
||||
Doručeno
|
||||
</Button>
|
||||
)}
|
||||
{settings?.bankAccount && settings?.holderName && !group.qrGenerated && (
|
||||
<Button variant="primary" size="sm" onClick={() => setPayModal(group)}>
|
||||
<FontAwesomeIcon icon={faBasketShopping} className="me-1" />
|
||||
Generovat QR
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline-warning" size="sm" onClick={() => handleRevertOrdered(group)} title="Vrátit na Uzamčeno (smaže QR kódy)">
|
||||
<FontAwesomeIcon icon={faLockOpen} />
|
||||
</Button>
|
||||
{isDelivered ? (
|
||||
<Button variant="outline-warning" size="sm" onClick={() => handleRevertDelivered(group)} title="Vrátit na Objednáno">
|
||||
<FontAwesomeIcon icon={faClockRotateLeft} />
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant="outline-warning" size="sm" onClick={() => handleRevertOrdered(group)} title="Vrátit na Uzamčeno (smaže QR kódy)">
|
||||
<FontAwesomeIcon icon={faLockOpen} />
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!isReadOnly && !isMember && !isOrdered && !isLocked && (
|
||||
{!isReadOnly && !isMember && !isSubmitted && !isLocked && (
|
||||
<Button variant="outline-success" size="sm" onClick={() => handleJoin(group.id)}>
|
||||
<FontAwesomeIcon icon={faUserPlus} className="me-1" />
|
||||
Přidat se
|
||||
@@ -773,7 +800,7 @@ export default function OrderGroupsPage() {
|
||||
)}
|
||||
|
||||
{/* Časy objednání a doručení */}
|
||||
{isOrdered && (
|
||||
{isSubmitted && (
|
||||
<div className="px-3 py-2 border-top">
|
||||
{!isReadOnly && isCreator && editingTimes ? (
|
||||
<div className="d-flex align-items-center gap-3 flex-wrap">
|
||||
|
||||
Reference in New Issue
Block a user