color changes, client, realization time

This commit is contained in:
2025-01-04 14:57:53 +01:00
parent 948dbbca61
commit c3f306575f
15 changed files with 247 additions and 144 deletions

View File

@ -0,0 +1,36 @@
# Generated by Django 5.1.4 on 2024-12-21 13:34
import app.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0002_meal'),
]
operations = [
migrations.AlterModelOptions(
name='meal',
options={'verbose_name': 'Danie', 'verbose_name_plural': 'Dania'},
),
migrations.AlterModelOptions(
name='order',
options={'verbose_name': 'Zamówienie', 'verbose_name_plural': 'Zamówienia'},
),
migrations.AlterModelOptions(
name='waiter',
options={'verbose_name': 'Kelner', 'verbose_name_plural': 'Kelnerzy'},
),
migrations.AddField(
model_name='order',
name='client',
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Klient'),
),
migrations.AddField(
model_name='order',
name='realization_time',
field=models.DateTimeField(default=app.models.default_realization_time, verbose_name='Godzina realizacji'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 5.1.4 on 2024-12-21 14:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0003_alter_meal_options_alter_order_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='order',
name='realization_time',
field=models.CharField(blank=True, max_length=250, null=True, verbose_name='Godzina realizacji'),
),
migrations.AlterField(
model_name='order',
name='status',
field=models.PositiveSmallIntegerField(choices=[(1, 'Zamówienie złożone'), (2, 'Zamówienie w trakcie przygotowywania'), (3, 'Zamówienie gotowe'), (4, 'Zamówienie zrealizowane')], default=1, verbose_name='Status zamówienia'),
),
]

View File

@ -1,9 +1,14 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
# Create your models here.
def default_realization_time():
return timezone.now() # + datetime.timedelta(hours=1)
class Waiter(models.Model):
name = models.CharField(_('Imię i nazwisko'), max_length=120)
@ -11,8 +16,8 @@ class Waiter(models.Model):
return self.name
class Meta:
verbose_name = _("Kelner")
verbose_name_plural = _("Kelnerzy")
verbose_name = _('Kelner')
verbose_name_plural = _('Kelnerzy')
class Meal(models.Model):
@ -22,23 +27,31 @@ class Meal(models.Model):
return self.name
class Meta:
verbose_name = _("Danie")
verbose_name_plural = _("Dania")
verbose_name = _('Danie')
verbose_name_plural = _('Dania')
class Order(models.Model):
created_on = models.DateTimeField(_('Utworzono'), auto_now_add=True)
updated_on = models.DateTimeField(_('Zaktualizowano'), auto_now=True)
waiter = models.ForeignKey(
Waiter, models.SET_NULL, related_name='orders', verbose_name=_('Kelner'), null=True
realization_time = models.CharField(
_('Godzina realizacji'), max_length=250, null=True, blank=True
)
waiter = models.ForeignKey(
Waiter,
models.SET_NULL,
related_name='orders',
verbose_name=_('Kelner'),
null=True,
)
client = models.CharField(_('Klient'), max_length=250, null=True, blank=True)
data = models.JSONField(_('Dane zamówienia'), default=dict)
class StatusChoices(models.IntegerChoices):
ORDERED = (1, _('Zamówienie złożone'))
IN_PROGRESS = (2, ('Zamówienie w trakcie przygotowywania'))
READY = (3, _('Zamówienie gotowe'))
FINALIZED = (4, _('Zamówienie skończone'))
FINALIZED = (4, _('Zamówienie zrealizowane'))
status = models.PositiveSmallIntegerField(
_('Status zamówienia'),
@ -47,8 +60,8 @@ class Order(models.Model):
)
def __str__(self):
return self.id
return str(self.id)
class Meta:
verbose_name = _("Zamówienie")
verbose_name_plural = _("Zamówienia")
verbose_name = _('Zamówienie')
verbose_name_plural = _('Zamówienia')

View File

@ -67,6 +67,8 @@ class OrderSerializer(serializers.ModelSerializer):
'id',
'created_on',
'updated_on',
'realization_time',
'client',
'waiter',
'waiter_name',
'data',

View File

@ -4,7 +4,7 @@ FROM node:alpine AS base
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
WORKDIR /src
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
@ -18,14 +18,15 @@ RUN \
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
WORKDIR /src
COPY --from=deps /src/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PRIVATE_STANDALONE=true
RUN yarn build
@ -34,16 +35,16 @@ RUN yarn build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
WORKDIR /src
ENV NODE_ENV production
ENV NODE_ENV=production
# Uncomment the following line in case you want to disable telemetry during runtime.
ENV NEXT_TELEMETRY_DISABLED 1
ENV NEXT_TELEMETRY_DISABLED=1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /src/public ./public
# Set the correct permission for prerender cache
RUN mkdir .next
@ -51,15 +52,13 @@ RUN chown nextjs:nodejs .next
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /src/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /src/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV PORT=3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
CMD HOSTNAME="0.0.0.0" node server.js

View File

@ -2,7 +2,8 @@
import useSWR from 'swr'
export const fetcher = (...args) => fetch(...args).then(res => res.json()) // @ts-ignore
// @ts-expect-error ignore ...args type
export const fetcher = (...args) => fetch(...args).then(res => res.json())
export function useUser (id: number) {

View File

@ -0,0 +1,15 @@
import { Order } from './tools';
export function OrderID({ order }: { order: Order }) {
return (
<h1 className="text-xl">
<span className="font-mono">
{'['}
{String(order.id % 1000).padStart(3, '0')}
{']'}
</span>{' '}
- Godzina realizacji -{' '}
<span className="font-mono">{order.realization_time}</span>
</h1>
);
}

View File

@ -3,7 +3,7 @@
@tailwind utilities;
form#form p {
form#form p, form#form div {
@apply flex flex-col;
}

View File

@ -2,19 +2,9 @@
import { fetcher } from '@/app/api/tools';
import useSWR from 'swr';
import Link from 'next/link';
import { useEffect, useRef, useState } from 'react';
type Order = {
id: number;
created_on: string;
updated_on: string;
waiter: number;
waiter_name: string;
data: [{ item: string; additional_info: string; finished: boolean }];
status: number;
status_name: string;
};
import { getDishBg, getOrderBg, Order } from '../tools';
import { OrderID } from '../components';
export default function Home() {
const audioRef = useRef({} as HTMLAudioElement);
@ -25,11 +15,7 @@ export default function Home() {
});
const play = () => {
if (audioRef.current) {
audioRef.current.play();
} else {
// Throw error
}
if (audioRef.current) audioRef.current.play();
};
useEffect(() => {
@ -48,12 +34,11 @@ export default function Home() {
play();
setPlayedIDs(playedIDs.concat(justPlayed));
}
}, [data]);
}, [playedIDs, data]);
const update_finished = (o: number, i: number, v: boolean) => {
let d = data;
const d = data;
d[o].data[i].finished = v;
console.log(v);
fetch(`/api/orders?id=${d[o].id}`, {
method: 'PUT',
body: JSON.stringify(d[o]),
@ -64,9 +49,8 @@ export default function Home() {
};
const update_status = (o: number, v: number) => {
let d = data;
const d = data;
d[o].status = v;
console.log(v);
fetch(`/api/orders?id=${d[o].id}`, {
method: 'PUT',
body: JSON.stringify(d[o]),
@ -87,36 +71,23 @@ export default function Home() {
{data?.map((order: Order, oi: number) => (
<li
key={order.id}
className={`m-2 p-2 rounded-md border-2 ${
{
1: 'bg-red-200 border-red-400',
2: 'bg-purple-200 border-purple-400',
3: 'bg-green-200 border-green-400',
4: 'bg-gray-100 border-gray-400',
}[order.status]
}`}
className={`m-2 p-2 rounded-md border-2 ${getOrderBg(order)}`}
>
<h1 className="text-xl">
<span className="font-mono">
{'['}
{String(order.id % 1000).padStart(3, '0')}
{']'}
</span>{' '}
- {new Date(order.updated_on).toLocaleTimeString('pl-PL')}
</h1>
<OrderID order={order} />
<h2 className="text-xl">{order.waiter_name}</h2>
{ (order.client) ? <h2 className="text-xl">Klient: {order.client}</h2> : <></>}
<h3>
<select
value={order.status}
onChange={(e) =>
update_status(oi, e.target.value as unknown as number)
}
className="form border-2 border-gray-500"
className="form border-2 my-2 border-gray-500"
>
<option value="1">Zamówienie złożone</option>
<option value="2">Zamówienie w trakcie przygotowywania</option>
<option value="3">Zamówienie gotowe</option>
<option value="4">Zamówienie skończone</option>
<option value="4">Zamówienie zrealizowane</option>
</select>
</h3>
<div className="">
@ -124,7 +95,7 @@ export default function Home() {
{order.data.map((dish, i) => (
<li
key={dish.item}
className={`p-2 m-2 flex gap-4 border-2 bg-white border-gray-400`}
className={`p-2 m-2 flex gap-4 border-2 ${getDishBg(dish)}`}
>
<input
type="checkbox"
@ -132,7 +103,7 @@ export default function Home() {
onChange={(e) => update_finished(oi, i, e.target.checked)}
/>
<div>
<p className="text-lg">{dish.item}</p>
<p className="text-lg">{dish.item} - {dish.takeout ? 'Na wynos' : 'Na miejscu'}</p>
<div>{dish.additional_info}</div>
</div>
</li>

View File

@ -1,9 +1,8 @@
import type { Metadata } from "next";
import "./globals.css";
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: 'Bistro',
};
export default function RootLayout({
@ -13,9 +12,7 @@ export default function RootLayout({
}>) {
return (
<html lang="pl">
<body
className={`antialiased p-4 flex flex-col gap-2 items-center`}
>
<body className="antialiased p-4 flex flex-col gap-2 items-center">
{children}
</body>
</html>

View File

@ -1,4 +1,3 @@
import Image from 'next/image';
import Link from 'next/link';
export default function Home() {

35
frontend/app/tools.ts Normal file
View File

@ -0,0 +1,35 @@
export type Dish = {
item: string;
additional_info: string;
finished: boolean;
takeout: boolean;
};
export type Order = {
id: number;
created_on: string;
updated_on: string;
realization_time: string;
client: string;
waiter: number;
waiter_name: string;
data: Dish[];
status: number;
status_name: string;
};
export function getOrderBg(order: Order): string {
const nonTakeouts = order.data.filter((dish) => !dish.takeout);
if (order.status == 4) return 'bg-red-200 border-red-400';
return nonTakeouts.length > 0
? 'bg-green-200 border-green-400'
: 'bg-blue-200 border-blue-400';
}
export function getDishBg(dish: Dish): string {
return dish.takeout
? 'bg-blue-300 border-blue-500'
: 'bg-green-300 border-green-500';
}

View File

@ -3,16 +3,15 @@
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import * as React from 'react';
import { useForm, useFieldArray, useWatch, Control } from 'react-hook-form';
import { useForm, useFieldArray } from 'react-hook-form';
import { fetcher } from '@/app/api/tools';
import useSWR from 'swr';
import { Dish } from '@/app/tools';
type FormValues = {
data: {
item: string;
additional_info: string;
finished: boolean;
}[];
client: string;
realization_time: string;
data: Dish[];
};
export default function App() {
@ -27,9 +26,13 @@ export default function App() {
formState: { errors },
} = useForm<FormValues>({
defaultValues: {
data: [{ item: '', additional_info: '', finished: false }],
client: '',
realization_time: new Date().toLocaleTimeString('pl-PL', { hour: '2-digit', minute: '2-digit' }),
data: [
{ item: 'Inne', additional_info: '', finished: false, takeout: false },
],
},
mode: 'onBlur',
mode: 'all',
});
const { fields, append, remove } = useFieldArray({
name: 'data',
@ -45,7 +48,7 @@ export default function App() {
},
})
.then(({ status }) => console.log(status))
.then((_) => router.push(`/waiter/${id}`));
.then(() => router.push(`/waiter/${id}`));
};
if (error) return <div>Błąd przy ładowaniu danych</div>;
@ -59,6 +62,26 @@ export default function App() {
className="flex flex-col max-w-screen-md w-full my-4"
>
<p className="text-2xl text-center">Nowe zamówienie</p>
<p>
<label htmlFor={`client`}>Klient</label>
<input
placeholder="Klient"
type="text"
{...register(`client` as const, {
required: false,
})}
className={errors?.client ? 'error' : ''}
/>
</p>
<p>
<label htmlFor={`realization_time`}>Godzina realizacji</label>
<input
{...register(`realization_time` as const, {
required: false,
})}
className={errors?.realization_time ? 'error' : ''}
/>
</p>
{fields.map((field, index) => {
return (
<div
@ -72,7 +95,7 @@ export default function App() {
required: false,
})}
/>
<p className="basis-1/3">
<div className="basis-1/3">
<label htmlFor={`data.${index}.item`}>Danie</label>
<select
{...register(`data.${index}.item` as const, {
@ -87,8 +110,18 @@ export default function App() {
</option>
))}
</select>
</p>
<p className="basis-1/3">
<section className="flex flex-row my-2 gap-2">
<label htmlFor={`data.${index}.takeout`}>Na wynos</label>
<input
{...register(`data.${index}.takeout` as const, {
required: false,
})}
type="checkbox"
className={errors?.data?.[index]?.takeout ? 'error' : ''}
/>
</section>
</div>
<div className="basis-1/3">
<label htmlFor={`data.${index}.additional_info`}>
Dodatkowe informacje
</label>
@ -101,7 +134,7 @@ export default function App() {
errors?.data?.[index]?.additional_info ? 'error' : ''
}
/>
</p>
</div>
{fields.length > 1 ? (
<p className="basis-1/3 flex items-center justify-center">
<button type="button" onClick={() => remove(index)}>
@ -123,6 +156,7 @@ export default function App() {
item: '',
additional_info: '',
finished: false,
takeout: false,
})
}
>
@ -141,7 +175,9 @@ export default function App() {
Złóż zamówienie
</button>
</form>
<Link href={`/waiter/${id}`} className='mt-10'><button>Powrót</button></Link>
<Link href={`/waiter/${id}`} className="mt-10">
<button>Powrót</button>
</Link>
</>
);
}

View File

@ -4,6 +4,8 @@ import { fetcher } from '@/app/api/tools';
import useSWR from 'swr';
import { useParams } from 'next/navigation';
import Link from 'next/link';
import { getDishBg, getOrderBg, Order } from '@/app/tools';
import { OrderID } from '@/app/components';
export default function Home() {
const { id } = useParams();
@ -27,59 +29,33 @@ export default function Home() {
</p>
<p className="text-4xl">Zamówienia:</p>
<ul className="max-w-screen-md w-full">
{data?.map(
(order: {
id: number;
created_on: string;
updated_on: string;
waiter: number;
waiter_name: string;
data: [
{ item: string; additional_info: string; finished: boolean }
];
status: string;
status_name: string;
}) => (
<li
key={order.id}
className={`m-2 p-2 rounded-md border-2 ${
{
1: 'bg-red-200 border-red-400',
2: 'bg-purple-200 border-purple-400',
3: 'bg-green-200 border-green-400',
4: 'bg-gray-100 border-gray-400',
}[order.status]
}`}
>
<h1 className="text-xl">
<span className="font-mono">
{'['}
{String(order.id % 1000).padStart(3, '0')}
{']'}
</span>{' '}
- {new Date(order.updated_on).toLocaleTimeString('pl-PL')}
</h1>
<h2 className="text-xl">{order.waiter_name}</h2>
<h3>{order.status_name}</h3>
<div className="">
<ul>
{order.data.map((dish) => (
<li
key={dish.item}
className="p-2 m-2 flex gap-4 bg-white border-2 border-gray-400"
>
<input type="checkbox" checked={dish.finished} readOnly />
<div>
<p className="text-lg">{dish.item}</p>
<div>{dish.additional_info}</div>
</div>
</li>
))}
</ul>
</div>
</li>
)
)}
{data?.map((order: Order) => (
<li
key={order.id}
className={`m-2 p-2 rounded-md border-2 ${getOrderBg(order)}`}
>
<OrderID order={order} />
<h2 className="text-xl">Kelner: {order.waiter_name}</h2>
{ (order.client) ? <h2 className="text-xl">Klient: {order.client}</h2> : <></>}
<h3>{order.status_name}</h3>
<div className="">
<ul>
{order.data.map((dish) => (
<li
key={dish.item}
className={`p-2 m-2 flex gap-4 border-2 ${getDishBg(dish)}`}
>
<input type="checkbox" checked={dish.finished} readOnly />
<div>
<p className="text-lg">{dish.item} - {dish.takeout ? 'Na wynos' : 'Na miejscu'}</p>
<div>{dish.additional_info}</div>
</div>
</li>
))}
</ul>
</div>
</li>
))}
</ul>
</>
);

View File

@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
output: 'standalone'
};
export default nextConfig;