feat: redesign aplikace pomocí claude code
This commit is contained in:
@@ -344,10 +344,10 @@ function App() {
|
||||
const renderFoodTable = (location: Restaurant, menu: RestaurantDayMenu) => {
|
||||
let content;
|
||||
if (menu?.closed) {
|
||||
content = <h3>Zavřeno</h3>
|
||||
content = <div className="restaurant-closed">Zavřeno</div>
|
||||
} else if (menu?.food?.length && menu.food.length > 0) {
|
||||
const hideSoups = settings?.hideSoups;
|
||||
content = <Table striped bordered hover>
|
||||
content = <Table className="food-table">
|
||||
<tbody style={{ cursor: canChangeChoice ? 'pointer' : 'default' }}>
|
||||
{menu.food.map((f: Food, index: number) =>
|
||||
(!hideSoups || !f.isSoup) &&
|
||||
@@ -356,15 +356,17 @@ function App() {
|
||||
<td>
|
||||
{f.name}
|
||||
{f.allergens && f.allergens.length > 0 && (
|
||||
<> ({f.allergens.map((a, idx) => (
|
||||
<span key={a}>
|
||||
<span title={ALLERGENS[a]} style={{ cursor: 'help', textDecoration: 'underline' }} onClick={e => {
|
||||
e.stopPropagation();
|
||||
window.open(LINK_ALLERGENS, '_blank');
|
||||
}}>{a}</span>
|
||||
{idx < f.allergens!.length - 1 && ','}
|
||||
</span>
|
||||
))})</>
|
||||
<span className="ms-1">
|
||||
({f.allergens.map((a, idx) => (
|
||||
<span key={a}>
|
||||
<span className="allergen-link" title={ALLERGENS[a]} onClick={e => {
|
||||
e.stopPropagation();
|
||||
window.open(LINK_ALLERGENS, '_blank');
|
||||
}}>{a}</span>
|
||||
{idx < f.allergens!.length - 1 && ', '}
|
||||
</span>
|
||||
))})
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
<td>{f.price}</td>
|
||||
@@ -373,12 +375,18 @@ function App() {
|
||||
</tbody>
|
||||
</Table>
|
||||
} else {
|
||||
content = <h3>Chyba načtení dat</h3>
|
||||
content = <div className="restaurant-error">Chyba načtení dat</div>
|
||||
}
|
||||
return <Col md={12} lg={3} className='mt-3'>
|
||||
<h3 style={{ cursor: canChangeChoice ? 'pointer' : 'default' }} onClick={() => doAddClickFoodChoice(location)}>{getLunchChoiceName(location)}</h3>
|
||||
{menu?.lastUpdate && <small>Poslední aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}</small>}
|
||||
{content}
|
||||
return <Col md={6} lg={3} className='mt-3'>
|
||||
<div className="restaurant-card">
|
||||
<div className="restaurant-header">
|
||||
<h3 style={{ cursor: canChangeChoice ? 'pointer' : 'default' }} onClick={() => doAddClickFoodChoice(location)}>
|
||||
{getLunchChoiceName(location)}
|
||||
</h3>
|
||||
{menu?.lastUpdate && <small>Aktualizace: {getHumanDateTime(new Date(menu.lastUpdate))}</small>}
|
||||
</div>
|
||||
{content}
|
||||
</div>
|
||||
</Col>
|
||||
}
|
||||
|
||||
@@ -421,16 +429,6 @@ function App() {
|
||||
<Header />
|
||||
<div className='wrapper'>
|
||||
{data.isWeekend ? <h4>Užívejte víkend :)</h4> : <>
|
||||
<Alert variant={'primary'}>
|
||||
<img alt="" src='hat.png' style={{ position: "absolute", width: "70px", rotate: "-45deg", left: -40, top: -58 }} />
|
||||
<img alt="" src='snowman.png' style={{ position: "absolute", height: "110px", right: 10, top: 5 }} />
|
||||
Poslední změny:
|
||||
<ul>
|
||||
<li>Oprava parsování Sladovnické a TechTower</li>
|
||||
<li>Zimní atmosféra</li>
|
||||
<li>Možnost označit se jako objednávající u volby "budu objednávat"</li>
|
||||
</ul>
|
||||
</Alert>
|
||||
{dayIndex != null &&
|
||||
<div className='day-navigator'>
|
||||
<span title='Předchozí den'>
|
||||
@@ -450,10 +448,10 @@ function App() {
|
||||
</Row>
|
||||
<div className='content-wrapper'>
|
||||
<div className='content'>
|
||||
{canChangeChoice && <>
|
||||
{canChangeChoice && <div className="choice-section fade-in">
|
||||
<p>{`Jak to ${dayIndex == null || dayIndex === data.todayDayIndex ? 'dnes' : 'tento den'} vidíš s obědem?`}</p>
|
||||
<Form.Select ref={choiceRef} onChange={doAddChoice}>
|
||||
<option></option>
|
||||
<option value="">Vyber možnost...</option>
|
||||
{Object.entries(LunchChoice)
|
||||
.filter(entry => {
|
||||
const locationKey = entry[0] as Restaurant;
|
||||
@@ -463,44 +461,44 @@ function App() {
|
||||
</Form.Select>
|
||||
<small>Je možné vybrat jen jednu možnost. Výběr jiné odstraní předchozí.</small>
|
||||
{foodChoiceList && !closed && <>
|
||||
<p style={{ marginTop: "10px" }}>Na co dobrého? <small>(nepovinné)</small></p>
|
||||
<p className="mt-3">Na co dobrého? <small style={{ color: 'var(--luncher-text-muted)' }}>(nepovinné)</small></p>
|
||||
<Form.Select ref={foodChoiceRef} onChange={doAddFoodChoice}>
|
||||
<option></option>
|
||||
<option value="">Vyber jídlo...</option>
|
||||
{foodChoiceList.map((food, index) => <option key={food.name} value={index}>{food.name}</option>)}
|
||||
</Form.Select>
|
||||
</>}
|
||||
{foodChoiceList && !closed && <>
|
||||
<p style={{ marginTop: "10px" }}>V kolik hodin preferuješ odchod?</p>
|
||||
<p className="mt-3">V kolik hodin preferuješ odchod?</p>
|
||||
<Form.Select ref={departureChoiceRef} onChange={handleChangeDepartureTime}>
|
||||
<option></option>
|
||||
<option value="">Vyber čas...</option>
|
||||
{Object.values(DepartureTime)
|
||||
.filter(time => isInTheFuture(time))
|
||||
.map(time => <option key={time} value={time}>{time}</option>)}
|
||||
</Form.Select>
|
||||
</>}
|
||||
</>}
|
||||
</div>}
|
||||
{Object.keys(data.choices).length > 0 ?
|
||||
<Table bordered className='mt-5'>
|
||||
<Table className='choices-table mt-4 fade-in'>
|
||||
<tbody>
|
||||
{Object.keys(data.choices).map(key => {
|
||||
const locationKey = key as LunchChoice;
|
||||
const locationName = getLunchChoiceName(locationKey);
|
||||
const loginObject = data.choices[locationKey];
|
||||
if (!loginObject) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
const locationLoginList = Object.entries(loginObject);
|
||||
const locationPickCount = locationLoginList.length
|
||||
return (
|
||||
<tr key={key}>
|
||||
{(locationPickCount ?? 0) > 1 ? (
|
||||
<td>{locationName} ({locationPickCount})</td>
|
||||
) : (
|
||||
<td>{locationName}</td>)}
|
||||
<td>
|
||||
{locationName}
|
||||
{(locationPickCount ?? 0) > 1 && <span className="ms-1">({locationPickCount})</span>}
|
||||
</td>
|
||||
<td className='p-0'>
|
||||
<Table>
|
||||
<Table className="nested-table">
|
||||
<tbody>
|
||||
{locationLoginList.map((entry: [string, UserLunchChoice], index) => {
|
||||
{locationLoginList.map((entry: [string, UserLunchChoice]) => {
|
||||
const login = entry[0];
|
||||
const userPayload = entry[1];
|
||||
const userChoices = userPayload?.selectedFoods;
|
||||
@@ -508,13 +506,12 @@ function App() {
|
||||
const isBuyer = userPayload?.isBuyer || false;
|
||||
return <tr key={entry[0]}>
|
||||
<td>
|
||||
{/* TODO zrefaktorovat, oddělit řádek do samostatné komponenty (a akce možná zvlášť) */}
|
||||
{trusted && <span className='trusted-icon' title='Uživatel ověřený doménovým přihlášením'>
|
||||
<FontAwesomeIcon icon={faCircleCheck} style={{ cursor: "help" }} />
|
||||
</span>}
|
||||
{login}
|
||||
{userPayload.departureTime && <small> ({userPayload.departureTime})</small>}
|
||||
{userPayload.note && <span style={{ fontSize: 'small' }}> ({userPayload.note})</span>}
|
||||
<strong>{login}</strong>
|
||||
{userPayload.departureTime && <small className="ms-2" style={{ color: 'var(--luncher-text-muted)' }}>({userPayload.departureTime})</small>}
|
||||
{userPayload.note && <span className="ms-2" style={{ fontSize: 'small', color: 'var(--luncher-text-secondary)' }}>({userPayload.note})</span>}
|
||||
{login === auth.login && canChangeChoice && locationKey === LunchChoice.OBJEDNAVAM && <span title='Označit/odznačit se jako objednávající'>
|
||||
<FontAwesomeIcon onClick={() => {
|
||||
markAsBuyer();
|
||||
@@ -569,97 +566,94 @@ function App() {
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
: <div className='mt-5'><i>Zatím nikdo nehlasoval...</i></div>
|
||||
: <div className='no-votes mt-4'>Zatím nikdo nehlasoval...</div>
|
||||
}
|
||||
</div>
|
||||
{dayIndex === data.todayDayIndex &&
|
||||
<div className='mt-5'>
|
||||
<div className='pizza-section fade-in'>
|
||||
{!data.pizzaDay &&
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<>
|
||||
<h3>Pizza Day</h3>
|
||||
<p>Pro dnešní den není aktuálně založen Pizza day.</p>
|
||||
{loadingPizzaDay ?
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faGear} className='fa-spin' /> Zjišťujeme dostupné pizzy
|
||||
<span style={{ color: 'var(--luncher-primary)' }}>
|
||||
<FontAwesomeIcon icon={faGear} className='fa-spin me-2' /> Zjišťujeme dostupné pizzy
|
||||
</span>
|
||||
:
|
||||
<>
|
||||
<div>
|
||||
<Button onClick={async () => {
|
||||
setLoadingPizzaDay(true);
|
||||
await createPizzaDay().then(() => setLoadingPizzaDay(false));
|
||||
}}>Založit Pizza day</Button>
|
||||
<Button onClick={doJdemeObed} style={{ marginLeft: "14px" }}>Jdeme na oběd !</Button>
|
||||
</>
|
||||
<Button variant="outline-primary" onClick={doJdemeObed}>Jdeme na oběd!</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{data.pizzaDay &&
|
||||
<div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h3>Pizza day</h3>
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.CREATED &&
|
||||
<div>
|
||||
<p>
|
||||
Pizza Day je založen a spravován uživatelem {data.pizzaDay.creator}.<br />
|
||||
Můžete upravovat své objednávky.
|
||||
</p>
|
||||
{
|
||||
data.pizzaDay.creator === auth.login &&
|
||||
<>
|
||||
<Button className='danger mb-3' title="Smaže kompletně pizza day, včetně dosud zadaných objednávek." onClick={async () => {
|
||||
await deletePizzaDay();
|
||||
}}>Smazat Pizza day</Button>
|
||||
<Button className='mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze uzamknout - neexistuje žádná objednávka" : "Zamezí přidávat/odebírat objednávky. Použij před samotným objednáním, aby již nemohlo docházet ke změnám."} disabled={noOrders} onClick={async () => {
|
||||
await lockPizzaDay();
|
||||
}}>Uzamknout</Button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.LOCKED &&
|
||||
<div>
|
||||
<p>Objednávky jsou uzamčeny uživatelem {data.pizzaDay.creator}</p>
|
||||
{data.pizzaDay.creator === auth.login &&
|
||||
<>
|
||||
<Button className='danger mb-3' title="Umožní znovu editovat objednávky." onClick={async () => {
|
||||
await unlockPizzaDay();
|
||||
}}>Odemknout</Button>
|
||||
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => {
|
||||
await finishOrder();
|
||||
}}>Objednáno</Button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.ORDERED &&
|
||||
<div>
|
||||
<p>Pizzy byly objednány uživatelem {data.pizzaDay.creator}</p>
|
||||
{data.pizzaDay.creator === auth.login &&
|
||||
<div>
|
||||
<Button className='danger mb-3' title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => {
|
||||
await lockPizzaDay();
|
||||
}}>Vrátit do "uzamčeno"</Button>
|
||||
<Button className='danger mb-3' style={{ marginLeft: '20px' }} title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
|
||||
await finishDelivery({ body: { bankAccount: settings?.bankAccount, bankAccountHolder: settings?.holderName } });
|
||||
}}>Doručeno</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.DELIVERED &&
|
||||
<div>
|
||||
<p>
|
||||
Pizzy byly doručeny.
|
||||
{myOrder?.hasQr ? ` Objednávku můžete uživateli ${data.pizzaDay.creator} uhradit pomocí QR kódu níže.` : ''}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<>
|
||||
<h3>Pizza Day</h3>
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.CREATED &&
|
||||
<>
|
||||
<p>
|
||||
Pizza Day je založen a spravován uživatelem <strong>{data.pizzaDay.creator}</strong>.<br />
|
||||
Můžete upravovat své objednávky.
|
||||
</p>
|
||||
{
|
||||
data.pizzaDay.creator === auth.login &&
|
||||
<div className="mb-4">
|
||||
<Button variant="danger" title="Smaže kompletně pizza day, včetně dosud zadaných objednávek." onClick={async () => {
|
||||
await deletePizzaDay();
|
||||
}}>Smazat Pizza day</Button>
|
||||
<Button title={noOrders ? "Nelze uzamknout - neexistuje žádná objednávka" : "Zamezí přidávat/odebírat objednávky. Použij před samotným objednáním, aby již nemohlo docházet ke změnám."} disabled={noOrders} onClick={async () => {
|
||||
await lockPizzaDay();
|
||||
}}>Uzamknout</Button>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.LOCKED &&
|
||||
<>
|
||||
<p>Objednávky jsou uzamčeny uživatelem <strong>{data.pizzaDay.creator}</strong></p>
|
||||
{data.pizzaDay.creator === auth.login &&
|
||||
<div className="mb-4">
|
||||
<Button variant="secondary" title="Umožní znovu editovat objednávky." onClick={async () => {
|
||||
await unlockPizzaDay();
|
||||
}}>Odemknout</Button>
|
||||
<Button title={noOrders ? "Nelze objednat - neexistuje žádná objednávka" : "Použij po objednání. Objednávky zůstanou zamčeny."} disabled={noOrders} onClick={async () => {
|
||||
await finishOrder();
|
||||
}}>Objednáno</Button>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.ORDERED &&
|
||||
<>
|
||||
<p>Pizzy byly objednány uživatelem <strong>{data.pizzaDay.creator}</strong></p>
|
||||
{data.pizzaDay.creator === auth.login &&
|
||||
<div className="mb-4">
|
||||
<Button variant="secondary" title="Vrátí stav do předchozího kroku (před objednáním)." onClick={async () => {
|
||||
await lockPizzaDay();
|
||||
}}>Vrátit do "uzamčeno"</Button>
|
||||
<Button title="Nastaví stav na 'Doručeno' - koncový stav." onClick={async () => {
|
||||
await finishDelivery({ body: { bankAccount: settings?.bankAccount, bankAccountHolder: settings?.holderName } });
|
||||
}}>Doručeno</Button>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.DELIVERED &&
|
||||
<p>
|
||||
Pizzy byly doručeny.
|
||||
{myOrder?.hasQr ? ` Objednávku můžete uživateli ${data.pizzaDay.creator} uhradit pomocí QR kódu níže.` : ''}
|
||||
</p>
|
||||
}
|
||||
{data.pizzaDay.state === PizzaDayState.CREATED &&
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div className="pizza-order-form">
|
||||
<SelectSearch
|
||||
search={true}
|
||||
options={pizzaSuggestions}
|
||||
@@ -668,29 +662,31 @@ function App() {
|
||||
onBlur={_ => { }}
|
||||
onFocus={_ => { }}
|
||||
/>
|
||||
Poznámka: <input ref={pizzaPoznamkaRef} className='mt-3' type="text" onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
handlePizzaPoznamkaChange();
|
||||
}
|
||||
event.stopPropagation();
|
||||
}} />
|
||||
<Button
|
||||
style={{ marginLeft: '20px' }}
|
||||
disabled={!myOrder?.pizzaList?.length}
|
||||
onClick={handlePizzaPoznamkaChange}>
|
||||
Uložit
|
||||
</Button>
|
||||
<div className="d-flex align-items-center gap-2">
|
||||
<label style={{ color: 'var(--luncher-text-secondary)' }}>Poznámka:</label>
|
||||
<input ref={pizzaPoznamkaRef} type="text" placeholder="Např. bez cibule" onKeyDown={event => {
|
||||
if (event.key === 'Enter') {
|
||||
handlePizzaPoznamkaChange();
|
||||
}
|
||||
event.stopPropagation();
|
||||
}} />
|
||||
<Button
|
||||
disabled={!myOrder?.pizzaList?.length}
|
||||
onClick={handlePizzaPoznamkaChange}>
|
||||
Uložit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<PizzaOrderList state={data.pizzaDay.state!} orders={data.pizzaDay.orders!} onDelete={handlePizzaDelete} creator={data.pizzaDay.creator!} />
|
||||
{
|
||||
data.pizzaDay.state === PizzaDayState.DELIVERED && myOrder?.hasQr ?
|
||||
<div className='qr-code'>
|
||||
<h3>QR platba</h3>
|
||||
<img src={`/api/qr?login=${auth.login}`} alt='QR kód' />
|
||||
</div> : null
|
||||
data.pizzaDay.state === PizzaDayState.DELIVERED && myOrder?.hasQr &&
|
||||
<div className='qr-code'>
|
||||
<h3>QR platba</h3>
|
||||
<img src={`/api/qr?login=${auth.login}`} alt='QR kód' />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user