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