Add project files.
This commit is contained in:
commit
86a57f81f8
52 changed files with 4823 additions and 0 deletions
8
.dockerignore
Normal file
8
.dockerignore
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# directories
|
||||||
|
**/bin/
|
||||||
|
**/obj/
|
||||||
|
**/out/
|
||||||
|
|
||||||
|
# files
|
||||||
|
Dockerfile*
|
||||||
|
**/*.md
|
||||||
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
||||||
363
.gitignore
vendored
Normal file
363
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
22
Dockerfile
Normal file
22
Dockerfile
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Learn about building .NET container images:
|
||||||
|
# https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ARG TARGETARCH
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
# copy csproj and restore as distinct layers
|
||||||
|
COPY PizzaBot/*.csproj .
|
||||||
|
RUN dotnet restore -a $TARGETARCH
|
||||||
|
|
||||||
|
# copy and publish app and libraries
|
||||||
|
COPY PizzaBot/. .
|
||||||
|
RUN dotnet publish -a $TARGETARCH --no-restore -o /app
|
||||||
|
|
||||||
|
|
||||||
|
# final stage/image
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0
|
||||||
|
EXPOSE 8080
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app .
|
||||||
|
USER $APP_UID
|
||||||
|
ENTRYPOINT ["./PizzaBot"]
|
||||||
27
PizzaBot.sln
Normal file
27
PizzaBot.sln
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.8.34408.163
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PizzaBot", "PizzaBot\PizzaBot.csproj", "{74F20D7A-6724-4A49-9EC2-61461D5193BC}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1BF3D050-0F18-4862-95CD-24C01C035F63}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{74F20D7A-6724-4A49-9EC2-61461D5193BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{74F20D7A-6724-4A49-9EC2-61461D5193BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{74F20D7A-6724-4A49-9EC2-61461D5193BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{74F20D7A-6724-4A49-9EC2-61461D5193BC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {E7F8AF82-C1E7-4F8A-98D1-5C5B3A486F04}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
25
PizzaBot/Components/App.razor
Normal file
25
PizzaBot/Components/App.razor
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<base href="/" />
|
||||||
|
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||||
|
<link rel="stylesheet" href="app.css" />
|
||||||
|
<link rel="stylesheet" href="PizzaBot.styles.css" />
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="_content/Radzen.Blazor/css/material-base.css">
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
|
<HeadOutlet />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="min-height:100svh">
|
||||||
|
<Routes />
|
||||||
|
<script src="_framework/blazor.web.js"></script>
|
||||||
|
<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
11
PizzaBot/Components/GlobalMessages.razor
Normal file
11
PizzaBot/Components/GlobalMessages.razor
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
|
||||||
|
@if (GlobalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
<p style="font-size: 1.2em; color: red;"> @GlobalStuffService.LOCKED_ORDERS_MESSAGE </p>
|
||||||
|
}
|
||||||
|
@if (GlobalStuffService.Message.Length >= 2)
|
||||||
|
{
|
||||||
|
<p style="font-size: 1.5em; color: limegreen;"> @GlobalStuffService.Message </p>
|
||||||
|
|
||||||
|
}
|
||||||
15
PizzaBot/Components/HelpButton.razor
Normal file
15
PizzaBot/Components/HelpButton.razor
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div style="margin:5px">
|
||||||
|
<span>Help</span>
|
||||||
|
<a href="/help" class="btn btn-primary" style="height:2rem; aspect-ratio:1; padding: 0.25rem;">
|
||||||
|
<div class="help-button" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.help-button {
|
||||||
|
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="whitesmoke" class="bi bi-question-lg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14"/></svg>');
|
||||||
|
background-size: contain;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
36
PizzaBot/Components/InputSlider.razor
Normal file
36
PizzaBot/Components/InputSlider.razor
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
@using System.Text.RegularExpressions
|
||||||
|
|
||||||
|
@inherits InputBase<float>
|
||||||
|
|
||||||
|
<input type="range" @attributes=AdditionalAttributes class=@CssClass @bind=CurrentValueAsString />
|
||||||
|
|
||||||
|
@code{
|
||||||
|
protected override string FormatValueAsString(float value)
|
||||||
|
=> value.ToString("F5");
|
||||||
|
|
||||||
|
|
||||||
|
static Regex decimalNumberRegex = new Regex(@"^-?\d+(\.\d+)?$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
protected override bool TryParseValueFromString(string value, out float result, out string validationErrorMessage)
|
||||||
|
{
|
||||||
|
Match match = decimalNumberRegex.Match(value);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
validationErrorMessage = "Not a valid float";
|
||||||
|
result = 0.0f;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(float.TryParse(value, out float parsingResult))
|
||||||
|
{
|
||||||
|
result = parsingResult;
|
||||||
|
validationErrorMessage = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationErrorMessage = "Not a valid float";
|
||||||
|
result = 0.0f;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
21
PizzaBot/Components/Layout/MainLayout.razor
Normal file
21
PizzaBot/Components/Layout/MainLayout.razor
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="sidebar">
|
||||||
|
<NavMenu />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<article class="content px-4">
|
||||||
|
<GlobalMessages />
|
||||||
|
@Body
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="blazor-error-ui">
|
||||||
|
An unhandled error has occurred.
|
||||||
|
<a href="" class="reload">Reload</a>
|
||||||
|
<a class="dismiss">🗙</a>
|
||||||
|
</div>
|
||||||
96
PizzaBot/Components/Layout/MainLayout.razor.css
Normal file
96
PizzaBot/Components/Layout/MainLayout.razor.css
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border-bottom: 1px solid #d6d5d5;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: 3.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a:first-child {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640.98px) {
|
||||||
|
.top-row {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.page {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 250px;
|
||||||
|
height: 100vh;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row.auth ::deep a:first-child {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row, article {
|
||||||
|
padding-left: 2rem !important;
|
||||||
|
padding-right: 1.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-error-ui {
|
||||||
|
background: lightyellow;
|
||||||
|
bottom: 0;
|
||||||
|
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
display: none;
|
||||||
|
left: 0;
|
||||||
|
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-error-ui .dismiss {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.75rem;
|
||||||
|
top: 0.5rem;
|
||||||
|
}
|
||||||
30
PizzaBot/Components/Layout/NavMenu.razor
Normal file
30
PizzaBot/Components/Layout/NavMenu.razor
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="top-row ps-3 navbar navbar-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="">🍕 PizzaBot 2000</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
|
||||||
|
|
||||||
|
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||||
|
<nav class="flex-column">
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="orderlist">
|
||||||
|
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Pizza Order List
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="order">
|
||||||
|
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> New Pizza Order
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="help">
|
||||||
|
<span class="bi bi-help-nav-menu" aria-hidden="true"></span> How does this work?
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
109
PizzaBot/Components/Layout/NavMenu.razor.css
Normal file
109
PizzaBot/Components/Layout/NavMenu.razor.css
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
.navbar-toggler {
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
color: white;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 1rem;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler:checked {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
height: 3.5rem;
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
top: -1px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-house-door-fill-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-plus-square-fill-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-list-nested-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-help-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-question-lg' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:first-of-type {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:last-of-type {
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep .nav-link {
|
||||||
|
color: #d7d7d7;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 3rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 3rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep a.active {
|
||||||
|
background-color: rgba(255,255,255,0.37);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep .nav-link:hover {
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-scrollable {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler:checked ~ .nav-scrollable {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.navbar-toggler {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-scrollable {
|
||||||
|
/* Never collapse the sidebar for wide screens */
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
/* Allow sidebar to scroll for tall menus */
|
||||||
|
height: calc(100vh - 3.5rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
PizzaBot/Components/OrderButton.razor
Normal file
15
PizzaBot/Components/OrderButton.razor
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div style="margin:5px">
|
||||||
|
<span>Order</span>
|
||||||
|
<a href="/order" class="btn btn-primary" style="height:2rem; aspect-ratio:1; padding: 0.25rem;">
|
||||||
|
<div class=" order-button" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.order-button {
|
||||||
|
background-image: url('data:image/svg+xml, <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="whitesmoke" class="bi bi-plus-square-fill" viewBox="0 0 16 16"><path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0" /></svg>');
|
||||||
|
background-size: contain;
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
164
PizzaBot/Components/Pages/Admin.razor
Normal file
164
PizzaBot/Components/Pages/Admin.razor
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
@page "/admin{secretpath}"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
@inject PizzaDBService PizzaDBService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<PageTitle>Pizza Admin</PageTitle>
|
||||||
|
|
||||||
|
<TotalPizzasDisplay />
|
||||||
|
|
||||||
|
<h3>Hello, you are my GOD!</h3>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Order Locking</h4>
|
||||||
|
<button @onclick="ToggleOrdersLocked" class="btn btn-danger">
|
||||||
|
@if (GlobalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
<span>Unlock Orders</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>Lock Orders</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>Global Message</h4>
|
||||||
|
<InputText @bind-Value=GlobalMessage id="Message" />
|
||||||
|
<button @onclick="SetGlobalMessage">
|
||||||
|
Confirm Message
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@if (GlobalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
<h4>Payment</h4>
|
||||||
|
<a href="/admin@(secretpath)/pay"><div class="btn btn-primary">Go to payment screen</div></a>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h4>Delete Orders</h4>
|
||||||
|
@if (HasDeletedEverything)
|
||||||
|
{
|
||||||
|
<p style="color: lime">Deleted everything</p>
|
||||||
|
}
|
||||||
|
@if (WrongDeletionPasscode)
|
||||||
|
{
|
||||||
|
<p style="color: red">Wrong passphrase</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<label for="Passphrase">Deletion Passphrase</label>
|
||||||
|
<InputText @bind-Value=DeletionPasscode id="Passphrase" class="form-control" />
|
||||||
|
|
||||||
|
<button @onclick="() => {DeleteOrderConfirmation = true;}" class="btn btn-danger">
|
||||||
|
DELETE ALL ORDERS
|
||||||
|
</button>
|
||||||
|
@if (DeleteOrderConfirmation)
|
||||||
|
{
|
||||||
|
<button @onclick="DeleteAllOrders" class="btn btn-danger">
|
||||||
|
ARE YOU SURE?
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h4>Configuration</h4>
|
||||||
|
<EditForm Model="@PConfig" OnValidSubmit="@SaveNewPizzaConfig" FormName="PizzaConfigForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="SizeX">Width of Pizza</label>
|
||||||
|
<InputNumber @bind-Value=PConfig.SizeX id="SizeX" class="form-control" ParsingErrorMessage="Must be float value" />
|
||||||
|
<ValidationMessage For="() => PConfig.SizeX " />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="SizeY">Height of Pizza</label>
|
||||||
|
<InputNumber @bind-Value=PConfig.SizeY id="SizeY" class="form-control" ParsingErrorMessage="Must be float value" />
|
||||||
|
<ValidationMessage For="() => PConfig.SizeY " />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Price">Price of Pizza in Cents</label>
|
||||||
|
<InputNumber @bind-Value=PConfig.Price id="Price" class="form-control" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => PConfig.Price " />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Pieces">Pieces per Pizza</label>
|
||||||
|
<InputNumber @bind-Value=PConfig.Fragments id="Pieces" class="form-control" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => PConfig.Fragments " />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Toppings">Number of allowed toppings per pizza</label>
|
||||||
|
<InputNumber @bind-Value=PConfig.Toppings id="Toppings" class="form-control" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => PConfig.Toppings " />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for"MaxNameLength">Maximum length of names</label>
|
||||||
|
<InputNumber @bind-Value=PConfig.NameLength id="MaxNameLength" class="form-control" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => PConfig.NameLength " />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="PenaltyType">Penalty Type used for balancing</label>
|
||||||
|
<InputSelect @bind-Value=PConfig.PenaltyType id="PenaltyType" class="form-control">
|
||||||
|
<option value="@Models.PenaltyType.Tuxic">Tuxic Penalty (Original)</option>
|
||||||
|
<option value="@Models.PenaltyType.PfeifferTreimer">Pfeiffer-Treimer Penalty (Default)</option>
|
||||||
|
<option value="@Models.PenaltyType.PfeifferTreimerLockedDown">Pfeiffer-Treimer Penalty (Clamped)</option>
|
||||||
|
</InputSelect>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Submit" class="btn btn-primary" />
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h4 {
|
||||||
|
margin-top: 2rem !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@code {
|
||||||
|
PizzaConfig PConfig = new PizzaConfig();
|
||||||
|
string GlobalMessage = "";
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string secretpath { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (secretpath != Environment.GetEnvironmentVariable("ADMIN_PATH"))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("/"); return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalMessage = GlobalStuffService.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeleteOrderConfirmation = false;
|
||||||
|
bool HasDeletedEverything = false;
|
||||||
|
bool WrongDeletionPasscode = false;
|
||||||
|
string DeletionPasscode = "";
|
||||||
|
|
||||||
|
void SaveNewPizzaConfig(EditContext editContext)
|
||||||
|
{
|
||||||
|
GlobalStuffService.SetConfig(PConfig);
|
||||||
|
GlobalStuffService.ShouldBalance = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToggleOrdersLocked()
|
||||||
|
{
|
||||||
|
GlobalStuffService.SetOrdersLocked(!GlobalStuffService.OrdersLocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetGlobalMessage()
|
||||||
|
{
|
||||||
|
GlobalStuffService.SetMessage(GlobalMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteAllOrders()
|
||||||
|
{
|
||||||
|
HasDeletedEverything = PizzaDBService.DeleteAllOrders(DeletionPasscode);
|
||||||
|
WrongDeletionPasscode = !HasDeletedEverything;
|
||||||
|
|
||||||
|
DeleteOrderConfirmation = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
PConfig = GlobalStuffService.GetConfig() ?? new PizzaConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
125
PizzaBot/Components/Pages/AdminPay.razor
Normal file
125
PizzaBot/Components/Pages/AdminPay.razor
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
@page "/admin{secretpath}/pay"
|
||||||
|
@using System.Globalization
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inject PizzaDBService PizzaDBService
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
<PageTitle>PizzaPay</PageTitle>
|
||||||
|
|
||||||
|
<h3>AdminPay</h3>
|
||||||
|
|
||||||
|
<div class="pizza-order-grid">
|
||||||
|
<div class="right-border">Paid?</div>
|
||||||
|
<div class="right-border">Name</div>
|
||||||
|
<div class="right-border">Price</div>
|
||||||
|
<div style="text-align:center; grid-column-start: span 3;">Pieces</div>
|
||||||
|
|
||||||
|
<div class="right-border"></div>
|
||||||
|
<div class="right-border"></div>
|
||||||
|
<div class="right-border"></div>
|
||||||
|
<div class="right-border">🍖</div>
|
||||||
|
<div class="right-border">🍄+🧀</div>
|
||||||
|
<div class="right-border">🌽</div>
|
||||||
|
|
||||||
|
@foreach (PizzaRequest request in PizzaDBService.GetAllRequests())
|
||||||
|
{
|
||||||
|
PizzaResult? result = PizzaDBService.GetResultById(request.Id);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
<p class="request error" style="color: red; grid-column-start: span @gridwidth;">ERROR: Result of request with name @request.Name not found!</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="request">
|
||||||
|
@if (result.hasPaid)
|
||||||
|
{
|
||||||
|
<button @onclick="() => MarkAsUnpaid(result.Id)">
|
||||||
|
✅
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button @onclick="() => MarkAsPaid(result.Id)">
|
||||||
|
❌
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="request">@request.Name</div>
|
||||||
|
<div class="request">@totalCostToString(result.totalCost)</div>
|
||||||
|
<div class="request">@result.resPiecesMeat / @request.reqPiecesMeat</div>
|
||||||
|
<div class="request">@result.resPiecesVegetarian / @request.reqPiecesVegetarian</div>
|
||||||
|
<div class="request">@result.resPiecesVegan / @request.reqPiecesVegan</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.pizza-order-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 25vw repeat(@(gridwidth - 2), 1fr);
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-order-grid > * {
|
||||||
|
word-break: break-all;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-order-grid > .request {
|
||||||
|
border-top: solid 1px gray;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-order-grid > .request:not(.price):not(.error),
|
||||||
|
.pizza-order-grid > .right-border {
|
||||||
|
border-right: solid 1px gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.request button {
|
||||||
|
width: 2.5em;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
appearance:none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string secretpath { get; set; }
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
if (secretpath != Environment.GetEnvironmentVariable("ADMIN_PATH"))
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("/"); return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int gridwidth = 6;
|
||||||
|
|
||||||
|
string totalCostToString(float totalCost)
|
||||||
|
{
|
||||||
|
return totalCost.ToString("C2", CultureInfo.CreateSpecificCulture("de-DE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkAsPaid(int id)
|
||||||
|
{
|
||||||
|
PizzaDBService.MarkAsPaid(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkAsUnpaid(int id)
|
||||||
|
{
|
||||||
|
PizzaDBService.MarkAsNotPaid(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
PizzaBot/Components/Pages/Error.razor
Normal file
36
PizzaBot/Components/Pages/Error.razor
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
@page "/Error"
|
||||||
|
@using System.Diagnostics
|
||||||
|
|
||||||
|
<PageTitle>Error</PageTitle>
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@code{
|
||||||
|
[CascadingParameter]
|
||||||
|
private HttpContext? HttpContext { get; set; }
|
||||||
|
|
||||||
|
private string? RequestId { get; set; }
|
||||||
|
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
|
protected override void OnInitialized() =>
|
||||||
|
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||||
|
}
|
||||||
33
PizzaBot/Components/Pages/Help.razor
Normal file
33
PizzaBot/Components/Pages/Help.razor
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
@page "/help"
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
|
||||||
|
<PageTitle>Pizza Help</PageTitle>
|
||||||
|
|
||||||
|
<h3>How To Pizza</h3>
|
||||||
|
|
||||||
|
<p>You can choose any number of slices from any of the three categories of pizzas - meat, vegetarian and vegan.</p>
|
||||||
|
<p>Each pizza of size @((_config.SizeX * 100).ToString("F0"))cm X @(_config.SizeY * 100)cm is cut into @(_config.Fragments) pieces of approximately @GlobalStuffService.GetSizeOfSliceInCM2().ToString("F0")cm<sup>2</sup>.</p>
|
||||||
|
<p>
|
||||||
|
The system calculates how many pizzas should be ordered and distributes slices according to the pieces you request and the priority you set.
|
||||||
|
This means, you might get more or less slices than you requested, or slices from a different category than you requested.
|
||||||
|
The system tries to respect your wishes. If you are not happy with the outcome, try changing your priority.
|
||||||
|
</p>
|
||||||
|
<p>In the list, you can see your order: <i> Name, Assigned pieces / Requested pieces (for each category), Price </i></p>
|
||||||
|
<p>
|
||||||
|
🍖 = Meat <br />
|
||||||
|
🍄🧀 = Vegetarian <br />
|
||||||
|
🌽 = Vegan
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
PizzaConfig _config;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
_config = GlobalStuffService.GetConfig();
|
||||||
|
if(_config == null)
|
||||||
|
{
|
||||||
|
_config = new PizzaConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
PizzaBot/Components/Pages/PizzaOrder.razor
Normal file
118
PizzaBot/Components/Pages/PizzaOrder.razor
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
@page "/order"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inherits LayoutComponentBase
|
||||||
|
@inject PizzaDBService PizzaDBService
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
@inject Microsoft.AspNetCore.Components.NavigationManager NavigationManager
|
||||||
|
|
||||||
|
|
||||||
|
<PageTitle> Order Pizza </PageTitle>
|
||||||
|
|
||||||
|
@if (errorEncountered)
|
||||||
|
{
|
||||||
|
<p style="color: red; font-weight: 700;"> @ErrorMessage </p>
|
||||||
|
}
|
||||||
|
@if (submissionSuccessful)
|
||||||
|
{
|
||||||
|
<p style="color: lawngreen; font-weight: 700;"> @SuccessfulSubmissionMessage </p>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<EditForm Model=@Order OnValidSubmit="@FormSubmitted" FormName="PizzaOrderForm">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="Name">Name</label><span style="font-size:0.7em; color:dimgrey; margin:0;">must be unique</span>
|
||||||
|
<InputText @bind-Value=Order.Name id="Name" class="form-control" />
|
||||||
|
<ValidationMessage For="() => Order.Name" />
|
||||||
|
</div>
|
||||||
|
<label for="PiecesGroup" style="margin-top: 1em;">Pieces</label>
|
||||||
|
<span style="font-size: 0.7em;">One slice = ca. @GlobalStuffService.GetSizeOfSliceInCM2().ToString("F0") cm<sup>2</sup></span>
|
||||||
|
<div class="row" id="PiecesGroup">
|
||||||
|
<div class="form-group col">
|
||||||
|
<label for="meatPieces">🍖</label>
|
||||||
|
<InputNumber @bind-Value=Order.reqPiecesMeat class="form-control" id="meatPieces" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => Order.reqPiecesMeat" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col">
|
||||||
|
<label for="vegetarianPieces">🍄 + 🧀</label>
|
||||||
|
<InputNumber @bind-Value=Order.reqPiecesVegetarian class="form-control" id="vegetarianPieces" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => Order.reqPiecesVegetarian" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col">
|
||||||
|
<label for="veganPieces">🌽</label>
|
||||||
|
<InputNumber @bind-Value=Order.reqPiecesVegan class="form-control" id="veganPieces" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => Order.reqPiecesVegan" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-top: 3rem;">
|
||||||
|
<label for="priorityMeat" style="text-align:center;"> Priority </label>
|
||||||
|
<p style="text-align:center; font-size: 0.8em; margin: 0;">
|
||||||
|
The balancing algorithm tries to avoid changes of the corresponding Variable <br />
|
||||||
|
<i style="font-size: 0.8em;">E.g. If you want to only get pieces from your chosen category put the slider all the way to the left</i></p>
|
||||||
|
|
||||||
|
<div class="priority-slider-container">
|
||||||
|
<div> Category </div>
|
||||||
|
<RadzenSlider @bind-Value=Order.priority TValue="float" Min="0" Max="1" Step="0.01" id="priorityMeat" />
|
||||||
|
<div> Total Count</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Submit" class="btn btn-primary"/>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#priorityMeat,
|
||||||
|
#priorityVegan {
|
||||||
|
--rz-slider-horizontal-width: 100%;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-piece-form-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-gap: 3px;
|
||||||
|
margin: 2rem 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.priority-slider-container{
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction:row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1em;
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
bool errorEncountered = false;
|
||||||
|
string ErrorMessage = "";
|
||||||
|
|
||||||
|
bool submissionSuccessful = false;
|
||||||
|
string SuccessfulSubmissionMessage = "Request submitted successfully";
|
||||||
|
|
||||||
|
PizzaRequest Order = new PizzaRequest();
|
||||||
|
// Random rnd = new Random();
|
||||||
|
|
||||||
|
void FormSubmitted(EditContext editContext)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (PizzaDBService.Create(Order, out ErrorMessage) == null)
|
||||||
|
{
|
||||||
|
errorEncountered = true;
|
||||||
|
submissionSuccessful = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorEncountered = false;
|
||||||
|
submissionSuccessful = true;
|
||||||
|
|
||||||
|
Order = new PizzaRequest();
|
||||||
|
|
||||||
|
NavigationManager.NavigateTo("/orderlist");
|
||||||
|
}
|
||||||
|
}
|
||||||
0
PizzaBot/Components/Pages/PizzaOrder.razor.css
Normal file
0
PizzaBot/Components/Pages/PizzaOrder.razor.css
Normal file
198
PizzaBot/Components/Pages/PizzaOrderEdit.razor
Normal file
198
PizzaBot/Components/Pages/PizzaOrderEdit.razor
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
@page "/order/edit/{id:int}"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@inherits LayoutComponentBase
|
||||||
|
@inject PizzaDBService pizzaDBService
|
||||||
|
@inject NavigationManager Navigation
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
|
||||||
|
<PageTitle> Change Pizza Order </PageTitle>
|
||||||
|
|
||||||
|
@if (errorEncountered)
|
||||||
|
{
|
||||||
|
<p style="color: red; font-weight: 700;"> @ErrorMessage </p>
|
||||||
|
}
|
||||||
|
@if (submissionSuccessful)
|
||||||
|
{
|
||||||
|
<p style="color: lawngreen; font-weight: 700;"> @SuccessfulSubmissionMessage </p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<EditForm Model=@Order OnValidSubmit="@FormSubmitted" FormName="PizzaOrderForm">
|
||||||
|
<DataAnnotationsValidator />
|
||||||
|
<h3>
|
||||||
|
@Order.Name
|
||||||
|
</h3>
|
||||||
|
<label for="PiecesGroup" style="margin-top: 1em;">Pieces</label>
|
||||||
|
<span style="font-size: 0.7em;">One slice = ca. @GlobalStuffService.GetSizeOfSliceInCM2().ToString("F0") cm<sup>2</sup></span>
|
||||||
|
<div class="row" id="PiecesGroup">
|
||||||
|
<div class="form-group col">
|
||||||
|
<label for="meatPieces">🍖</label>
|
||||||
|
<InputNumber @bind-Value=Order.reqPiecesMeat class="form-control" id="meatPieces" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => Order.reqPiecesMeat" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col">
|
||||||
|
<label for="vegetarianPieces">🍄 + 🧀</label>
|
||||||
|
<InputNumber @bind-Value=Order.reqPiecesVegetarian class="form-control" id="vegetarianPieces" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => Order.reqPiecesVegetarian" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group col">
|
||||||
|
<label for="veganPieces">🌽</label>
|
||||||
|
<InputNumber @bind-Value=Order.reqPiecesVegan class="form-control" id="veganPieces" ParsingErrorMessage="Must be integer value" />
|
||||||
|
<ValidationMessage For="() => Order.reqPiecesVegan" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-top: 3rem;">
|
||||||
|
<label for="priorityMeat" style="text-align:center;"> Priority </label>
|
||||||
|
<p style="text-align:center; font-size: 0.8em; margin: 0;">
|
||||||
|
The balancing algorithm tries to avoid changes of the corresponding Variable <br />
|
||||||
|
<i style="font-size: 0.8em;">E.g. If you want to only get pieces from your chosen category put the slider all the way to the left</i>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="priority-slider-container">
|
||||||
|
<div> Category </div>
|
||||||
|
<RadzenSlider @bind-Value=Order.priority TValue="float" Min="0" Max="1" Step="0.01" id="priorityMeat" />
|
||||||
|
<div> Total Count</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="💾 Save Changes 💾" class="btn btn-primary" />
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
<div class="deletion-container">
|
||||||
|
@if (!showDeletionConfirmation)
|
||||||
|
{
|
||||||
|
<button @onclick="ShowDeleteConfirmation"> 🗑 Delete </button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
<p>Are you sure you want to delete this order? <br /></p>
|
||||||
|
<div class="confirmation-buttons">
|
||||||
|
<button @onclick="DeleteOrder">🗑 Delete</button>
|
||||||
|
<button @onclick="() => showDeletionConfirmation = false">Cancel</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#priorityMeat,
|
||||||
|
#priorityVegan {
|
||||||
|
--rz-slider-horizontal-width: 80%;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-piece-form-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-gap: 3px;
|
||||||
|
margin: 2rem 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletion-container {
|
||||||
|
margin: 2rem 0;
|
||||||
|
background-color: red;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 0.5rem;
|
||||||
|
color: whitesmoke;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirmation-buttons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletion-container .confirmation-buttons button {
|
||||||
|
border: solid 0.1rem whitesmoke;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deletion-container button {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.3rem;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
color: whitesmoke;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
bool showDeletionConfirmation = false;
|
||||||
|
|
||||||
|
private void ShowDeleteConfirmation()
|
||||||
|
{
|
||||||
|
// Display the confirmation modal
|
||||||
|
showDeletionConfirmation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int id { get; set; }
|
||||||
|
|
||||||
|
bool errorEncountered = false;
|
||||||
|
string ErrorMessage = "";
|
||||||
|
|
||||||
|
bool submissionSuccessful = false;
|
||||||
|
string SuccessfulSubmissionMessage = "Request submitted successfully";
|
||||||
|
|
||||||
|
PizzaRequest Order = new PizzaRequest();
|
||||||
|
// Random rnd = new Random();
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
Order = pizzaDBService.GetRequestById(id);
|
||||||
|
if(Order == null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("/orderlist", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FormSubmitted(EditContext editContext)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!pizzaDBService.UpdateRequest(Order, out ErrorMessage))
|
||||||
|
{
|
||||||
|
errorEncountered = true;
|
||||||
|
submissionSuccessful = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorEncountered = false;
|
||||||
|
submissionSuccessful = true;
|
||||||
|
|
||||||
|
Order = pizzaDBService.GetRequestById(id);
|
||||||
|
|
||||||
|
// if(Order.reqPiecesMeat + Order.reqPiecesVegetarian + Order.reqPiecesVegan < 1)
|
||||||
|
// {
|
||||||
|
// submittedZeroPieces = true;
|
||||||
|
// submissionSuccessful = false;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// submittedZeroPieces = false;
|
||||||
|
// submissionSuccessful = true;
|
||||||
|
|
||||||
|
// Status = "Form Submitted";
|
||||||
|
// Order.Id = rnd.Next(int.MaxValue);
|
||||||
|
// OrderContext.Requests.Add(Order);
|
||||||
|
// OrderContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeleteOrder()
|
||||||
|
{
|
||||||
|
pizzaDBService.DeleteById(id);
|
||||||
|
Navigation.NavigateTo("/orderlist");
|
||||||
|
}
|
||||||
|
}
|
||||||
121
PizzaBot/Components/Pages/PizzaOrderList.razor
Normal file
121
PizzaBot/Components/Pages/PizzaOrderList.razor
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
@page "/orderlist"
|
||||||
|
@page "/"
|
||||||
|
@using System.Globalization
|
||||||
|
@inject PizzaDBService PizzaDBService
|
||||||
|
@inject Microsoft.AspNetCore.Components.NavigationManager navigationManager
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
@attribute [StreamRendering]
|
||||||
|
|
||||||
|
<PageTitle>Pizza List</PageTitle>
|
||||||
|
|
||||||
|
<div style="display: flex; ">
|
||||||
|
<TotalPizzasDisplay />
|
||||||
|
<div style ="display: flex; flex-direction:column; margin-left:auto; align-items:flex-end;">
|
||||||
|
<OrderButton />
|
||||||
|
<HelpButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Pizza Order List</h3>
|
||||||
|
|
||||||
|
<div class="pizza-order-grid">
|
||||||
|
<div class="right-border">Name</div>
|
||||||
|
<div class="right-border" style="grid-column-start: span 3;">Pieces</div>
|
||||||
|
<div class ="right-border">Price</div>
|
||||||
|
@if (GlobalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
<div>Paid?</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="right-border" style="grid-area: 2 / 1 / 2 / 1"></div>
|
||||||
|
<div class="right-border" style="font-size: 0.7em;">Meat</div>
|
||||||
|
<div class="right-border" style="font-size: 0.6em;">Veggie</div>
|
||||||
|
<div class="right-border" style="font-size: 0.7em;">Vegan</div>
|
||||||
|
<div class="right-border"></div>
|
||||||
|
<div style="grid-column-start: span @(gridwidth - 5);"></div>
|
||||||
|
|
||||||
|
<div class="right-border" style="grid-area: 3 / 1 / 3 / 1"></div>
|
||||||
|
<div class="right-border">🍖</div>
|
||||||
|
<div class="right-border">🍄🧀</div>
|
||||||
|
<div class="right-border">🌽</div>
|
||||||
|
<div class="right-border"></div>
|
||||||
|
<div style="grid-column-start: span @(gridwidth - 5);"></div>
|
||||||
|
|
||||||
|
@foreach (PizzaRequest request in PizzaDBService.GetAllRequests())
|
||||||
|
{
|
||||||
|
PizzaResult? result = PizzaDBService.GetResultById(request.Id);
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
<p class="request error" style="color: red; grid-column-start: span @gridwidth;">ERROR: Result of request with name @request.Name not found!</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="request right-border">@request.Name</div>
|
||||||
|
<div class="request right-border">@result.resPiecesMeat<span style="font-size:0.8em; color:dimgrey;">/ @request.reqPiecesMeat</span></div>
|
||||||
|
<div class="request right-border">@result.resPiecesVegetarian<span style="font-size:0.8em; color:dimgrey;">/ @request.reqPiecesVegetarian</span></div>
|
||||||
|
<div class="request right-border">@result.resPiecesVegan<span style="font-size:0.8em; color:dimgrey;">/ @request.reqPiecesVegan</span></div>
|
||||||
|
<div class="request price right-border">@totalCostToString(result.totalCost)</div>
|
||||||
|
@if (GlobalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
if (result.hasPaid)
|
||||||
|
{
|
||||||
|
<div class="request">
|
||||||
|
✅
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="request">
|
||||||
|
❌
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="request"><a href="/order/edit/@request.Id"><button class="btn btn-info" style="width: 2em; height: 2em; padding: 0; text-align:center;">🔧</button></a></div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="height:50px;" />
|
||||||
|
<style>
|
||||||
|
.pizza-order-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 25vw repeat(@(gridwidth - 2), 1fr) 3em;
|
||||||
|
grid-auto-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-order-grid > * {
|
||||||
|
word-break: break-all;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-order-grid > .request {
|
||||||
|
border-top: solid 1px gray;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pizza-order-grid > .right-border:not(.error) {
|
||||||
|
border-right: solid 1px gray;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
int gridwidth = 6;
|
||||||
|
|
||||||
|
string totalCostToString(float totalCost)
|
||||||
|
{
|
||||||
|
return totalCost.ToString("C2", CultureInfo.CreateSpecificCulture("de-DE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NavigateToEdit(int id)
|
||||||
|
{
|
||||||
|
navigationManager.NavigateTo($"/order/edit/{id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
6
PizzaBot/Components/Routes.razor
Normal file
6
PizzaBot/Components/Routes.razor
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<Router AppAssembly="typeof(Program).Assembly">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
17
PizzaBot/Components/TotalPizzasDisplay.razor
Normal file
17
PizzaBot/Components/TotalPizzasDisplay.razor
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
@inject GlobalStuffService GlobalStuffService
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span>🍖: @GlobalStuffService.MeatPizzas</span>
|
||||||
|
<span>🍄 + 🧀: @GlobalStuffService.VeggiePizzas</span>
|
||||||
|
<span>🌽: @GlobalStuffService.VeganPizzas</span>
|
||||||
|
<span>=> @GlobalStuffService.TotalCost.ToString("F2")€</span></p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
p{
|
||||||
|
font-size: 0.6em;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
padding-inline: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
14
PizzaBot/Components/_Imports.razor
Normal file
14
PizzaBot/Components/_Imports.razor
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
@using System.Net.Http
|
||||||
|
@using System.Net.Http.Json
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
@using PizzaBot
|
||||||
|
@using PizzaBot.Components
|
||||||
|
@using Radzen
|
||||||
|
@using Radzen.Blazor
|
||||||
|
@using PizzaBot.Models
|
||||||
|
@using PizzaBot.Services
|
||||||
31
PizzaBot/Dockerfile
Normal file
31
PizzaBot/Dockerfile
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0-focal-amd64 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
ENV DELETION_PASSPHRASE=""
|
||||||
|
ENV DATABASE_URL=""
|
||||||
|
ENV DATABASE_USERNAME=""
|
||||||
|
ENV DATABASE_PASSWD=""
|
||||||
|
ENV DATABASE_NAME=""
|
||||||
|
ENV ADMIN_PATH=""
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
WORKDIR /source
|
||||||
|
COPY PizzaBot.csproj .
|
||||||
|
RUN dotnet restore "PizzaBot.csproj"
|
||||||
|
# Copy everything
|
||||||
|
COPY . .
|
||||||
|
# Build and publish a release
|
||||||
|
RUN dotnet build "Pizzabot.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "Pizzabot.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
|
|
||||||
|
# Build runtime image
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
|
||||||
|
ENTRYPOINT ["dotnet", "Pizzabot.dll"]
|
||||||
85
PizzaBot/Migrations/20240126122625_InitialCreate.Designer.cs
generated
Normal file
85
PizzaBot/Migrations/20240126122625_InitialCreate.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using PizzaBot.Models;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PizzaBot.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PizzaContext))]
|
||||||
|
[Migration("20240126122625_InitialCreate")]
|
||||||
|
partial class InitialCreate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.2")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("PizzaBot.Models.PizzaRequest", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<float>("priority")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.Property<int>("reqPiecesMeat")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("reqPiecesVegan")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("reqPiecesVegetarian")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Requests");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PizzaBot.Models.PizzaResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("hasPaid")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<float>("penaltyMeatVeggi")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.Property<float>("penaltyVeggieVegan")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.Property<int>("resPiecesMeat")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("resPiecesVegan")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("resPiecesVegetarian")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<float>("totalCost")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Results");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
PizzaBot/Migrations/20240126122625_InitialCreate.cs
Normal file
67
PizzaBot/Migrations/20240126122625_InitialCreate.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PizzaBot.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialCreate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AlterDatabase()
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Requests",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Name = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
reqPiecesMeat = table.Column<int>(type: "int", nullable: false),
|
||||||
|
reqPiecesVegetarian = table.Column<int>(type: "int", nullable: false),
|
||||||
|
reqPiecesVegan = table.Column<int>(type: "int", nullable: false),
|
||||||
|
priority = table.Column<float>(type: "float", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Requests", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Results",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
resPiecesMeat = table.Column<int>(type: "int", nullable: false),
|
||||||
|
resPiecesVegetarian = table.Column<int>(type: "int", nullable: false),
|
||||||
|
resPiecesVegan = table.Column<int>(type: "int", nullable: false),
|
||||||
|
penaltyMeatVeggi = table.Column<float>(type: "float", nullable: false),
|
||||||
|
penaltyVeggieVegan = table.Column<float>(type: "float", nullable: false),
|
||||||
|
totalCost = table.Column<float>(type: "float", nullable: false),
|
||||||
|
hasPaid = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Results", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Requests");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Results");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
PizzaBot/Migrations/PizzaContextModelSnapshot.cs
Normal file
82
PizzaBot/Migrations/PizzaContextModelSnapshot.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using PizzaBot.Models;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PizzaBot.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PizzaContext))]
|
||||||
|
partial class PizzaContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.2")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||||
|
|
||||||
|
modelBuilder.Entity("PizzaBot.Models.PizzaRequest", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<float>("priority")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.Property<int>("reqPiecesMeat")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("reqPiecesVegan")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("reqPiecesVegetarian")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Requests");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PizzaBot.Models.PizzaResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<bool>("hasPaid")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
b.Property<float>("penaltyMeatVeggi")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.Property<float>("penaltyVeggieVegan")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.Property<int>("resPiecesMeat")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("resPiecesVegan")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("resPiecesVegetarian")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<float>("totalCost")
|
||||||
|
.HasColumnType("float");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Results");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
PizzaBot/Models/PizzaConfig.cs
Normal file
53
PizzaBot/Models/PizzaConfig.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace PizzaBot.Models
|
||||||
|
{
|
||||||
|
public enum PenaltyType
|
||||||
|
{
|
||||||
|
Tuxic = 0,
|
||||||
|
PfeifferTreimer = 1,
|
||||||
|
PfeifferTreimerLockedDown = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PizzaConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// size of pizza in m
|
||||||
|
/// </summary>
|
||||||
|
[Range(0.2f, 10)]
|
||||||
|
public float SizeX { get; set; }
|
||||||
|
[Range(0.2f, 10)]
|
||||||
|
public float SizeY { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// price of a pizza in ct
|
||||||
|
/// </summary>
|
||||||
|
[Range(1000, 10000, ErrorMessage = "Price of a Pizza needs to be between 10€ and 100€")]
|
||||||
|
public int Price { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// number of pieces per pizza
|
||||||
|
/// </summary>
|
||||||
|
[Range(8, 100, ErrorMessage = "Pieces per Pizza need to be between 8 and 100")]
|
||||||
|
public int Fragments { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// amount of toppings a pizza can have
|
||||||
|
/// </summary>
|
||||||
|
[Range(1, 50, ErrorMessage = "Toppings per Pizza need to be between 1 and 50")]
|
||||||
|
public int Toppings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// max length of order names
|
||||||
|
/// </summary>
|
||||||
|
[Range(1, 500, ErrorMessage = "Name length needs to be between 1 and 500")]
|
||||||
|
public int NameLength { get; set; }
|
||||||
|
|
||||||
|
public PenaltyType PenaltyType { get; set; }
|
||||||
|
|
||||||
|
public PizzaConfig GetShallowCopy()
|
||||||
|
{
|
||||||
|
return (PizzaConfig)this.MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
PizzaBot/Models/PizzaContext.cs
Normal file
14
PizzaBot/Models/PizzaContext.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace PizzaBot.Models
|
||||||
|
{
|
||||||
|
public class PizzaContext : DbContext
|
||||||
|
{
|
||||||
|
public DbSet<PizzaRequest> Requests { get; set; }
|
||||||
|
public DbSet<PizzaResult> Results { get; set; }
|
||||||
|
|
||||||
|
public PizzaContext(DbContextOptions options) : base(options) { }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
29
PizzaBot/Models/PizzaRequest.cs
Normal file
29
PizzaBot/Models/PizzaRequest.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace PizzaBot.Models
|
||||||
|
{
|
||||||
|
public class PizzaRequest
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[Range(0, 1000, ErrorMessage = "Pieces can't be negative or over 1000")]
|
||||||
|
public int reqPiecesMeat { get; set; }
|
||||||
|
|
||||||
|
[Range(0, 1000, ErrorMessage = "Pieces can't be negative or over 1000")]
|
||||||
|
public int reqPiecesVegetarian { get; set; }
|
||||||
|
|
||||||
|
[Range(0, 1000, ErrorMessage = "Pieces can't be negative or over 1000")]
|
||||||
|
public int reqPiecesVegan { get; set; }
|
||||||
|
|
||||||
|
[Range(0.0f, 1.0f)]
|
||||||
|
public float priority { get; set; } = 0.5f;
|
||||||
|
|
||||||
|
public PizzaRequest GetShallowCopy()
|
||||||
|
{
|
||||||
|
return (PizzaRequest)this.MemberwiseClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
PizzaBot/Models/PizzaResult.cs
Normal file
27
PizzaBot/Models/PizzaResult.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace PizzaBot.Models
|
||||||
|
{
|
||||||
|
public class PizzaResult
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Range(0, 1000, ErrorMessage = "Pieces can't be negative or over 1000")]
|
||||||
|
public int resPiecesMeat { get; set; }
|
||||||
|
|
||||||
|
[Range(0, 1000, ErrorMessage = "Pieces can't be negative or over 1000")]
|
||||||
|
public int resPiecesVegetarian { get; set; }
|
||||||
|
|
||||||
|
[Range(0, 1000, ErrorMessage = "Pieces can't be negative or over 1000")]
|
||||||
|
public int resPiecesVegan { get; set; }
|
||||||
|
|
||||||
|
public float penaltyMeatVeggi { get; set; }
|
||||||
|
|
||||||
|
public float penaltyVeggieVegan { get; set; }
|
||||||
|
|
||||||
|
public float totalCost { get; set; }
|
||||||
|
|
||||||
|
public bool hasPaid { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
31
PizzaBot/PizzaBot.csproj
Normal file
31
PizzaBot/PizzaBot.csproj
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Components\Pages\PizzaOrder.razor.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Components\Pages\PizzaOrder.razor.css" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.2" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Radzen.Blazor" Version="4.24.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Migrations\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
41
PizzaBot/Program.cs
Normal file
41
PizzaBot/Program.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using PizzaBot.Components;
|
||||||
|
using PizzaBot.Models;
|
||||||
|
using PizzaBot.Services;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents();
|
||||||
|
|
||||||
|
String connectionString = "server=" + Environment.GetEnvironmentVariable("DATABASE_URL") +
|
||||||
|
";uid=" + Environment.GetEnvironmentVariable("DATABASE_USERNAME") +
|
||||||
|
";pwd=" + Environment.GetEnvironmentVariable("DATABASE_PASSWD") +
|
||||||
|
";database=" + Environment.GetEnvironmentVariable("DATABASE_NAME");
|
||||||
|
builder.Services.AddDbContext<PizzaContext>(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)), ServiceLifetime.Singleton, ServiceLifetime.Singleton);
|
||||||
|
builder.Services.AddSingleton<JSONService>();
|
||||||
|
builder.Services.AddSingleton<PizzaBalancingService>();
|
||||||
|
builder.Services.AddSingleton<PizzaDBService>();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<GlobalStuffService>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||||
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
|
app.UseHsts();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
|
app.UseAntiforgery();
|
||||||
|
|
||||||
|
app.MapRazorComponents<App>()
|
||||||
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
44
PizzaBot/Properties/launchSettings.json
Normal file
44
PizzaBot/Properties/launchSettings.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "http://localhost:5167"
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"DELETION_PASSPHRASE": "Lenny ist cool!",
|
||||||
|
"DATABASE_URL": "localhost",
|
||||||
|
"DATABASE_USERNAME": "PizzaBotDev",
|
||||||
|
"DATABASE_PASSWD": "PizzaDeliveryEverywhere!",
|
||||||
|
"DATABASE_NAME": "PizzaBotTest",
|
||||||
|
"ADMIN_PATH": "hades"
|
||||||
|
},
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"applicationUrl": "https://localhost:7032;http://localhost:5167"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:11491",
|
||||||
|
"sslPort": 44378
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
PizzaBot/Services/GlobalStuffService.cs
Normal file
68
PizzaBot/Services/GlobalStuffService.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using PizzaBot.Models;
|
||||||
|
|
||||||
|
namespace PizzaBot.Services
|
||||||
|
{
|
||||||
|
public class GlobalStuffService
|
||||||
|
{
|
||||||
|
public string Message { get { return _message; }}
|
||||||
|
private string _message = " ";
|
||||||
|
|
||||||
|
public bool OrdersLocked { get { return _ordersLocked; } }
|
||||||
|
private bool _ordersLocked;
|
||||||
|
public readonly string LOCKED_ORDERS_MESSAGE = "Orders have been locked. Pizza will be ordered soon.";
|
||||||
|
|
||||||
|
public int MeatPizzas { get; set; }
|
||||||
|
public int VeggiePizzas { get; set; }
|
||||||
|
public int VeganPizzas { get; set; }
|
||||||
|
|
||||||
|
public float TotalCost { get; set; }
|
||||||
|
|
||||||
|
private PizzaConfig _pizzaConfig;
|
||||||
|
private JSONService _jsonService;
|
||||||
|
|
||||||
|
|
||||||
|
public bool ShouldBalance { get; set; } = true;
|
||||||
|
|
||||||
|
public GlobalStuffService(JSONService jSONService)
|
||||||
|
{
|
||||||
|
_jsonService = jSONService;
|
||||||
|
_pizzaConfig = _jsonService.ReadPizzaConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetMessage(string message)
|
||||||
|
{
|
||||||
|
_message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetOrdersLocked(bool ordersLocked)
|
||||||
|
{
|
||||||
|
_ordersLocked = ordersLocked;
|
||||||
|
|
||||||
|
ShouldBalance = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PizzaConfig? GetConfig()
|
||||||
|
{
|
||||||
|
if(_pizzaConfig == null)
|
||||||
|
{
|
||||||
|
_pizzaConfig = _jsonService.ReadPizzaConfig();
|
||||||
|
if(_pizzaConfig == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _pizzaConfig.GetShallowCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetConfig(PizzaConfig pizzaConfig)
|
||||||
|
{
|
||||||
|
_pizzaConfig = pizzaConfig;
|
||||||
|
_jsonService.WritePizzaConfig(pizzaConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetSizeOfSliceInCM2()
|
||||||
|
{
|
||||||
|
return ((_pizzaConfig.SizeX*100) * (_pizzaConfig.SizeY*100)) / _pizzaConfig.Fragments;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
PizzaBot/Services/JSONService.cs
Normal file
38
PizzaBot/Services/JSONService.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
using PizzaBot.Models;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace PizzaBot.Services
|
||||||
|
{
|
||||||
|
public class JSONService
|
||||||
|
{
|
||||||
|
public IWebHostEnvironment WebHostEnvironment { get; }
|
||||||
|
|
||||||
|
const string CONFIG_DIRECTORY_PATH = "config";
|
||||||
|
const string PIZZA_CONFIG_FILENAME = "pizza.config";
|
||||||
|
|
||||||
|
public JSONService(IWebHostEnvironment webHostEnvironment)
|
||||||
|
{
|
||||||
|
WebHostEnvironment = webHostEnvironment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PizzaConfig? ReadPizzaConfig()
|
||||||
|
{
|
||||||
|
string path = Path.Combine(WebHostEnvironment.WebRootPath, CONFIG_DIRECTORY_PATH, PIZZA_CONFIG_FILENAME);
|
||||||
|
string jsonString = File.ReadAllText(path);
|
||||||
|
PizzaConfig? pizzaConfig = JsonSerializer.Deserialize<PizzaConfig>(jsonString);
|
||||||
|
return pizzaConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WritePizzaConfig(PizzaConfig pizzaConfig)
|
||||||
|
{
|
||||||
|
string path = Path.Combine(WebHostEnvironment.WebRootPath, CONFIG_DIRECTORY_PATH, PIZZA_CONFIG_FILENAME);
|
||||||
|
string jsonString = JsonSerializer.Serialize(pizzaConfig);
|
||||||
|
File.WriteAllText(path, jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPizzaConfigPath()
|
||||||
|
{
|
||||||
|
return Path.Combine(WebHostEnvironment.WebRootPath, CONFIG_DIRECTORY_PATH, PIZZA_CONFIG_FILENAME);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1617
PizzaBot/Services/PizzaBalancingService.cs
Normal file
1617
PizzaBot/Services/PizzaBalancingService.cs
Normal file
File diff suppressed because it is too large
Load diff
229
PizzaBot/Services/PizzaDBService.cs
Normal file
229
PizzaBot/Services/PizzaDBService.cs
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
using PizzaBot.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace PizzaBot.Services
|
||||||
|
{
|
||||||
|
class PizzaRequestNameEqualityComparer : IEqualityComparer<PizzaRequest>
|
||||||
|
{
|
||||||
|
public bool Equals(PizzaRequest? x, PizzaRequest? y)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(x, y)) return true;
|
||||||
|
|
||||||
|
if (x == null || y == null) return false;
|
||||||
|
|
||||||
|
if (x.Name == y.Name) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetHashCode([DisallowNull] PizzaRequest obj)
|
||||||
|
{
|
||||||
|
return obj.Name.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PizzaDBService
|
||||||
|
{
|
||||||
|
private readonly PizzaContext _context;
|
||||||
|
private readonly PizzaBalancingService _balancingService;
|
||||||
|
private readonly GlobalStuffService _globalStuffService;
|
||||||
|
|
||||||
|
private PizzaRequestNameEqualityComparer _reqNameEqualityComparer = new PizzaRequestNameEqualityComparer();
|
||||||
|
private Random _rnd = new Random();
|
||||||
|
|
||||||
|
public PizzaDBService(PizzaContext context, PizzaBalancingService balancingService, GlobalStuffService globalStuffService)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_balancingService = balancingService;
|
||||||
|
_globalStuffService = globalStuffService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PizzaRequest? Create(PizzaRequest request, out string ErrorMessage)
|
||||||
|
{
|
||||||
|
ErrorMessage = "";
|
||||||
|
request.Name = request.Name.Trim();
|
||||||
|
//test if orders are closed
|
||||||
|
if (_globalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Orders are locked, you are too late.";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
//test if request is valid
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request was null. If you see this, contact the admin!";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (_context.Requests.AsEnumerable().Contains(request, _reqNameEqualityComparer))
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Request with name {request.Name} already exists. Use a different name!";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (request.Name == null || request.Name == "")
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request needs a name!";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (request.Name.Length > _globalStuffService.GetConfig().NameLength)
|
||||||
|
{
|
||||||
|
ErrorMessage = $"Request name is too long! Max Length: {_globalStuffService.GetConfig().NameLength}";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (request.reqPiecesVegan + request.reqPiecesVegetarian + request.reqPiecesMeat < 1)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request needs to have at least one piece!";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//insert valid request
|
||||||
|
request.Id = _rnd.Next(int.MaxValue);
|
||||||
|
_context.Requests.Add(request);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
_globalStuffService.ShouldBalance = true;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<PizzaRequest> GetAllRequests()
|
||||||
|
{
|
||||||
|
if (_globalStuffService.ShouldBalance)
|
||||||
|
{
|
||||||
|
Balance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context.Requests.OrderBy(r => r.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<PizzaResult> GetAllResults()
|
||||||
|
{
|
||||||
|
if (_globalStuffService.ShouldBalance)
|
||||||
|
{
|
||||||
|
Balance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context.Results.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PizzaResult? GetResultById(int id)
|
||||||
|
{
|
||||||
|
if (_globalStuffService.ShouldBalance)
|
||||||
|
{
|
||||||
|
Balance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context.Results.Find(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PizzaRequest? GetRequestById(int id)
|
||||||
|
{
|
||||||
|
if(_context.Requests.Find(id) == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _context.Requests.Find(id).GetShallowCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteById(int id)
|
||||||
|
{
|
||||||
|
var request = _context.Requests.Find(id);
|
||||||
|
var result = _context.Results.Find(id);
|
||||||
|
|
||||||
|
if (request != null)
|
||||||
|
{
|
||||||
|
_context.Requests.Remove(request);
|
||||||
|
}
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
_context.Results.Remove(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
_globalStuffService.ShouldBalance = true;
|
||||||
|
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UpdateRequest(PizzaRequest request, out string ErrorMessage)
|
||||||
|
{
|
||||||
|
ErrorMessage = "";
|
||||||
|
//test if orders are closed
|
||||||
|
if (_globalStuffService.OrdersLocked)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Orders are locked, you are too late.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//test if request is valid
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request was null. If you see this, contact the admin!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (request.reqPiecesVegan + request.reqPiecesVegetarian + request.reqPiecesMeat < 1)
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request needs to have at least one piece!";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_context.Requests.Remove(_context.Requests.Find(request.Id));
|
||||||
|
_context.Requests.Add(request);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
_globalStuffService.ShouldBalance = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Balance()
|
||||||
|
{
|
||||||
|
Dictionary<int, PizzaRequest> orders = new Dictionary<int, PizzaRequest>();
|
||||||
|
|
||||||
|
foreach (var request in _context.Requests.ToList())
|
||||||
|
{
|
||||||
|
orders.Add(request.Id, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
var balancingResult = _balancingService.Distribute(orders);
|
||||||
|
|
||||||
|
_context.Results.RemoveRange(_context.Results.ToList());
|
||||||
|
_context.Results.AddRange(balancingResult.results.Values);
|
||||||
|
|
||||||
|
_globalStuffService.MeatPizzas = balancingResult.requiredMeat;
|
||||||
|
_globalStuffService.VeggiePizzas = balancingResult.requiredVeggie;
|
||||||
|
_globalStuffService.VeganPizzas = balancingResult.requiredVegan;
|
||||||
|
|
||||||
|
_globalStuffService.TotalCost = balancingResult.totalCost;
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
_globalStuffService.ShouldBalance = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool DeleteAllOrders(string passPhrase)
|
||||||
|
{
|
||||||
|
if (passPhrase == Environment.GetEnvironmentVariable("DELETION_PASSPHRASE"))
|
||||||
|
{
|
||||||
|
_globalStuffService.MeatPizzas = 0;
|
||||||
|
_globalStuffService.VeggiePizzas = 0;
|
||||||
|
_globalStuffService.VeganPizzas = 0;
|
||||||
|
_globalStuffService.TotalCost = 0;
|
||||||
|
|
||||||
|
_context.Requests.RemoveRange(_context.Requests);
|
||||||
|
_context.Results.RemoveRange(_context.Results);
|
||||||
|
_context.SaveChanges();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MarkAsPaid(int id)
|
||||||
|
{
|
||||||
|
_context.Results.Find(id).hasPaid = true;
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
public void MarkAsNotPaid(int id)
|
||||||
|
{
|
||||||
|
_context.Results.Find(id).hasPaid = false;
|
||||||
|
_context.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
583
PizzaBot/Services/old_PizzaBalancingService.cs
Normal file
583
PizzaBot/Services/old_PizzaBalancingService.cs
Normal file
|
|
@ -0,0 +1,583 @@
|
||||||
|
using PizzaBot.Models;
|
||||||
|
|
||||||
|
namespace PizzaBot.Services
|
||||||
|
{
|
||||||
|
public class old_PizzaBalancingService
|
||||||
|
{
|
||||||
|
|
||||||
|
private readonly JSONService _jsonService;
|
||||||
|
public old_PizzaBalancingService(JSONService jsonService)
|
||||||
|
{
|
||||||
|
_jsonService = jsonService;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DistributionPerformance
|
||||||
|
{
|
||||||
|
public float maxPenalty;
|
||||||
|
public float avgPenalty;
|
||||||
|
public float penaltyVariance;
|
||||||
|
public float penaltyStandardDeviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (float penalty, bool isOk) CalculatePenalty(PizzaRequest request, PizzaResult result)
|
||||||
|
{
|
||||||
|
int reqMeat = request.reqPiecesMeat;
|
||||||
|
int reqVeggie = request.reqPiecesVegetarian;
|
||||||
|
int reqVegan = request.reqPiecesVegan;
|
||||||
|
|
||||||
|
int resMeat = result.resPiecesMeat;
|
||||||
|
int resVeggie = result.resPiecesVegetarian;
|
||||||
|
int resVegan = result.resPiecesVegan;
|
||||||
|
|
||||||
|
float categoryToleranceMeatVeggie = request.priority;
|
||||||
|
|
||||||
|
int reqNum = reqMeat + reqVeggie;
|
||||||
|
int resNum = resMeat + resVeggie;
|
||||||
|
|
||||||
|
uint diffNumReqRes = (uint)Math.Abs(reqNum - resNum);
|
||||||
|
|
||||||
|
//result is in 0.01 to sq(diffNum)*catTol*0.99
|
||||||
|
float num_base = 0.01f;
|
||||||
|
//float penaltyCount = (diffNumReqRes * diffNumReqRes) * (categoryToleranceMeatVeggie * (1 - num_base) + num_base);
|
||||||
|
|
||||||
|
|
||||||
|
//float epsilon = 0.001f;
|
||||||
|
//float percentMeatInRequest = reqMeat / (reqNum + epsilon);
|
||||||
|
//float percentMeatInResult = resMeat / (resNum + epsilon);
|
||||||
|
|
||||||
|
//float diffMeatShareResReq = percentMeatInResult - percentMeatInRequest;
|
||||||
|
//float penaltyCat = (diffMeatShareResReq * diffMeatShareResReq) / (categoryToleranceMeatVeggie + epsilon);
|
||||||
|
|
||||||
|
//float f = 0.5f; // fav: 0.0: num, 1.0: cat
|
||||||
|
//float penaltyMeatVeggi = ((1.0f - f) * penaltyCount + f * penaltyCat) / reqNum;
|
||||||
|
|
||||||
|
// ________Vanessa Penalties________
|
||||||
|
float penaltyCount = (Math.Abs(reqNum - resNum)) / (float)reqNum; // 0 - 1
|
||||||
|
// apply function to make higher differences have higher penalty
|
||||||
|
penaltyCount = (float)Math.Pow(penaltyCount, 2);
|
||||||
|
|
||||||
|
float epsilon = 0.001f;
|
||||||
|
float percentMeatInRequest = reqMeat / (reqNum + epsilon); // 0 - 1
|
||||||
|
float percentMeatInResult = resMeat / (resNum + epsilon); // 0 - 1
|
||||||
|
|
||||||
|
float percentVeggiInRequest = reqMeat / (reqNum + epsilon);
|
||||||
|
float percentVeggiInResult = resMeat / (resNum + epsilon);
|
||||||
|
|
||||||
|
float diffMeatShareResReq = Math.Abs(percentMeatInResult - percentMeatInRequest); // 0 - 1
|
||||||
|
float diffVeggiShareResReq = Math.Abs(percentVeggiInResult - percentVeggiInRequest);
|
||||||
|
|
||||||
|
float penaltyCat = (diffMeatShareResReq + diffVeggiShareResReq) / 2; // 0 - 1
|
||||||
|
// apply function to make small differences have higher penalty
|
||||||
|
penaltyCat = (float)Math.Sqrt(penaltyCat);
|
||||||
|
|
||||||
|
|
||||||
|
// take weighted average of both penalties using categoryToleranceMeatVeggie
|
||||||
|
float f = categoryToleranceMeatVeggie; // fav: 0.0: num, 1.0: cat
|
||||||
|
float penaltyMeatVeggi = (1.0f - f) * penaltyCount + f * penaltyCat;
|
||||||
|
// ________Vanessa Penalties________
|
||||||
|
|
||||||
|
|
||||||
|
//float f = 0.5f; // fav: 0.0: num, 1.0: cat
|
||||||
|
//float penaltyMeatVeggi = ((1.0f - f) * penaltyCount + f * penaltyCat) / reqNum;
|
||||||
|
|
||||||
|
//only ok, if at least one piece of each requested type has been assigned
|
||||||
|
bool meatOk = !(reqMeat == 0 && resMeat != 0);
|
||||||
|
bool veggieOk = !(reqVeggie == 0 && resVeggie != 0);
|
||||||
|
|
||||||
|
return (penaltyMeatVeggi, categoryToleranceMeatVeggie != 0.0f || (meatOk && veggieOk));
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Dictionary<int, PizzaResult> results, int requiredMeat, int requiredVeggie, int requiredVegan, float totalCost) Distribute(Dictionary<int, PizzaRequest> orders)
|
||||||
|
{
|
||||||
|
PizzaConfig config = _jsonService.ReadPizzaConfig();
|
||||||
|
//PizzaConfig config = new PizzaConfig();
|
||||||
|
//config.Fragments = 15;
|
||||||
|
//config.Price = 2150;
|
||||||
|
|
||||||
|
|
||||||
|
// In general, cannot determine the leveling strategy at this point.
|
||||||
|
// However, the optimal solution follows one of four strategies:
|
||||||
|
// - drain both
|
||||||
|
// - drain V, fill P
|
||||||
|
// - fill P, drain V
|
||||||
|
// - fill both
|
||||||
|
// So, try all these to find the optimal solution.
|
||||||
|
|
||||||
|
// ppp should be the same for all pizzas of all categories (or?)
|
||||||
|
// however, the user or this function could try getting better-fitting results
|
||||||
|
// by testing other ppp values.
|
||||||
|
// this would probably need floating point requests or distribution factors
|
||||||
|
|
||||||
|
bool compareResults(float oldMaxPenalty, float oldAveragePenalty, float newMaxPenalty, float newAveragePenalty)
|
||||||
|
{
|
||||||
|
float f = 0.1f;
|
||||||
|
|
||||||
|
float oldVal = (1.0f - f) * oldMaxPenalty + oldAveragePenalty;
|
||||||
|
float newVal = (1.0f - f) * newMaxPenalty + newAveragePenalty;
|
||||||
|
|
||||||
|
return newVal < oldVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
var balanced = Balance(orders, config.Fragments, false, false);
|
||||||
|
Console.WriteLine("chose false, false" + balanced);
|
||||||
|
var newBalanced = Balance(orders, config.Fragments, false, true);
|
||||||
|
if (compareResults(balanced.maxPen, balanced.avgPen, newBalanced.maxPen, newBalanced.avgPen))
|
||||||
|
{
|
||||||
|
balanced = newBalanced;
|
||||||
|
Console.WriteLine("chose false, true" + balanced);
|
||||||
|
}
|
||||||
|
newBalanced = Balance(orders, config.Fragments, true, false);
|
||||||
|
if (compareResults(balanced.maxPen, balanced.avgPen, newBalanced.maxPen, newBalanced.avgPen))
|
||||||
|
{
|
||||||
|
balanced = newBalanced;
|
||||||
|
Console.WriteLine("chose true, false" + balanced);
|
||||||
|
|
||||||
|
}
|
||||||
|
newBalanced = Balance(orders, config.Fragments, true, true);
|
||||||
|
if (compareResults(balanced.maxPen, balanced.avgPen, newBalanced.maxPen, newBalanced.avgPen))
|
||||||
|
{
|
||||||
|
balanced = newBalanced;
|
||||||
|
Console.WriteLine("chose true, true" + balanced);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
float p = 1.0f / balanced.balanced.Count;
|
||||||
|
DistributionPerformance performance = new DistributionPerformance();
|
||||||
|
performance.maxPenalty = balanced.maxPen;
|
||||||
|
performance.avgPenalty = balanced.avgPen;
|
||||||
|
foreach (var result in balanced.balanced)
|
||||||
|
{
|
||||||
|
float d = performance.avgPenalty - result.Value.penaltyMeatVeggi;
|
||||||
|
performance.penaltyVariance += d * d * p;
|
||||||
|
}
|
||||||
|
performance.penaltyStandardDeviation = (float)Math.Sqrt(performance.penaltyVariance);
|
||||||
|
|
||||||
|
// set order price, count and verify required fragments
|
||||||
|
int requiredMeat = 0, requiredVeggie = 0, requiredVegan = 0;
|
||||||
|
foreach (var result in balanced.balanced)
|
||||||
|
{
|
||||||
|
p = (result.Value.resPiecesMeat + result.Value.resPiecesVegetarian) * config.Price;
|
||||||
|
p = (p + config.Fragments - 1) / config.Fragments;
|
||||||
|
result.Value.totalCost = p * 0.01f;
|
||||||
|
requiredMeat += result.Value.resPiecesMeat;
|
||||||
|
requiredVeggie += result.Value.resPiecesVegetarian;
|
||||||
|
}
|
||||||
|
if (requiredMeat % config.Fragments != 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Meat fragments don't fit!");
|
||||||
|
}
|
||||||
|
if (requiredVeggie % config.Fragments != 0)
|
||||||
|
{
|
||||||
|
throw new Exception("Veggie fragments don't fit!");
|
||||||
|
}
|
||||||
|
|
||||||
|
requiredMeat /= config.Fragments;
|
||||||
|
requiredVeggie /= config.Fragments;
|
||||||
|
|
||||||
|
float totalCost = (requiredMeat + requiredVeggie + requiredVegan) * config.Price / 100.0f;
|
||||||
|
|
||||||
|
return (balanced.balanced, requiredMeat, requiredVeggie, 0, totalCost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Dictionary<int, PizzaResult> balanced, float maxPen, float avgPen) Balance(Dictionary<int, PizzaRequest> requests, int piecesPerPizza, bool fillMeat, bool fillVeggie)
|
||||||
|
{
|
||||||
|
Dictionary<int, PizzaResult> balanced = new Dictionary<int, PizzaResult>();
|
||||||
|
|
||||||
|
//allocated pieces per category
|
||||||
|
int numMeat = 0, numVeggie = 0, numVegan = 0;
|
||||||
|
|
||||||
|
//fill categories with preferences
|
||||||
|
foreach (var request in requests)
|
||||||
|
{
|
||||||
|
PizzaResult result = new PizzaResult();
|
||||||
|
result.Id = request.Value.Id;
|
||||||
|
result.resPiecesMeat = request.Value.reqPiecesMeat;
|
||||||
|
result.resPiecesVegetarian = request.Value.reqPiecesVegetarian;
|
||||||
|
result.resPiecesVegan = request.Value.reqPiecesVegan;
|
||||||
|
|
||||||
|
numMeat += request.Value.reqPiecesMeat;
|
||||||
|
numVeggie += request.Value.reqPiecesVegetarian;
|
||||||
|
numVegan += request.Value.reqPiecesVegan;
|
||||||
|
|
||||||
|
balanced.Add(request.Value.Id, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//determine next viable pizza order with balancing strategy
|
||||||
|
int requiredMeatPizzas = numMeat / piecesPerPizza;
|
||||||
|
int requiredVeggiePizzas = numVeggie / piecesPerPizza;
|
||||||
|
int requiredVeganPizzas = numVegan / piecesPerPizza;
|
||||||
|
|
||||||
|
if (fillMeat)
|
||||||
|
{
|
||||||
|
requiredMeatPizzas++;
|
||||||
|
}
|
||||||
|
if (fillVeggie)
|
||||||
|
{
|
||||||
|
requiredVeggiePizzas++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//determine delta of pieces to next viable pizza order
|
||||||
|
int deltaMeat = numMeat - requiredMeatPizzas * piecesPerPizza;
|
||||||
|
int deltaVeggie = numVeggie - requiredVeggiePizzas * piecesPerPizza;
|
||||||
|
|
||||||
|
//-----tuxic-----
|
||||||
|
// balance using provided strategy
|
||||||
|
// this can be done greedily, advancing to a balanced state,
|
||||||
|
// if the advancement per operation is constant.
|
||||||
|
// there are 2 basic operations:
|
||||||
|
// - compress/expand P/V (advance: 1)
|
||||||
|
// - move right/left (P->V / V->P) (advance: 0 or 2)
|
||||||
|
// since we want to minimize the maximum penalty any (not every) one has to pay,
|
||||||
|
// the cheapest/best operation is the operation that results in the lowest penalty
|
||||||
|
// when draining both:
|
||||||
|
// loop (best) compress until one category fits (advance: 1)
|
||||||
|
// loop select (best) of (advance: 1):
|
||||||
|
// - (best) compress of overfull category
|
||||||
|
// - (best) compress of fitting category and (best) move from overfull to fitting
|
||||||
|
// when filling both:
|
||||||
|
// loop (best) expand until one category fits (advance: 1)
|
||||||
|
// loop select (best) of (advance: 1):
|
||||||
|
// - (best) expand of underfull category
|
||||||
|
// - (best) expand of fitting category and (best) move from fitting to underfull
|
||||||
|
// when draining D and filling F:
|
||||||
|
// // level operations
|
||||||
|
// loop select (best) of until one fits (advance: 2, but max. one on each side):
|
||||||
|
// - [A](best) compress D and (best) expand F
|
||||||
|
// - [B](best) move from D to F
|
||||||
|
// // drain/fill other
|
||||||
|
// either if (D fits):
|
||||||
|
// - loop select (best) of (advance: 1):
|
||||||
|
// - [C](best) expand F
|
||||||
|
// - [D](best) move from D to F and (best) expand D
|
||||||
|
// or if (F fits):
|
||||||
|
// - loop select (best) of (advance: 1):
|
||||||
|
// - [E](best) compress D
|
||||||
|
// - [F](best) move from D to F and (best) compress F
|
||||||
|
// since [A] = [C] + [E] and [B] = [C] + [F] = [D] + [E] and [B] (^=, but simpler than) [D] + [F],
|
||||||
|
// and so [A] and [B] are preferable to [C], [D], [E], [F],
|
||||||
|
// this should yield the optimal solution.
|
||||||
|
//-----tuxic-----
|
||||||
|
|
||||||
|
if (!fillMeat && !fillVeggie)
|
||||||
|
{
|
||||||
|
while (!(deltaMeat == 0 || deltaVeggie == 0))
|
||||||
|
{
|
||||||
|
// (best) compress any category
|
||||||
|
var scale = Scale(requests, ref balanced, false, true, true, false);
|
||||||
|
deltaMeat += scale.deltaMeat;
|
||||||
|
deltaVeggie += scale.deltaVeggie;
|
||||||
|
|
||||||
|
if (scale.deltaMeat == 0 && scale.deltaVeggie == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!(deltaMeat == 0 && deltaVeggie == 0))
|
||||||
|
{
|
||||||
|
// (best) compress !fitting, allow deferred
|
||||||
|
var scale = Scale(requests, ref balanced, false, deltaMeat != 0, deltaVeggie != 0, true);
|
||||||
|
deltaMeat += scale.deltaMeat;
|
||||||
|
deltaVeggie += scale.deltaVeggie;
|
||||||
|
|
||||||
|
if (scale.deltaMeat == 0 && scale.deltaVeggie == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (fillMeat && fillVeggie)
|
||||||
|
{
|
||||||
|
while (!(deltaMeat == 0 || deltaVeggie == 0))
|
||||||
|
{
|
||||||
|
// (best) expand any category
|
||||||
|
var scale = Scale(requests, ref balanced, true, true, true, false);
|
||||||
|
deltaMeat += scale.deltaMeat;
|
||||||
|
deltaVeggie += scale.deltaVeggie;
|
||||||
|
|
||||||
|
if (scale.deltaMeat == 0 && scale.deltaVeggie == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!(deltaMeat == 0 && deltaVeggie == 0))
|
||||||
|
{
|
||||||
|
// (best) expand !fitting, allow deferred
|
||||||
|
var scale = Scale(requests, ref balanced, true, deltaMeat != 0, deltaVeggie != 0, true);
|
||||||
|
deltaMeat += scale.deltaMeat;
|
||||||
|
deltaVeggie += scale.deltaVeggie;
|
||||||
|
|
||||||
|
if (scale.deltaMeat == 0 && scale.deltaVeggie == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (!(deltaMeat == 0 || deltaVeggie == 0))
|
||||||
|
{
|
||||||
|
var shift = Shift(requests, ref balanced, fillVeggie, true);
|
||||||
|
if (shift.bestPenalty == float.MaxValue)
|
||||||
|
{
|
||||||
|
return (null, shift.bestPenalty, shift.bestPenalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaMeat += shift.deltaMeat;
|
||||||
|
deltaVeggie += shift.deltaVeggie;
|
||||||
|
|
||||||
|
if (shift.deltaMeat == 0 && shift.deltaVeggie == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int deltaFill = fillMeat ? deltaMeat : deltaVeggie;
|
||||||
|
bool fillFits = deltaFill == 0;
|
||||||
|
while (!(deltaMeat == 0 && deltaVeggie == 0))
|
||||||
|
{
|
||||||
|
var scale = Scale(requests, ref balanced, !fillFits, deltaMeat != 0, deltaVeggie != 0, true);
|
||||||
|
if (scale.bestPenalty == float.MaxValue)
|
||||||
|
{
|
||||||
|
return (null, scale.bestPenalty, scale.bestPenalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaMeat += scale.deltaMeat;
|
||||||
|
deltaVeggie += scale.deltaVeggie;
|
||||||
|
|
||||||
|
if (scale.deltaMeat == 0 && scale.deltaVeggie == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float num = balanced.Count;
|
||||||
|
float avgPenalty = 0;
|
||||||
|
float maxPenalty = float.MinValue;
|
||||||
|
foreach (var result in balanced)
|
||||||
|
{
|
||||||
|
var penaltyVal = CalculatePenalty(requests[result.Key], result.Value);
|
||||||
|
result.Value.penaltyMeatVeggi = penaltyVal.penalty;
|
||||||
|
|
||||||
|
avgPenalty += penaltyVal.penalty / num;
|
||||||
|
if (penaltyVal.penalty > maxPenalty)
|
||||||
|
{
|
||||||
|
maxPenalty = penaltyVal.penalty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (balanced, maxPenalty, avgPenalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Dictionary<int, PizzaResult> DuplicatePizzaResultDict(Dictionary<int, PizzaResult> original)
|
||||||
|
{
|
||||||
|
Dictionary<int, PizzaResult> duplicate = new Dictionary<int, PizzaResult>();
|
||||||
|
foreach (var result in original)
|
||||||
|
{
|
||||||
|
var resultCopy = new PizzaResult();
|
||||||
|
resultCopy.Id = result.Value.Id;
|
||||||
|
resultCopy.resPiecesMeat = result.Value.resPiecesMeat;
|
||||||
|
resultCopy.resPiecesVegetarian = result.Value.resPiecesVegetarian;
|
||||||
|
resultCopy.resPiecesVegan = result.Value.resPiecesVegan;
|
||||||
|
resultCopy.hasPaid = result.Value.hasPaid;
|
||||||
|
resultCopy.totalCost = result.Value.totalCost;
|
||||||
|
resultCopy.penaltyMeatVeggi = result.Value.penaltyMeatVeggi;
|
||||||
|
resultCopy.penaltyVeggieVegan = result.Value.penaltyVeggieVegan;
|
||||||
|
|
||||||
|
duplicate.Add(result.Key, resultCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return duplicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
//balance operations
|
||||||
|
|
||||||
|
//-----tuxic-----
|
||||||
|
// try improving balance using on compression/expansion of allowed categories (p/v),
|
||||||
|
// may allow defering the operation to the other category and moving over.
|
||||||
|
// return deltas (both zero if failed)
|
||||||
|
// -----tuxic-----
|
||||||
|
private (int deltaMeat, int deltaVeggie, float bestPenalty) Scale(Dictionary<int, PizzaRequest> requests, ref Dictionary<int, PizzaResult> resultsIn, bool expand, bool meat, bool veggie, bool allow_deferred)
|
||||||
|
{
|
||||||
|
var results = DuplicatePizzaResultDict(resultsIn);
|
||||||
|
|
||||||
|
int bestId = -1;
|
||||||
|
float bestPenalty = float.MaxValue;
|
||||||
|
PizzaResult bestResult = null;
|
||||||
|
|
||||||
|
int delta = expand ? 1 : -1;
|
||||||
|
|
||||||
|
int deltaMeat = 0, deltaVeggie = 0;
|
||||||
|
|
||||||
|
//adds delta to result of every order
|
||||||
|
if (meat)
|
||||||
|
{
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
int value = result.Value.resPiecesMeat + delta;
|
||||||
|
if (value >= 0)
|
||||||
|
{
|
||||||
|
result.Value.resPiecesMeat = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var penResult = CalculatePenalty(requests[result.Key], result.Value);
|
||||||
|
if (penResult.isOk && penResult.penalty < bestPenalty)
|
||||||
|
{
|
||||||
|
bestId = result.Key;
|
||||||
|
bestPenalty = penResult.penalty;
|
||||||
|
bestResult = result.Value;
|
||||||
|
|
||||||
|
deltaMeat = delta;
|
||||||
|
deltaVeggie = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results = DuplicatePizzaResultDict(resultsIn);
|
||||||
|
//adds delta to result of every order
|
||||||
|
if (veggie)
|
||||||
|
{
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
int value = result.Value.resPiecesVegetarian + delta;
|
||||||
|
if (value >= 0)
|
||||||
|
{
|
||||||
|
result.Value.resPiecesVegetarian = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var penResult = CalculatePenalty(requests[result.Key], result.Value);
|
||||||
|
if (penResult.isOk && penResult.penalty < bestPenalty)
|
||||||
|
{
|
||||||
|
bestId = result.Key;
|
||||||
|
bestPenalty = penResult.penalty;
|
||||||
|
bestResult = result.Value;
|
||||||
|
|
||||||
|
deltaMeat = 0;
|
||||||
|
deltaVeggie = delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (allow_deferred && (meat != veggie))
|
||||||
|
{
|
||||||
|
//duplicate results
|
||||||
|
Dictionary<int, PizzaResult> duplicate = DuplicatePizzaResultDict(resultsIn);
|
||||||
|
|
||||||
|
//find best move
|
||||||
|
var move = Shift(requests, ref duplicate, meat != expand, false);
|
||||||
|
//find best scale
|
||||||
|
var scale = Scale(requests, ref duplicate, expand, !meat, !veggie, false);
|
||||||
|
|
||||||
|
float maxPenalty = Math.Max(move.bestPenalty, scale.bestPenalty);
|
||||||
|
|
||||||
|
if (maxPenalty < bestPenalty)
|
||||||
|
{
|
||||||
|
deltaMeat = move.deltaMeat + scale.deltaMeat;
|
||||||
|
deltaVeggie = move.deltaMeat + scale.deltaVeggie;
|
||||||
|
bestPenalty = maxPenalty;
|
||||||
|
resultsIn = duplicate;
|
||||||
|
}
|
||||||
|
else if (bestId >= 0)
|
||||||
|
{
|
||||||
|
resultsIn[bestId] = bestResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (bestId >= 0)
|
||||||
|
{
|
||||||
|
resultsIn[bestId] = bestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (deltaMeat, deltaVeggie, bestPenalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int deltaMeat, int deltaVeggie, float bestPenalty) Shift(Dictionary<int, PizzaRequest> requests, ref Dictionary<int, PizzaResult> resultsIn, bool toVeggie, bool allowScaling)
|
||||||
|
{
|
||||||
|
var results = DuplicatePizzaResultDict(resultsIn);
|
||||||
|
|
||||||
|
int bestId = -1;
|
||||||
|
float bestPenalty = float.MaxValue;
|
||||||
|
PizzaResult bestResult = null;
|
||||||
|
|
||||||
|
int delta = toVeggie ? -1 : 1;
|
||||||
|
|
||||||
|
int deltaMeat = 0, deltaVeggie = 0;
|
||||||
|
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
int value = result.Value.resPiecesMeat + delta;
|
||||||
|
if (value >= 0)
|
||||||
|
{
|
||||||
|
result.Value.resPiecesMeat = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = result.Value.resPiecesVegetarian - delta;
|
||||||
|
if (value >= 0)
|
||||||
|
{
|
||||||
|
result.Value.resPiecesVegetarian = value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var penResult = CalculatePenalty(requests[result.Key], result.Value);
|
||||||
|
if (penResult.isOk && penResult.penalty < bestPenalty)
|
||||||
|
{
|
||||||
|
bestId = result.Key;
|
||||||
|
bestPenalty = penResult.penalty;
|
||||||
|
bestResult = result.Value;
|
||||||
|
|
||||||
|
deltaMeat = delta;
|
||||||
|
deltaVeggie = -delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowScaling)
|
||||||
|
{
|
||||||
|
Dictionary<int, PizzaResult> duplicate = DuplicatePizzaResultDict(resultsIn);
|
||||||
|
|
||||||
|
//find best compress
|
||||||
|
var compress = Scale(requests, ref duplicate, false, toVeggie, !toVeggie, false);
|
||||||
|
//find best expand
|
||||||
|
var expand = Scale(requests, ref duplicate, true, !toVeggie, toVeggie, false);
|
||||||
|
|
||||||
|
|
||||||
|
// cannot be on the same order (id), or it would not be better than the non-deferred scale
|
||||||
|
float maxPen = Math.Max(compress.bestPenalty, expand.bestPenalty);
|
||||||
|
|
||||||
|
if (maxPen < bestPenalty)
|
||||||
|
{
|
||||||
|
deltaMeat = compress.deltaMeat + expand.deltaMeat;
|
||||||
|
deltaVeggie = compress.deltaVeggie + expand.deltaVeggie;
|
||||||
|
bestPenalty = maxPen;
|
||||||
|
resultsIn = duplicate;
|
||||||
|
}
|
||||||
|
else if (bestId >= 0)
|
||||||
|
{
|
||||||
|
resultsIn[bestId] = bestResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (bestId >= 0)
|
||||||
|
{
|
||||||
|
resultsIn[bestId] = bestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (deltaMeat, deltaVeggie, bestPenalty);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
8
PizzaBot/appsettings.Development.json
Normal file
8
PizzaBot/appsettings.Development.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
PizzaBot/appsettings.json
Normal file
10
PizzaBot/appsettings.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"Urls": "http://*:8080"
|
||||||
|
}
|
||||||
51
PizzaBot/wwwroot/app.css
Normal file
51
PizzaBot/wwwroot/app.css
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
html, body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, .btn-link {
|
||||||
|
color: #006bb7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||||
|
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding-top: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valid.modified:not([type=checkbox]) {
|
||||||
|
outline: 1px solid #26b050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid {
|
||||||
|
outline: 1px solid #e50000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-message {
|
||||||
|
color: #e50000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blazor-error-boundary {
|
||||||
|
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||||
|
padding: 1rem 1rem 1rem 3.7rem;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blazor-error-boundary::after {
|
||||||
|
content: "An error has occurred."
|
||||||
|
}
|
||||||
|
|
||||||
|
.darker-border-checkbox.form-check-input {
|
||||||
|
border-color: #929292;
|
||||||
|
}
|
||||||
7
PizzaBot/wwwroot/bootstrap/bootstrap.min.css
vendored
Normal file
7
PizzaBot/wwwroot/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
PizzaBot/wwwroot/bootstrap/bootstrap.min.css.map
Normal file
1
PizzaBot/wwwroot/bootstrap/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
1
PizzaBot/wwwroot/config/pizza.config
Normal file
1
PizzaBot/wwwroot/config/pizza.config
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
{"SizeX":0.6,"SizeY":0.4,"Price":2250,"Fragments":15,"Toppings":4,"NameLength":40,"PenaltyType":1}
|
||||||
BIN
PizzaBot/wwwroot/favicon.png
Normal file
BIN
PizzaBot/wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
1
README.md
Normal file
1
README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# TestBlazorApp
|
||||||
1
TestDatabase/.gitignore
vendored
Normal file
1
TestDatabase/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
database/
|
||||||
14
TestDatabase/docker-compose.yml
Normal file
14
TestDatabase/docker-compose.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: mysql
|
||||||
|
volumes:
|
||||||
|
- ./database:/var/lib/mysql
|
||||||
|
- ./setup.sql:/docker-entrypoint-initdb.d/1.sql
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: QEwqTdS#7pxEy#
|
||||||
|
MYSQL_USER: PizzaBotDev
|
||||||
|
MYSQL_PASSWORD: PizzaDeliveryEverywhere!
|
||||||
|
MYSQL_DATABASE: PizzaBotTest
|
||||||
17
TestDatabase/setup.sql
Normal file
17
TestDatabase/setup.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
CREATE TABLE PizzaBotTest.Requests (
|
||||||
|
Id INT PRIMARY KEY,
|
||||||
|
Name LONGTEXT NOT NULL,
|
||||||
|
reqPiecesMeat INT NOT NULL,
|
||||||
|
reqPiecesVegetarian INT NOT NULL,
|
||||||
|
reqPiecesVegan INT NOT NULL,
|
||||||
|
priority FLOAT);
|
||||||
|
|
||||||
|
CREATE TABLE PizzaBotTest.Results (
|
||||||
|
Id INT PRIMARY KEY,
|
||||||
|
resPiecesMeat INT NOT NULL,
|
||||||
|
resPiecesVegetarian INT NOT NULL,
|
||||||
|
resPiecesVegan INT NOT NULL,
|
||||||
|
penaltyMeatVeggi FLOAT NOT NULL,
|
||||||
|
penaltyVeggieVegan FLOAT NOT NULL,
|
||||||
|
totalCost FLOAT NOT NULL,
|
||||||
|
hasPaid TINYINT NOT NULL);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue