Compare commits
No commits in common. "main" and "0.2.0" have entirely different histories.
109 changed files with 3245 additions and 8397 deletions
4
.env
4
.env
|
@ -28,7 +28,3 @@ DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
|
||||||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||||
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
# DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||||
###< doctrine/doctrine-bundle ###
|
###< doctrine/doctrine-bundle ###
|
||||||
|
|
||||||
###> nelmio/cors-bundle ###
|
|
||||||
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
|
|
||||||
###< nelmio/cors-bundle ###
|
|
||||||
|
|
4
.env.dev
4
.env.dev
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
|
||||||
APP_SECRET=11c8937d48993fb3aee1a476413161f5
|
|
||||||
###< symfony/framework-bundle ###
|
|
|
@ -4,3 +4,4 @@ APP_SECRET='$ecretf0rt3st'
|
||||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||||
PANTHER_APP_ENV=panther
|
PANTHER_APP_ENV=panther
|
||||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
||||||
|
DATABASE_URL="sqlite:///%kernel.project_dir%/var/test-data.db"
|
||||||
|
|
|
@ -3,7 +3,7 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
image: git.php.fail/lubiana/container/php:8.4.1-ci
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
|
@ -23,7 +23,19 @@ jobs:
|
||||||
- name: lint
|
- name: lint
|
||||||
run: composer lint
|
run: composer lint
|
||||||
- name: test
|
- name: test
|
||||||
run: composer test
|
run: composer mutation
|
||||||
|
- name: Add comment to pull request
|
||||||
|
run: |
|
||||||
|
echo '```' >> /tmp/pull-request-comment
|
||||||
|
cat var/log/infection.txt >> /tmp/pull-request-comment
|
||||||
|
cat var/log/summary.log >> /tmp/pull-request-comment
|
||||||
|
echo '```' >> /tmp/pull-request-comment
|
||||||
|
jq -n --arg msg "$(cat /tmp/pull-request-comment)" '{"body": $msg}' > /tmp/git-msg
|
||||||
|
curl -X POST \
|
||||||
|
-H "Authorization: token ${GITHUB_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @/tmp/git-msg \
|
||||||
|
"${{ env.GITHUB_SERVER_URL }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments"
|
||||||
- name: GIT commit and push all changed files
|
- name: GIT commit and push all changed files
|
||||||
env:
|
env:
|
||||||
CI_COMMIT_MESSAGE: Continuous Integration Fixes
|
CI_COMMIT_MESSAGE: Continuous Integration Fixes
|
||||||
|
|
|
@ -6,7 +6,7 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
image: git.php.fail/lubiana/container/php:8.4.1-ci
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
|
@ -27,7 +27,7 @@ jobs:
|
||||||
- name: lint
|
- name: lint
|
||||||
run: composer lint
|
run: composer lint
|
||||||
- name: test
|
- name: test
|
||||||
run: composer test
|
run: composer mutation
|
||||||
- name: GIT commit and push all changed files
|
- name: GIT commit and push all changed files
|
||||||
env:
|
env:
|
||||||
CI_COMMIT_MESSAGE: Continuous Integration Fixes
|
CI_COMMIT_MESSAGE: Continuous Integration Fixes
|
||||||
|
|
|
@ -4,7 +4,7 @@ jobs:
|
||||||
ls:
|
ls:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: git.php.fail/lubiana/container/php:8.4.8-ci
|
image: git.php.fail/lubiana/container/php:8.4.1-ci
|
||||||
steps:
|
steps:
|
||||||
- name: Manually checkout
|
- name: Manually checkout
|
||||||
env:
|
env:
|
||||||
|
|
33
.gitignore
vendored
33
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
###> symfony/framework-bundle ###
|
###> symfony/framework-bundle ###
|
||||||
/.env.local
|
/.env.local
|
||||||
/.env.local.php
|
/.env.local.php
|
||||||
|
@ -7,27 +8,17 @@
|
||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
||||||
|
###> symfony/phpunit-bridge ###
|
||||||
|
.phpunit.result.cache
|
||||||
|
/phpunit.xml
|
||||||
|
###< symfony/phpunit-bridge ###
|
||||||
|
|
||||||
|
###> phpunit/phpunit ###
|
||||||
|
/phpunit.xml
|
||||||
|
.phpunit.result.cache
|
||||||
|
###< phpunit/phpunit ###
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
/deploy/var/
|
/deploy/var/
|
||||||
/deploy/app/
|
/deploy/app/
|
||||||
|
|
||||||
###> phpunit/phpunit ###
|
|
||||||
.phpunit.result.cache
|
|
||||||
.phpunit.cache
|
|
||||||
###< phpunit/phpunit ###
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
|
|
||||||
###> squizlabs/php_codesniffer ###
|
|
||||||
/.phpcs-cache
|
|
||||||
/phpcs.xml
|
|
||||||
###< squizlabs/php_codesniffer ###
|
|
||||||
|
|
||||||
###> phpstan/phpstan ###
|
|
||||||
phpstan.neon
|
|
||||||
###< phpstan/phpstan ###
|
|
||||||
|
|
||||||
###> symfony/asset-mapper ###
|
|
||||||
/public/assets/
|
|
||||||
/assets/vendor/
|
|
||||||
###< symfony/asset-mapper ###
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Welcome to your app's main JavaScript file!
|
|
||||||
*
|
|
||||||
* This file will be included onto the page via the importmap() Twig function,
|
|
||||||
* which should already be in your base.html.twig.
|
|
||||||
*/
|
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
||||||
import './styles/app.css';
|
|
||||||
import './styles/modes.css';
|
|
||||||
import './styles/emoji-footprint.css';
|
|
||||||
|
|
||||||
// Import modules
|
|
||||||
import './javascript/theme.js';
|
|
||||||
import './javascript/emoji-footprint.js';
|
|
||||||
import './javascript/modes.js';
|
|
||||||
import './javascript/htmx.js';
|
|
||||||
import emojiButtonListener from './javascript/emoji-button.js';
|
|
||||||
import 'bootstrap';
|
|
||||||
import { initRadioState } from './javascript/radioState.js';
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
initRadioState();
|
|
||||||
emojiButtonListener();
|
|
||||||
});
|
|
|
@ -1,14 +0,0 @@
|
||||||
const emojiButtonListener = function () {
|
|
||||||
const buttons = document.querySelectorAll('.emoji-buttons .btn.btn-primary');
|
|
||||||
|
|
||||||
buttons.forEach(button => {
|
|
||||||
button.addEventListener('click', function() {
|
|
||||||
const emojiField = document.querySelector('#food_vendor_emojis');
|
|
||||||
if (emojiField) {
|
|
||||||
emojiField.value += this.textContent;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default emojiButtonListener;
|
|
|
@ -1,19 +0,0 @@
|
||||||
// Sparkle effect on mouse move
|
|
||||||
document.addEventListener('mousemove', function (e) {
|
|
||||||
const emojis = ['✨', '💖', '🌟', '💅', '🦄', '🎉', '🌈'];
|
|
||||||
const sparkle = document.createElement('div');
|
|
||||||
sparkle.className = 'emoji-footprint';
|
|
||||||
sparkle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
|
|
||||||
sparkle.style.left = e.pageX + 'px';
|
|
||||||
sparkle.style.top = e.pageY + 'px';
|
|
||||||
document.body.appendChild(sparkle);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
sparkle.remove();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function initEmojiFootprint() {
|
|
||||||
// The sparkle effect is already initialized when this module is imported
|
|
||||||
// This function can be used if we need to control when the effect starts
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
import htmx from 'htmx.org';
|
|
||||||
|
|
||||||
window.htmx = htmx;
|
|
|
@ -1,136 +0,0 @@
|
||||||
// Bonkers mode functionality
|
|
||||||
function setEmojiLevelClass(mode) {
|
|
||||||
document.body.classList.remove('emoji-normal', 'emoji-enhanced', 'emoji-bonkers');
|
|
||||||
if (mode === 'bonkers') {
|
|
||||||
document.body.classList.add('emoji-bonkers');
|
|
||||||
} else if (mode === 'enhanced') {
|
|
||||||
document.body.classList.add('emoji-enhanced');
|
|
||||||
} else {
|
|
||||||
document.body.classList.add('emoji-normal');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initBonkersMode() {
|
|
||||||
// Check if we're in bonkers mode
|
|
||||||
const currentMode = document.documentElement.getAttribute('data-website-mode');
|
|
||||||
setEmojiLevelClass(currentMode);
|
|
||||||
|
|
||||||
if (currentMode === 'bonkers') {
|
|
||||||
// Apply bonkers mode immediately
|
|
||||||
document.body.classList.add('bonkers-mode');
|
|
||||||
|
|
||||||
// Start the fabulous effects
|
|
||||||
createExtraSparkles();
|
|
||||||
createSlayEffects();
|
|
||||||
|
|
||||||
console.log('🌈✨ Bonkers mode activated! ✨🌈');
|
|
||||||
} else {
|
|
||||||
// Remove bonkers mode if it was active
|
|
||||||
document.body.classList.remove('bonkers-mode');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create extra sparkles during bonkers mode
|
|
||||||
function createExtraSparkles() {
|
|
||||||
const currentMode = document.documentElement.getAttribute('data-website-mode');
|
|
||||||
if (currentMode !== 'bonkers') return;
|
|
||||||
|
|
||||||
const extraEmojis = [
|
|
||||||
'💃', '🕺',
|
|
||||||
'🍑', '💦', '😏', '😈', '👅', '💋', '🥵', '😳', '🤤', '😍', '🥴',
|
|
||||||
'💕', '💖', '💗', '💘', '💝', '💞', '💟', '💌', '💏', '💑',
|
|
||||||
'🍆', '🥒', '🍌', '💦', '👀', '😉', '😌', '😍', '🥰', '😘',
|
|
||||||
'😚', '😋', '😏', '😫', '😩', '🥺', '🥵', '🥴',
|
|
||||||
'💖', '💗', '💕', '💞', '💓', '💗', '💖', '💘', '💝',
|
|
||||||
'💋', '💏', '💑'
|
|
||||||
];
|
|
||||||
const sparkle = document.createElement('div');
|
|
||||||
sparkle.className = 'emoji-footprint';
|
|
||||||
sparkle.textContent = extraEmojis[Math.floor(Math.random() * extraEmojis.length)];
|
|
||||||
sparkle.style.left = Math.random() * window.innerWidth + 'px';
|
|
||||||
sparkle.style.top = Math.random() * window.innerHeight + 'px';
|
|
||||||
document.body.appendChild(sparkle);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (sparkle.parentNode) {
|
|
||||||
sparkle.remove();
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
// Continue creating extra sparkles while in bonkers mode
|
|
||||||
const newMode = document.documentElement.getAttribute('data-website-mode');
|
|
||||||
if (newMode === 'bonkers') {
|
|
||||||
setTimeout(() => createExtraSparkles(), 150);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create slay effects
|
|
||||||
function createSlayEffects() {
|
|
||||||
const currentMode = document.documentElement.getAttribute('data-website-mode');
|
|
||||||
if (currentMode !== 'bonkers') return;
|
|
||||||
|
|
||||||
// Create floating "SLAY" text effects
|
|
||||||
const slayWords = [
|
|
||||||
'SLAY', 'QUEEN', 'FABULOUS', 'ICONIC', 'LEGENDARY', 'STUNNING', 'GORGEOUS', 'FLAWLESS',
|
|
||||||
'DAZZLING', 'RADIANT', 'BREATHTAKING', 'EXQUISITE', 'DIVINE'
|
|
||||||
];
|
|
||||||
const slayElement = document.createElement('div');
|
|
||||||
slayElement.className = 'slay-text';
|
|
||||||
slayElement.textContent = slayWords[Math.floor(Math.random() * slayWords.length)];
|
|
||||||
slayElement.style.left = Math.random() * window.innerWidth + 'px';
|
|
||||||
slayElement.style.top = Math.random() * window.innerHeight + 'px';
|
|
||||||
document.body.appendChild(slayElement);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (slayElement.parentNode) {
|
|
||||||
slayElement.remove();
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
// Continue creating slay effects while in bonkers mode
|
|
||||||
const newMode = document.documentElement.getAttribute('data-website-mode');
|
|
||||||
if (newMode === 'bonkers') {
|
|
||||||
setTimeout(() => createSlayEffects(), 800);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for mode changes
|
|
||||||
function watchModeChanges() {
|
|
||||||
// Create a MutationObserver to watch for changes to the data-website-mode attribute
|
|
||||||
const observer = new MutationObserver(function(mutations) {
|
|
||||||
mutations.forEach(function(mutation) {
|
|
||||||
if (mutation.type === 'attributes' && mutation.attributeName === 'data-website-mode') {
|
|
||||||
const newMode = document.documentElement.getAttribute('data-website-mode');
|
|
||||||
|
|
||||||
if (newMode === 'bonkers') {
|
|
||||||
document.body.classList.add('bonkers-mode');
|
|
||||||
setEmojiLevelClass(newMode);
|
|
||||||
|
|
||||||
// Start the fabulous effects
|
|
||||||
createExtraSparkles();
|
|
||||||
createSlayEffects();
|
|
||||||
|
|
||||||
console.log('🌈✨ Switched to bonkers mode! ✨🌈');
|
|
||||||
} else {
|
|
||||||
document.body.classList.remove('bonkers-mode');
|
|
||||||
setEmojiLevelClass(newMode);
|
|
||||||
console.log(`😴 Switched to ${newMode} mode`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start observing
|
|
||||||
observer.observe(document.documentElement, {
|
|
||||||
attributes: true,
|
|
||||||
attributeFilter: ['data-website-mode']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize when DOM is loaded
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
initBonkersMode();
|
|
||||||
watchModeChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
export { initBonkersMode, watchModeChanges };
|
|
|
@ -1,55 +0,0 @@
|
||||||
// Function to initialize number input buttons
|
|
||||||
function initNumberInputs(container = document) {
|
|
||||||
container.querySelectorAll('.number-input-wrapper').forEach(function(wrapper) {
|
|
||||||
const input = wrapper.querySelector('input[type="number"]');
|
|
||||||
const decreaseBtn = wrapper.querySelector('[data-action="decrease"]');
|
|
||||||
const increaseBtn = wrapper.querySelector('[data-action="increase"]');
|
|
||||||
|
|
||||||
if (!input || !decreaseBtn || !increaseBtn) return;
|
|
||||||
|
|
||||||
// Skip if already initialized
|
|
||||||
if (decreaseBtn.hasAttribute('data-initialized')) return;
|
|
||||||
|
|
||||||
const step = parseFloat(input.getAttribute('step')) || 1;
|
|
||||||
const min = 0;
|
|
||||||
const max = input.getAttribute('max') ? parseFloat(input.getAttribute('max')) : null;
|
|
||||||
|
|
||||||
decreaseBtn.addEventListener('click', function() {
|
|
||||||
const currentValue = parseFloat(input.value) || 0;
|
|
||||||
const newValue = currentValue - step;
|
|
||||||
|
|
||||||
if (min === null || newValue >= min) {
|
|
||||||
input.value = newValue;
|
|
||||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
increaseBtn.addEventListener('click', function() {
|
|
||||||
const currentValue = parseFloat(input.value) || 0;
|
|
||||||
const newValue = currentValue + step;
|
|
||||||
|
|
||||||
if (max === null || newValue <= max) {
|
|
||||||
input.value = newValue;
|
|
||||||
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate input on change
|
|
||||||
input.addEventListener('input', function() {
|
|
||||||
const value = parseFloat(this.value);
|
|
||||||
|
|
||||||
if (min !== null && value < min) {
|
|
||||||
this.value = min;
|
|
||||||
}
|
|
||||||
if (max !== null && value > max) {
|
|
||||||
this.value = max;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mark as initialized
|
|
||||||
decreaseBtn.setAttribute('data-initialized', 'true');
|
|
||||||
increaseBtn.setAttribute('data-initialized', 'true');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { initNumberInputs };
|
|
|
@ -1,35 +0,0 @@
|
||||||
// Radio button state management with localStorage
|
|
||||||
function initRadioState() {
|
|
||||||
// Store and retrieve radio button state
|
|
||||||
const radioButtons = document.querySelectorAll('input[name="mode"]');
|
|
||||||
|
|
||||||
// Load saved state on page load
|
|
||||||
const savedMode = localStorage.getItem('selectedMode');
|
|
||||||
if (savedMode) {
|
|
||||||
const radioToCheck = document.getElementById(savedMode);
|
|
||||||
if (radioToCheck) {
|
|
||||||
radioToCheck.checked = true;
|
|
||||||
// Set the data attribute to match the saved mode
|
|
||||||
document.documentElement.setAttribute('data-website-mode', savedMode);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If no saved state, set to the currently checked radio button
|
|
||||||
const checkedRadio = document.querySelector('input[name="mode"]:checked');
|
|
||||||
if (checkedRadio) {
|
|
||||||
document.documentElement.setAttribute('data-website-mode', checkedRadio.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save state when radio button changes
|
|
||||||
radioButtons.forEach(radio => {
|
|
||||||
radio.addEventListener('change', function() {
|
|
||||||
if (this.checked) {
|
|
||||||
localStorage.setItem('selectedMode', this.id);
|
|
||||||
// Update the data attribute when mode changes
|
|
||||||
document.documentElement.setAttribute('data-website-mode', this.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export { initRadioState };
|
|
|
@ -1,18 +0,0 @@
|
||||||
// Theme detection and switching
|
|
||||||
const getPreferredTheme = () => {
|
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
||||||
}
|
|
||||||
|
|
||||||
const setTheme = theme => {
|
|
||||||
document.documentElement.setAttribute('data-bs-theme', theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set initial theme
|
|
||||||
setTheme(getPreferredTheme())
|
|
||||||
|
|
||||||
// Listen for changes in user's preferred color scheme
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
||||||
setTheme(getPreferredTheme())
|
|
||||||
})
|
|
||||||
|
|
||||||
export { getPreferredTheme, setTheme };
|
|
|
@ -1,179 +0,0 @@
|
||||||
/*
|
|
||||||
* =================================================================================================
|
|
||||||
* 💖 BUBBLEGUM PUNK THEME (LIGHT) 💖
|
|
||||||
*
|
|
||||||
* This isn't just a theme. It's a statement.
|
|
||||||
* Unapologetically loud, pink, and quirky.
|
|
||||||
* =================================================================================================
|
|
||||||
*/
|
|
||||||
:root,
|
|
||||||
[data-bs-theme=light] {
|
|
||||||
/* --- CORE VIBE --- */
|
|
||||||
--bs-pink: #FF007A; /* 💖 Hyper Pink (Our Queen) */
|
|
||||||
--bs-green: #CFFF50; /* 🧪 Toxic Slime */
|
|
||||||
--bs-purple: #A328D6; /* 👾 Graffiti Purple */
|
|
||||||
--bs-yellow: #F9F871; /* ⚡ Neon Lemon */
|
|
||||||
--bs-cyan: #00F5D4; /* 💎 Glitchy Teal */
|
|
||||||
--bs-blue: #00A9E0; /* 💦 Splash Zone */
|
|
||||||
|
|
||||||
/* Let's redefine ALL the core colors to match the new energy */
|
|
||||||
--bs-primary: var(--bs-pink);
|
|
||||||
--bs-secondary: var(--bs-green);
|
|
||||||
--bs-success: var(--bs-cyan);
|
|
||||||
--bs-info: var(--bs-blue);
|
|
||||||
--bs-warning: var(--bs-yellow);
|
|
||||||
--bs-danger: #FF3D3D; /* 🚨 Code Red Rave */
|
|
||||||
|
|
||||||
/* --- BACKGROUNDS & TEXT --- */
|
|
||||||
/* No more boring white! */
|
|
||||||
--bs-body-bg: #FFF5FD; /* A soft, dreamy pink canvas */
|
|
||||||
--bs-body-color: #4A003D; /* Dark Plum (instead of black) for text */
|
|
||||||
--bs-heading-color: var(--bs-purple); /* Make headings POP */
|
|
||||||
--bs-secondary-color: rgba(74, 0, 61, 0.75); /* Plum, but softer */
|
|
||||||
--bs-tertiary-color: rgba(74, 0, 61, 0.5);
|
|
||||||
|
|
||||||
/* Make cards and containers pure white to contrast the pink background */
|
|
||||||
--bs-tertiary-bg: #FFFFFF;
|
|
||||||
--bs-secondary-bg: #FEF9FE;
|
|
||||||
|
|
||||||
/* --- LINKS & CODE --- */
|
|
||||||
--bs-link-color: var(--bs-pink);
|
|
||||||
--bs-link-hover-color: var(--bs-purple);
|
|
||||||
--bs-code-color: var(--bs-purple);
|
|
||||||
|
|
||||||
/* --- BORDERS & SHADOWS: LET'S GET QUIRKY --- */
|
|
||||||
--bs-border-width: 2px; /* Chunky borders! */
|
|
||||||
--bs-border-color: #FFD6F5; /* Pink-tinted border color */
|
|
||||||
--bs-border-color-translucent: rgba(74, 0, 61, 0.2);
|
|
||||||
--bs-border-radius: 1rem; /* Super bubbly and round */
|
|
||||||
--bs-border-radius-sm: 0.5rem;
|
|
||||||
--bs-border-radius-lg: 1.5rem;
|
|
||||||
--bs-border-radius-pill: 50rem;
|
|
||||||
|
|
||||||
/* Say goodbye to black shadows, hello to colored glows! */
|
|
||||||
--bs-box-shadow: 0 4px 12px rgba(255, 0, 122, 0.2);
|
|
||||||
--bs-box-shadow-sm: 0 2px 4px rgba(255, 0, 122, 0.15);
|
|
||||||
--bs-box-shadow-lg: 0 8px 30px rgba(255, 0, 122, 0.25);
|
|
||||||
--bs-box-shadow-inset: inset 0 1px 4px rgba(74, 0, 61, 0.2);
|
|
||||||
|
|
||||||
/* --- THE GRADIENT: THE SOUL OF THE THEME --- */
|
|
||||||
--bs-gradient: linear-gradient(75deg, var(--bs-primary), var(--bs-secondary));
|
|
||||||
|
|
||||||
/* --- Don't forget the RGB values for Bootstrap components! --- */
|
|
||||||
--bs-primary-rgb: 255, 0, 122;
|
|
||||||
--bs-secondary-rgb: 207, 255, 80;
|
|
||||||
--bs-body-color-rgb: 74, 0, 61;
|
|
||||||
--bs-body-bg-rgb: 255, 245, 253;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* =================================================================================================
|
|
||||||
* 🌙🦇 CYBER GOTH THEME (DARK) 🦇🌙
|
|
||||||
*
|
|
||||||
* The lights are out, the neon is ON.
|
|
||||||
* A dark, moody theme with vibrant, glowing accents.
|
|
||||||
* =================================================================================================
|
|
||||||
*/
|
|
||||||
[data-bs-theme=dark] {
|
|
||||||
color-scheme: dark;
|
|
||||||
|
|
||||||
/* --- BACKGROUNDS & TEXT --- */
|
|
||||||
--bs-body-bg: #1D001A; /* Deep, dark space purple */
|
|
||||||
--bs-body-color: #FFE9FA; /* Light pink text for high contrast */
|
|
||||||
--bs-heading-color: var(--bs-cyan); /* Glowing cyan headings */
|
|
||||||
|
|
||||||
--bs-tertiary-bg: #2E0028; /* A slightly lighter container background */
|
|
||||||
--bs-secondary-bg: #3A0033;
|
|
||||||
--bs-secondary-color: rgba(255, 233, 250, 0.75);
|
|
||||||
--bs-tertiary-color: rgba(255, 233, 250, 0.5);
|
|
||||||
|
|
||||||
/* --- LINKS & CODE --- */
|
|
||||||
/* Using the Toxic Slime for links gives it that cyber look */
|
|
||||||
--bs-link-color: var(--bs-green);
|
|
||||||
--bs-link-hover-color: var(--bs-cyan);
|
|
||||||
--bs-code-color: var(--bs-pink);
|
|
||||||
|
|
||||||
/* --- BORDERS & SHADOWS: NEON GLOWS --- */
|
|
||||||
--bs-border-color: #5C004F;
|
|
||||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
|
||||||
|
|
||||||
/* Redefine shadows to be neon glows */
|
|
||||||
--bs-box-shadow: 0 0 15px rgba(var(--bs-primary-rgb), 0.4);
|
|
||||||
--bs-box-shadow-lg: 0 0 30px rgba(var(--bs-primary-rgb), 0.5);
|
|
||||||
|
|
||||||
/* --- EMPHASIS & SUBTLE BACKGROUNDS --- */
|
|
||||||
/* These are for alerts, badges, etc. They'll be dark with glowing text. */
|
|
||||||
--bs-primary-text-emphasis: #FF8AD1;
|
|
||||||
--bs-secondary-text-emphasis: #E2FF8A;
|
|
||||||
--bs-success-text-emphasis: #8AFFEB;
|
|
||||||
--bs-info-text-emphasis: #7ADCF5;
|
|
||||||
--bs-warning-text-emphasis: #FAF8A8;
|
|
||||||
--bs-danger-text-emphasis: #FF8A8A;
|
|
||||||
|
|
||||||
--bs-primary-bg-subtle: #3D002B;
|
|
||||||
--bs-secondary-bg-subtle: #415215;
|
|
||||||
--bs-success-bg-subtle: #00332B;
|
|
||||||
--bs-info-bg-subtle: #00313D;
|
|
||||||
--bs-warning-bg-subtle: #3E3D1C;
|
|
||||||
--bs-danger-bg-subtle: #520E0E;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* === EMOJI LEVELS === */
|
|
||||||
.emoji-normal .emoji-normal { display: inline; }
|
|
||||||
.emoji-normal .emoji-enhanced,
|
|
||||||
.emoji-normal .emoji-bonkers { display: none; }
|
|
||||||
|
|
||||||
.emoji-enhanced .emoji-enhanced { display: inline; }
|
|
||||||
.emoji-enhanced .emoji-normal,
|
|
||||||
.emoji-enhanced .emoji-bonkers { display: none; }
|
|
||||||
|
|
||||||
.emoji-bonkers .emoji-bonkers { display: inline; }
|
|
||||||
.emoji-bonkers .emoji-normal,
|
|
||||||
.emoji-bonkers .emoji-enhanced { display: none; }
|
|
||||||
/*
|
|
||||||
* =================================================================================================
|
|
||||||
* 🌈 RAINBOW PRIDE ELEMENTS 🌈
|
|
||||||
*
|
|
||||||
* Fabulous rainbow-themed elements to celebrate diversity and pride!
|
|
||||||
* =================================================================================================
|
|
||||||
*/
|
|
||||||
.bg-rainbow {
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
#FF5757, /* Red */
|
|
||||||
#FFBD59, /* Orange */
|
|
||||||
#F9F871, /* Yellow */
|
|
||||||
#CFFF50, /* Green */
|
|
||||||
#00F5D4, /* Teal */
|
|
||||||
#00A9E0, /* Blue */
|
|
||||||
#A328D6 /* Purple */
|
|
||||||
);
|
|
||||||
color: white;
|
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
||||||
font-weight: bold;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fun-fact {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add a subtle rainbow border to the fun facts card */
|
|
||||||
.card:has(.fun-fact) {
|
|
||||||
border-width: 2px;
|
|
||||||
border-style: solid;
|
|
||||||
border-image: linear-gradient(
|
|
||||||
to right,
|
|
||||||
#FF5757, /* Red */
|
|
||||||
#FFBD59, /* Orange */
|
|
||||||
#F9F871, /* Yellow */
|
|
||||||
#CFFF50, /* Green */
|
|
||||||
#00F5D4, /* Teal */
|
|
||||||
#00A9E0, /* Blue */
|
|
||||||
#A328D6 /* Purple */
|
|
||||||
) 1;
|
|
||||||
box-shadow: 0 4px 15px rgba(163, 40, 214, 0.2);
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
|
|
||||||
/* Emoji Footprint Animation */
|
|
||||||
.emoji-footprint {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
pointer-events: none;
|
|
||||||
animation: emojiFade 1s ease-out forwards;
|
|
||||||
transform: translate(-50%, -50%) scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
z-index: 9999;
|
|
||||||
text-shadow:
|
|
||||||
0 0 4px #ff00bf,
|
|
||||||
0 0 8px #ff80df,
|
|
||||||
0 0 12px #ffccff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes emojiFade {
|
|
||||||
0% {
|
|
||||||
transform: translate(-50%, -50%) scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translate(-50%, -50%) scale(1.5);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translate(-50%, -50%) scale(2);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,568 +0,0 @@
|
||||||
/* 🌈✨ BONKERS MODE ANIMATIONS ✨🌈 */
|
|
||||||
@keyframes rainbowGradient {
|
|
||||||
0% { background-position: 0% 50%; }
|
|
||||||
50% { background-position: 100% 50%; }
|
|
||||||
100% { background-position: 0% 50%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes discoFlash {
|
|
||||||
0%, 100% {
|
|
||||||
background-color: var(--bs-pink);
|
|
||||||
box-shadow: 0 0 20px var(--bs-pink), 0 0 40px var(--bs-pink);
|
|
||||||
}
|
|
||||||
16.66% {
|
|
||||||
background-color: var(--bs-purple);
|
|
||||||
box-shadow: 0 0 20px var(--bs-purple), 0 0 40px var(--bs-purple);
|
|
||||||
}
|
|
||||||
33.33% {
|
|
||||||
background-color: var(--bs-cyan);
|
|
||||||
box-shadow: 0 0 20px var(--bs-cyan), 0 0 40px var(--bs-cyan);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
background-color: var(--bs-yellow);
|
|
||||||
box-shadow: 0 0 20px var(--bs-yellow), 0 0 40px var(--bs-yellow);
|
|
||||||
}
|
|
||||||
66.66% {
|
|
||||||
background-color: var(--bs-green);
|
|
||||||
box-shadow: 0 0 20px var(--bs-green), 0 0 40px var(--bs-green);
|
|
||||||
}
|
|
||||||
83.33% {
|
|
||||||
background-color: var(--bs-orange);
|
|
||||||
box-shadow: 0 0 20px var(--bs-orange), 0 0 40px var(--bs-orange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes wiggle {
|
|
||||||
0%, 100% { transform: rotate(0deg); }
|
|
||||||
25% { transform: rotate(-2deg); }
|
|
||||||
75% { transform: rotate(2deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { transform: scale(1); }
|
|
||||||
50% { transform: scale(1.05); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rainbowText {
|
|
||||||
0% { color: var(--bs-red); }
|
|
||||||
14.28% { color: var(--bs-orange); }
|
|
||||||
28.57% { color: var(--bs-yellow); }
|
|
||||||
42.85% { color: var(--bs-green); }
|
|
||||||
57.14% { color: var(--bs-cyan); }
|
|
||||||
71.42% { color: var(--bs-purple); }
|
|
||||||
85.71% { color: var(--bs-pink); }
|
|
||||||
100% { color: var(--bs-red); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes shine {
|
|
||||||
0% { left: -100%; }
|
|
||||||
50% { left: 100%; }
|
|
||||||
100% { left: 100%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slayFloat {
|
|
||||||
0% {
|
|
||||||
transform: translateY(0) scale(0.5);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
20% {
|
|
||||||
transform: translateY(-20px) scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
80% {
|
|
||||||
transform: translateY(-60px) scale(1.2);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateY(-100px) scale(1.5);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎭 BONKERS MODE CLASSES 🎭 */
|
|
||||||
.bonkers-mode {
|
|
||||||
background: linear-gradient(270deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red), var(--bs-pink));
|
|
||||||
background-size: 1600% 1600%;
|
|
||||||
animation: rainbowGradient 10s ease infinite;
|
|
||||||
transition: all 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .btn {
|
|
||||||
animation: discoFlash 0.3s infinite, wiggle 0.2s infinite;
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: discoFlash 0.3s infinite, wiggle 0.2s infinite, rainbowGradient 1s ease infinite;
|
|
||||||
border: 4px solid var(--bs-white);
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .btn:hover {
|
|
||||||
animation: discoFlash 0.2s infinite, wiggle 0.1s infinite, rainbowGradient 0.5s ease infinite;
|
|
||||||
box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .btn::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
animation: spin 0.5s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar {
|
|
||||||
background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
|
||||||
background-size: 200% 200%;
|
|
||||||
animation: rainbowGradient 2s ease infinite;
|
|
||||||
box-shadow: 0 0 50px rgba(255, 105, 180, 0.9);
|
|
||||||
height: auto !important;
|
|
||||||
min-height: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-brand {
|
|
||||||
animation: rainbowText 0.8s infinite, wiggle 0.4s infinite;
|
|
||||||
font-size: 1.8em;
|
|
||||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.5);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-brand::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-nav .nav-link {
|
|
||||||
animation: rainbowText 1.2s infinite, wiggle 0.3s infinite;
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
margin: 0 4px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-nav .nav-link::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
|
||||||
animation: shine 1.5s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-nav .nav-link:hover {
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
|
||||||
border-color: var(--bs-white);
|
|
||||||
box-shadow: 0 0 20px var(--bs-pink);
|
|
||||||
animation: discoFlash 0.5s infinite, wiggle 0.2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-nav .nav-link.active {
|
|
||||||
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
|
||||||
border-color: var(--bs-white);
|
|
||||||
box-shadow: 0 0 25px var(--bs-yellow);
|
|
||||||
animation: discoFlash 0.8s infinite, wiggle 0.3s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-text {
|
|
||||||
animation: rainbowText 1.5s infinite, wiggle 0.5s infinite;
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
border: 2px solid var(--bs-white);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue));
|
|
||||||
box-shadow: 0 0 15px var(--bs-cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-toggler {
|
|
||||||
border: 3px solid var(--bs-white);
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
|
||||||
animation: discoFlash 0.6s infinite, wiggle 0.4s infinite;
|
|
||||||
box-shadow: 0 0 20px var(--bs-pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-toggler:focus {
|
|
||||||
box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-toggler-icon {
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .dropdown-menu {
|
|
||||||
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
|
||||||
border: 3px solid var(--bs-white);
|
|
||||||
box-shadow: 0 0 30px rgba(255,105,180,0.8);
|
|
||||||
animation: rainbowGradient 2s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .dropdown-item {
|
|
||||||
animation: rainbowText 1.8s infinite, wiggle 0.6s infinite;
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.3);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .dropdown-item:hover {
|
|
||||||
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
|
||||||
color: var(--bs-white);
|
|
||||||
box-shadow: 0 0 15px var(--bs-yellow);
|
|
||||||
animation: discoFlash 0.5s infinite, wiggle 0.3s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .navbar-collapse {
|
|
||||||
background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1));
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 2px solid var(--bs-pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode h1, .bonkers-mode h2, .bonkers-mode h3 {
|
|
||||||
animation: rainbowText 1.5s infinite;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .table {
|
|
||||||
background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2));
|
|
||||||
animation: rainbowGradient 3s ease infinite;
|
|
||||||
border: 3px solid var(--bs-pink);
|
|
||||||
box-shadow: 0 0 30px rgba(255,105,180,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .table th {
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
|
||||||
color: var(--bs-white);
|
|
||||||
animation: discoFlash 0.8s infinite;
|
|
||||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .form-control {
|
|
||||||
border: 3px solid var(--bs-pink);
|
|
||||||
box-shadow: 0 0 15px var(--bs-pink);
|
|
||||||
animation: pulse 0.6s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .alert {
|
|
||||||
animation: discoFlash 0.6s infinite, wiggle 0.3s infinite;
|
|
||||||
border: 4px solid var(--bs-white);
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .card {
|
|
||||||
background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2));
|
|
||||||
border: 3px solid var(--bs-purple);
|
|
||||||
box-shadow: 0 0 35px rgba(138,43,226,0.6);
|
|
||||||
animation: pulse 1s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .modal-content {
|
|
||||||
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
|
||||||
border: 4px solid var(--bs-white);
|
|
||||||
box-shadow: 0 0 50px rgba(255,105,180,0.8);
|
|
||||||
animation: rainbowGradient 2s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .modal-header {
|
|
||||||
background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange));
|
|
||||||
animation: discoFlash 0.8s infinite;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .number-input-wrapper {
|
|
||||||
animation: wiggle 0.4s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bonkers-mode .number-input-wrapper .btn {
|
|
||||||
animation: discoFlash 0.3s infinite, wiggle 0.2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enhanced mode styles (for future use) */
|
|
||||||
[data-website-mode="enhanced"] .btn {
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
|
||||||
background-size: 400% 400%;
|
|
||||||
animation: rainbowGradient 1s ease infinite;
|
|
||||||
border: 4px solid var(--bs-white);
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .btn:hover {
|
|
||||||
animation: rainbowGradient 0.5s ease infinite;
|
|
||||||
box-shadow: 0 0 30px var(--bs-pink), 0 0 60px var(--bs-purple);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .btn::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.5), transparent);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
animation: spin 0.5s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar {
|
|
||||||
background: linear-gradient(90deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow), var(--bs-green), var(--bs-orange), var(--bs-red));
|
|
||||||
background-size: 200% 200%;
|
|
||||||
animation: rainbowGradient 2s ease infinite;
|
|
||||||
box-shadow: 0 0 50px rgba(255, 105, 180, 0.9);
|
|
||||||
height: auto !important;
|
|
||||||
min-height: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-brand {
|
|
||||||
animation: rainbowText 0.8s infinite;
|
|
||||||
font-size: 1.8em;
|
|
||||||
text-shadow: 3px 3px 6px rgba(0,0,0,0.5);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-brand::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
left: -50%;
|
|
||||||
width: 200%;
|
|
||||||
height: 200%;
|
|
||||||
background: linear-gradient(45deg, transparent, rgba(255,255,255,0.3), transparent);
|
|
||||||
transform: rotate(45deg);
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-nav .nav-link {
|
|
||||||
animation: rainbowText 1.2s infinite;
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
margin: 0 4px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-nav .nav-link::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: -100%;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
|
||||||
animation: shine 1.5s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-nav .nav-link:hover {
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
|
||||||
border-color: var(--bs-white);
|
|
||||||
box-shadow: 0 0 20px var(--bs-pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-nav .nav-link.active {
|
|
||||||
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
|
||||||
border-color: var(--bs-white);
|
|
||||||
box-shadow: 0 0 25px var(--bs-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-text {
|
|
||||||
animation: rainbowText 1.5s infinite;
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
border: 2px solid var(--bs-white);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background: linear-gradient(45deg, var(--bs-cyan), var(--bs-blue));
|
|
||||||
box-shadow: 0 0 15px var(--bs-cyan);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-toggler {
|
|
||||||
border: 3px solid var(--bs-white);
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
|
||||||
animation: rainbowGradient 0.6s ease infinite;
|
|
||||||
box-shadow: 0 0 20px var(--bs-pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-toggler:focus {
|
|
||||||
box-shadow: 0 0 30px var(--bs-pink), 0 0 0 0.2rem rgba(255, 105, 180, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-toggler-icon {
|
|
||||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 1)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .dropdown-menu {
|
|
||||||
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
|
||||||
border: 3px solid var(--bs-white);
|
|
||||||
box-shadow: 0 0 30px rgba(255,105,180,0.8);
|
|
||||||
animation: rainbowGradient 2s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .dropdown-item {
|
|
||||||
animation: rainbowText 1.8s infinite;
|
|
||||||
font-weight: bold;
|
|
||||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
|
||||||
border-bottom: 1px solid rgba(255,255,255,0.3);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .dropdown-item:hover {
|
|
||||||
background: linear-gradient(45deg, var(--bs-yellow), var(--bs-orange));
|
|
||||||
color: var(--bs-white);
|
|
||||||
box-shadow: 0 0 15px var(--bs-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .navbar-collapse {
|
|
||||||
background: linear-gradient(135deg, rgba(255,105,180,0.1), rgba(138,43,226,0.1));
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 8px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 2px solid var(--bs-pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] h1, [data-website-mode="enhanced"] h2, [data-website-mode="enhanced"] h3 {
|
|
||||||
animation: rainbowText 1.5s infinite;
|
|
||||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .table {
|
|
||||||
background: linear-gradient(135deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2), rgba(0,255,255,0.2));
|
|
||||||
animation: rainbowGradient 3s ease infinite;
|
|
||||||
border: 3px solid var(--bs-pink);
|
|
||||||
box-shadow: 0 0 30px rgba(255,105,180,0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .table th {
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple));
|
|
||||||
color: var(--bs-white);
|
|
||||||
animation: rainbowGradient 0.8s ease infinite;
|
|
||||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .form-control {
|
|
||||||
border: 3px solid var(--bs-pink);
|
|
||||||
box-shadow: 0 0 15px var(--bs-pink);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .alert {
|
|
||||||
animation: rainbowGradient 0.6s ease infinite;
|
|
||||||
border: 4px solid var(--bs-white);
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .card {
|
|
||||||
background: linear-gradient(45deg, rgba(255,105,180,0.2), rgba(138,43,226,0.2));
|
|
||||||
border: 3px solid var(--bs-purple);
|
|
||||||
box-shadow: 0 0 35px rgba(138,43,226,0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .modal-content {
|
|
||||||
background: linear-gradient(135deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan));
|
|
||||||
border: 4px solid var(--bs-white);
|
|
||||||
box-shadow: 0 0 50px rgba(255,105,180,0.8);
|
|
||||||
animation: rainbowGradient 2s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .modal-header {
|
|
||||||
background: linear-gradient(90deg, var(--bs-yellow), var(--bs-orange));
|
|
||||||
animation: rainbowGradient 0.8s ease infinite;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .number-input-wrapper {
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-website-mode="enhanced"] .number-input-wrapper .btn {
|
|
||||||
animation: rainbowGradient 0.3s ease infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Emoji Footprint Animation */
|
|
||||||
.emoji-footprint {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
pointer-events: none;
|
|
||||||
animation: emojiFade 1s ease-out forwards;
|
|
||||||
transform: translate(-50%, -50%) scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
z-index: 9999;
|
|
||||||
text-shadow:
|
|
||||||
0 0 4px #ff00bf,
|
|
||||||
0 0 8px #ff80df,
|
|
||||||
0 0 12px #ffccff;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes emojiFade {
|
|
||||||
0% {
|
|
||||||
transform: translate(-50%, -50%) scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: translate(-50%, -50%) scale(1.5);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translate(-50%, -50%) scale(2);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 💅 SLAY TEXT EFFECTS 💅 */
|
|
||||||
.slay-text {
|
|
||||||
position: fixed;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 10000;
|
|
||||||
animation: slayFloat 3s ease-out forwards;
|
|
||||||
text-shadow:
|
|
||||||
0 0 10px #ff00bf,
|
|
||||||
0 0 20px #ff80df,
|
|
||||||
0 0 30px #ffccff,
|
|
||||||
2px 2px 4px rgba(0,0,0,0.5);
|
|
||||||
background: linear-gradient(45deg, var(--bs-pink), var(--bs-purple), var(--bs-cyan), var(--bs-yellow));
|
|
||||||
background-size: 400% 400%;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
animation: slayFloat 3s ease-out forwards, rainbowGradient 1s ease infinite;
|
|
||||||
}
|
|
23
bin/phpunit
Executable file
23
bin/phpunit
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
if (!ini_get('date.timezone')) {
|
||||||
|
ini_set('date.timezone', 'UTC');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||||
|
if (PHP_VERSION_ID >= 80000) {
|
||||||
|
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||||
|
} else {
|
||||||
|
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||||
|
require PHPUNIT_COMPOSER_INSTALL;
|
||||||
|
PHPUnit\TextUI\Command::main();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||||
|
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||||
|
}
|
|
@ -7,58 +7,43 @@
|
||||||
"php": ">=8.4",
|
"php": ">=8.4",
|
||||||
"ext-ctype": "*",
|
"ext-ctype": "*",
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"api-platform/doctrine-orm": ">=4.1.18",
|
"doctrine/dbal": "^4.1",
|
||||||
"api-platform/symfony": ">=4.1.18",
|
"doctrine/doctrine-bundle": "^2.12",
|
||||||
"doctrine/dbal": "^4.2.4",
|
"doctrine/doctrine-migrations-bundle": "^3.3.1",
|
||||||
"doctrine/doctrine-bundle": "^2.15.0",
|
"doctrine/orm": "^3.2.1",
|
||||||
"doctrine/doctrine-migrations-bundle": "^3.4.2",
|
|
||||||
"doctrine/orm": "^3.5.0",
|
|
||||||
"nelmio/cors-bundle": "^2.5",
|
|
||||||
"phpdocumentor/reflection-docblock": "^5.6.2",
|
|
||||||
"psr/clock": "^1.0",
|
"psr/clock": "^1.0",
|
||||||
"symfony/asset": "7.3.*",
|
"symfony/console": "7.1.*",
|
||||||
"symfony/asset-mapper": "7.3.*",
|
"symfony/dotenv": "7.1.*",
|
||||||
"symfony/console": "7.3.*",
|
"symfony/flex": "^2.4.6",
|
||||||
"symfony/dotenv": "7.3.*",
|
"symfony/form": "7.1.*",
|
||||||
"symfony/expression-language": "7.3.*",
|
"symfony/framework-bundle": "7.1.*",
|
||||||
"symfony/flex": "^2.8.1",
|
"symfony/runtime": "7.1.*",
|
||||||
"symfony/form": "7.3.*",
|
"symfony/security-csrf": "7.1.*",
|
||||||
"symfony/framework-bundle": "7.3.*",
|
"symfony/twig-bundle": "7.1.*",
|
||||||
"symfony/monolog-bundle": "^3.10",
|
"symfony/uid": "7.1.*",
|
||||||
"symfony/property-access": "7.3.*",
|
"symfony/validator": "7.1.*",
|
||||||
"symfony/property-info": "7.3.*",
|
"symfony/yaml": "7.1.*"
|
||||||
"symfony/runtime": "7.3.*",
|
|
||||||
"symfony/security-bundle": "7.3.*",
|
|
||||||
"symfony/security-csrf": "7.3.*",
|
|
||||||
"symfony/serializer": "7.3.*",
|
|
||||||
"symfony/twig-bundle": "7.3.*",
|
|
||||||
"symfony/uid": "7.3.*",
|
|
||||||
"symfony/validator": "7.3.*",
|
|
||||||
"symfony/yaml": "7.3.*",
|
|
||||||
"twig/extra-bundle": "^2.12|^3.21",
|
|
||||||
"twig/intl-extra": "^3.21",
|
|
||||||
"twig/twig": "^2.12|^3.21.1"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/doctrine-fixtures-bundle": "^4.1",
|
"doctrine/doctrine-fixtures-bundle": "^4.0",
|
||||||
"liip/test-fixtures-bundle": "^3.5",
|
"infection/infection": "^0.29.6",
|
||||||
"lubiana/code-quality": "1.7.3",
|
"lubiana/code-quality": "^1.7.2",
|
||||||
"pestphp/pest": "^3.8.2",
|
"phpunit/phpunit": "^9.6.20",
|
||||||
"symfony/browser-kit": "7.3.*",
|
"symfony/browser-kit": "7.2.*",
|
||||||
"symfony/css-selector": "7.3.*",
|
"symfony/css-selector": "7.2.*",
|
||||||
"symfony/http-client": "7.3.*",
|
"symfony/maker-bundle": "^1.60",
|
||||||
"symfony/maker-bundle": "^1.64",
|
"symfony/phpunit-bridge": "7.2.*",
|
||||||
"symfony/stopwatch": "7.3.*",
|
"symfony/stopwatch": "7.2.*",
|
||||||
"symfony/web-profiler-bundle": "7.3.*",
|
"symfony/web-profiler-bundle": "7.2.*",
|
||||||
"symplify/config-transformer": "^12.4.0"
|
"symplify/config-transformer": "^12.3.4"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
|
||||||
"pestphp/pest-plugin": true,
|
|
||||||
"php-http/discovery": true,
|
"php-http/discovery": true,
|
||||||
"symfony/flex": true,
|
"symfony/flex": true,
|
||||||
"symfony/runtime": true
|
"symfony/runtime": true,
|
||||||
|
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||||
|
"infection/extension-installer": true
|
||||||
},
|
},
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"platform": {
|
"platform": {
|
||||||
|
@ -83,15 +68,12 @@
|
||||||
"symfony/polyfill-php74": "*",
|
"symfony/polyfill-php74": "*",
|
||||||
"symfony/polyfill-php80": "*",
|
"symfony/polyfill-php80": "*",
|
||||||
"symfony/polyfill-php81": "*",
|
"symfony/polyfill-php81": "*",
|
||||||
"symfony/polyfill-php82": "*",
|
"symfony/polyfill-php82": "*"
|
||||||
"symfony/polyfill-php83": "*",
|
|
||||||
"symfony/polyfill-php84": "*"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"auto-scripts": {
|
"auto-scripts": {
|
||||||
"cache:clear": "symfony-cmd",
|
"cache:clear": "symfony-cmd",
|
||||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||||
"importmap:install": "symfony-cmd"
|
|
||||||
},
|
},
|
||||||
"post-install-cmd": [
|
"post-install-cmd": [
|
||||||
"@auto-scripts"
|
"@auto-scripts"
|
||||||
|
@ -104,7 +86,8 @@
|
||||||
"rector",
|
"rector",
|
||||||
"ecs --fix || ecs --fix"
|
"ecs --fix || ecs --fix"
|
||||||
],
|
],
|
||||||
"test": "pest --parallel"
|
"test": "bin/phpunit",
|
||||||
|
"mutation": "infection --threads=8 --show-mutations"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"symfony/symfony": "*"
|
"symfony/symfony": "*"
|
||||||
|
@ -112,7 +95,7 @@
|
||||||
"extra": {
|
"extra": {
|
||||||
"symfony": {
|
"symfony": {
|
||||||
"allow-contrib": false,
|
"allow-contrib": false,
|
||||||
"require": "7.3.*"
|
"require": "7.2.*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
6765
composer.lock
generated
6765
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,12 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
|
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
|
||||||
use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle;
|
use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle;
|
||||||
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
|
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
|
||||||
use Liip\TestFixturesBundle\LiipTestFixturesBundle;
|
|
||||||
use Nelmio\CorsBundle\NelmioCorsBundle;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||||
use Symfony\Bundle\MakerBundle\MakerBundle;
|
use Symfony\Bundle\MakerBundle\MakerBundle;
|
||||||
use Symfony\Bundle\MonologBundle\MonologBundle;
|
|
||||||
use Symfony\Bundle\SecurityBundle\SecurityBundle;
|
|
||||||
use Symfony\Bundle\TwigBundle\TwigBundle;
|
use Symfony\Bundle\TwigBundle\TwigBundle;
|
||||||
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
|
||||||
use Twig\Extra\TwigExtraBundle\TwigExtraBundle;
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
FrameworkBundle::class => [
|
FrameworkBundle::class => [
|
||||||
|
@ -38,23 +32,4 @@ return [
|
||||||
'dev' => true,
|
'dev' => true,
|
||||||
'test' => true,
|
'test' => true,
|
||||||
],
|
],
|
||||||
SecurityBundle::class => [
|
|
||||||
'all' => true,
|
|
||||||
],
|
|
||||||
NelmioCorsBundle::class => [
|
|
||||||
'all' => true,
|
|
||||||
],
|
|
||||||
LiipTestFixturesBundle::class => [
|
|
||||||
'dev' => true,
|
|
||||||
'test' => true,
|
|
||||||
],
|
|
||||||
TwigExtraBundle::class => [
|
|
||||||
'all' => true,
|
|
||||||
],
|
|
||||||
MonologBundle::class => [
|
|
||||||
'all' => true,
|
|
||||||
],
|
|
||||||
ApiPlatformBundle::class => [
|
|
||||||
'all' => true,
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('api_platform', [
|
|
||||||
'title' => 'Hello API Platform',
|
|
||||||
'version' => '1.0.0',
|
|
||||||
'defaults' => [
|
|
||||||
'stateless' => true,
|
|
||||||
'cache_headers' => [
|
|
||||||
'vary' => [
|
|
||||||
'Content-Type',
|
|
||||||
'Authorization',
|
|
||||||
'Origin',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
use Symfony\Config\FrameworkConfig;
|
|
||||||
|
|
||||||
return static function (
|
|
||||||
ContainerConfigurator $containerConfigurator,
|
|
||||||
FrameworkConfig $frameworkConfig,
|
|
||||||
): void {
|
|
||||||
$frameworkConfig->assetMapper()
|
|
||||||
->path('assets/', true)
|
|
||||||
->missingImportMode('strict')
|
|
||||||
->importmapPolyfill(false)
|
|
||||||
;
|
|
||||||
if ($containerConfigurator->env() === 'prod') {
|
|
||||||
$frameworkConfig->assetMapper()
|
|
||||||
->missingImportMode('warn');
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('framework', [
|
|
||||||
'form' => [
|
|
||||||
'csrf_protection' => [
|
|
||||||
'token_id' => 'submit',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'csrf_protection' => [
|
|
||||||
'stateless_token_ids' => [
|
|
||||||
'submit',
|
|
||||||
'authenticate',
|
|
||||||
'logout',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -1,95 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('monolog', [
|
|
||||||
'channels' => [
|
|
||||||
'deprecation',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
if ($containerConfigurator->env() === 'dev') {
|
|
||||||
$containerConfigurator->extension('monolog', [
|
|
||||||
'handlers' => [
|
|
||||||
'main' => [
|
|
||||||
'type' => 'stream',
|
|
||||||
'path' => '%kernel.logs_dir%/%kernel.environment%.log',
|
|
||||||
'level' => 'debug',
|
|
||||||
'channels' => [
|
|
||||||
'!event',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'console' => [
|
|
||||||
'type' => 'console',
|
|
||||||
'process_psr_3_messages' => false,
|
|
||||||
'channels' => [
|
|
||||||
'!event',
|
|
||||||
'!doctrine',
|
|
||||||
'!console',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($containerConfigurator->env() === 'test') {
|
|
||||||
$containerConfigurator->extension('monolog', [
|
|
||||||
'handlers' => [
|
|
||||||
'main' => [
|
|
||||||
'type' => 'fingers_crossed',
|
|
||||||
'action_level' => 'error',
|
|
||||||
'handler' => 'nested',
|
|
||||||
'excluded_http_codes' => [
|
|
||||||
404,
|
|
||||||
405,
|
|
||||||
],
|
|
||||||
'channels' => [
|
|
||||||
'!event',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'nested' => [
|
|
||||||
'type' => 'stream',
|
|
||||||
'path' => '%kernel.logs_dir%/%kernel.environment%.log',
|
|
||||||
'level' => 'debug',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if ($containerConfigurator->env() === 'prod') {
|
|
||||||
$containerConfigurator->extension('monolog', [
|
|
||||||
'handlers' => [
|
|
||||||
'main' => [
|
|
||||||
'type' => 'fingers_crossed',
|
|
||||||
'action_level' => 'error',
|
|
||||||
'handler' => 'nested',
|
|
||||||
'excluded_http_codes' => [
|
|
||||||
404,
|
|
||||||
405,
|
|
||||||
],
|
|
||||||
'buffer_size' => 50,
|
|
||||||
],
|
|
||||||
'nested' => [
|
|
||||||
'type' => 'stream',
|
|
||||||
'path' => 'php://stderr',
|
|
||||||
'level' => 'debug',
|
|
||||||
'formatter' => 'monolog.formatter.json',
|
|
||||||
],
|
|
||||||
'console' => [
|
|
||||||
'type' => 'console',
|
|
||||||
'process_psr_3_messages' => false,
|
|
||||||
'channels' => [
|
|
||||||
'!event',
|
|
||||||
'!doctrine',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'deprecation' => [
|
|
||||||
'type' => 'stream',
|
|
||||||
'channels' => [
|
|
||||||
'deprecation',
|
|
||||||
],
|
|
||||||
'path' => 'php://stderr',
|
|
||||||
'formatter' => 'monolog.formatter.json',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,33 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('nelmio_cors', [
|
|
||||||
'defaults' => [
|
|
||||||
'origin_regex' => true,
|
|
||||||
'allow_origin' => [
|
|
||||||
'%env(CORS_ALLOW_ORIGIN)%',
|
|
||||||
],
|
|
||||||
'allow_methods' => [
|
|
||||||
'GET',
|
|
||||||
'OPTIONS',
|
|
||||||
'POST',
|
|
||||||
'PUT',
|
|
||||||
'PATCH',
|
|
||||||
'DELETE',
|
|
||||||
],
|
|
||||||
'allow_headers' => [
|
|
||||||
'Content-Type',
|
|
||||||
'Authorization',
|
|
||||||
],
|
|
||||||
'expose_headers' => [
|
|
||||||
'Link',
|
|
||||||
],
|
|
||||||
'max_age' => 3600,
|
|
||||||
],
|
|
||||||
'paths' => [
|
|
||||||
'^/' => null,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('framework', [
|
|
||||||
'property_info' => [
|
|
||||||
'with_constructor_extractor' => true,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
};
|
|
|
@ -1,40 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
$containerConfigurator->extension('security', [
|
|
||||||
'password_hashers' => [
|
|
||||||
PasswordAuthenticatedUserInterface::class => 'auto',
|
|
||||||
],
|
|
||||||
'providers' => [
|
|
||||||
'users_in_memory' => [
|
|
||||||
'memory' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'firewalls' => [
|
|
||||||
'dev' => [
|
|
||||||
'pattern' => '^/(_(profiler|wdt)|css|images|js)/',
|
|
||||||
'security' => false,
|
|
||||||
],
|
|
||||||
'main' => [
|
|
||||||
'lazy' => true,
|
|
||||||
'provider' => 'users_in_memory',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'access_control' => null,
|
|
||||||
]);
|
|
||||||
if ($containerConfigurator->env() === 'test') {
|
|
||||||
$containerConfigurator->extension('security', [
|
|
||||||
'password_hashers' => [
|
|
||||||
PasswordAuthenticatedUserInterface::class => [
|
|
||||||
'algorithm' => 'auto',
|
|
||||||
'cost' => 4,
|
|
||||||
'time_cost' => 3,
|
|
||||||
'memory_cost' => 10,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,16 +1,17 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
use Symfony\Config\TwigConfig;
|
|
||||||
|
|
||||||
return static function (
|
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
ContainerConfigurator $containerConfigurator,
|
$containerConfigurator->extension('twig', [
|
||||||
TwigConfig $twig,
|
'file_name_pattern' => '*.twig',
|
||||||
): void {
|
'globals' => [
|
||||||
|
'favicon' => '@App\Service\Favicon',
|
||||||
|
],
|
||||||
|
]);
|
||||||
if ($containerConfigurator->env() === 'test') {
|
if ($containerConfigurator->env() === 'test') {
|
||||||
$twig->strictVariables(true);
|
$containerConfigurator->extension('twig', [
|
||||||
|
'strict_variables' => true,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
$twig->formThemes(['bootstrap_5_layout.html.twig']);
|
|
||||||
$twig->fileNamePattern('*.twig');
|
|
||||||
$twig->global('favicon', '@App\Service\Favicon');
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
|
||||||
|
|
||||||
return static function (RoutingConfigurator $routingConfigurator): void {
|
|
||||||
$routingConfigurator->import('.', 'api_platform')
|
|
||||||
->prefix('/api');
|
|
||||||
};
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
|
|
||||||
|
|
||||||
return static function (RoutingConfigurator $routingConfigurator): void {
|
|
||||||
$routingConfigurator->import('security.route_loader.logout', 'service');
|
|
||||||
};
|
|
|
@ -8,13 +8,14 @@ fi
|
||||||
mkdir $TARGETDIR
|
mkdir $TARGETDIR
|
||||||
cd $TARGETDIR || return
|
cd $TARGETDIR || return
|
||||||
|
|
||||||
pathsToCopy="assets public bin config migrations src templates composer.json composer.lock symfony.lock .env importmap.php"
|
pathsToCopy="public bin config migrations src templates composer.json composer.lock symfony.lock .env"
|
||||||
|
|
||||||
for path in $pathsToCopy
|
for path in $pathsToCopy
|
||||||
do
|
do
|
||||||
cp -r ../../"$path" ./
|
cp -r ../../"$path" ./
|
||||||
done
|
done
|
||||||
|
|
||||||
|
rm ./bin/phpunit
|
||||||
APP_ENV=prod composer install --no-dev -a
|
APP_ENV=prod composer install --no-dev -a
|
||||||
rm -rf ./var/cache
|
rm -rf ./var/cache
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,12 @@ ExecStart=/usr/bin/podman run \
|
||||||
--replace \
|
--replace \
|
||||||
-d \
|
-d \
|
||||||
--name futtern-php \
|
--name futtern-php \
|
||||||
--volume %h/futtern/etc/php84/php-fpm.d/www.conf:/etc/php84/php-fpm.d/www.conf \
|
--volume %h/futtern/etc/php83/php-fpm.d/www.conf:/etc/php83/php-fpm.d/www.conf \
|
||||||
--volume %h/futtern/app:/var/www/html \
|
--volume %h/futtern/app:/var/www/html \
|
||||||
--volume %h/futtern/app/var:/var/www/html/var \
|
--volume %h/futtern/app/var:/var/www/html/var \
|
||||||
--env APP_ENV=prod \
|
--env APP_ENV=prod \
|
||||||
--env APP_SECRET=UwUtHiSisNotSecurePlZcHanGeMe \
|
--env APP_SECRET=UwUtHiSisNotSecurePlZcHanGeMe \
|
||||||
git.php.fail/lubiana/container/php:8.4-fpm
|
git.php.fail/lubiana/container/php:8.3-fpm
|
||||||
ExecStop=/usr/bin/podman stop \
|
ExecStop=/usr/bin/podman stop \
|
||||||
--ignore -t 10 \
|
--ignore -t 10 \
|
||||||
--cidfile=%t/%n.ctr-id
|
--cidfile=%t/%n.ctr-id
|
||||||
|
|
|
@ -5,7 +5,4 @@ systemctl --user start pod-futtern
|
||||||
sleep 2
|
sleep 2
|
||||||
podman exec -it futtern-php /var/www/html/bin/console cache:clear
|
podman exec -it futtern-php /var/www/html/bin/console cache:clear
|
||||||
podman exec -it futtern-php /var/www/html/bin/console cache:warmup
|
podman exec -it futtern-php /var/www/html/bin/console cache:warmup
|
||||||
podman exec -it futtern-php /var/www/html/bin/console asset-map:compile
|
|
||||||
|
|
||||||
|
|
||||||
echo 'yes' | podman exec -it futtern-php /var/www/html/bin/console doctrine:migrations:migrate
|
echo 'yes' | podman exec -it futtern-php /var/www/html/bin/console doctrine:migrations:migrate
|
||||||
|
|
4
ecs.php
4
ecs.php
|
@ -13,7 +13,5 @@ return ECSConfig::configure()
|
||||||
__DIR__ . '/tests',
|
__DIR__ . '/tests',
|
||||||
])
|
])
|
||||||
->withRootFiles()
|
->withRootFiles()
|
||||||
->withRules([
|
->withRules([FinalClassFixer::class])
|
||||||
FinalClassFixer::class,
|
|
||||||
])
|
|
||||||
->withSets([LubiSetList::ECS]);
|
->withSets([LubiSetList::ECS]);
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the importmap for this application.
|
|
||||||
*
|
|
||||||
* - "path" is a path inside the asset mapper system. Use the
|
|
||||||
* "debug:asset-map" command to see the full list of paths.
|
|
||||||
*
|
|
||||||
* - "entrypoint" (JavaScript only) set to true for any module that will
|
|
||||||
* be used as an "entrypoint" (and passed to the importmap() Twig function).
|
|
||||||
*
|
|
||||||
* The "importmap:require" command can be used to add new entries to this file.
|
|
||||||
*/
|
|
||||||
return [
|
|
||||||
'app' => [
|
|
||||||
'path' => './assets/app.js',
|
|
||||||
'entrypoint' => true,
|
|
||||||
],
|
|
||||||
'bootstrap' => [
|
|
||||||
'version' => '5.3.7',
|
|
||||||
],
|
|
||||||
'@popperjs/core' => [
|
|
||||||
'version' => '2.11.8',
|
|
||||||
],
|
|
||||||
'bootstrap/dist/css/bootstrap.min.css' => [
|
|
||||||
'version' => '5.3.7',
|
|
||||||
'type' => 'css',
|
|
||||||
],
|
|
||||||
'htmx.org' => [
|
|
||||||
'version' => '2.0.5',
|
|
||||||
],
|
|
||||||
];
|
|
20
infection.json5
Normal file
20
infection.json5
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"$schema": "vendor/infection/infection/resources/schema.json",
|
||||||
|
"source": {
|
||||||
|
"directories": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timeout": 30,
|
||||||
|
"logs": {
|
||||||
|
"text": "var/log/infection.txt",
|
||||||
|
"summary": "var/log/summary.log",
|
||||||
|
},
|
||||||
|
"mutators": {
|
||||||
|
"@default": true,
|
||||||
|
"global-ignore": [
|
||||||
|
"App\\Service\\Favicon::__toString",
|
||||||
|
"ORM\\Column.*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,47 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20250621131822 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE food_vendor ADD COLUMN emojis VARCHAR(30) DEFAULT NULL
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TEMPORARY TABLE __temp__food_vendor AS SELECT name, phone, menu_link, id FROM food_vendor
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE food_vendor
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TABLE food_vendor (name VARCHAR(50) NOT NULL, phone VARCHAR(50) DEFAULT '', menu_link VARCHAR(255) DEFAULT NULL, id BLOB NOT NULL, PRIMARY KEY(id))
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
INSERT INTO food_vendor (name, phone, menu_link, id) SELECT name, phone, menu_link, id FROM __temp__food_vendor
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE __temp__food_vendor
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20250629160123 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE order_item ADD COLUMN is_paid BOOLEAN DEFAULT 0 NOT NULL
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE order_item ADD COLUMN price_cents INTEGER DEFAULT 0 NOT NULL
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TEMPORARY TABLE __temp__order_item AS SELECT name, extras, created_by, id, food_order_id, menu_item_id FROM order_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE order_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TABLE order_item (name VARCHAR(255) NOT NULL, extras VARCHAR(255) DEFAULT NULL, created_by VARCHAR(255) DEFAULT 'nobody' NOT NULL, id BLOB NOT NULL, food_order_id BLOB DEFAULT NULL, menu_item_id BLOB NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_52EA1F09A5D24A7A FOREIGN KEY (food_order_id) REFERENCES food_order (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_52EA1F099AB44FE0 FOREIGN KEY (menu_item_id) REFERENCES menu_item (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
INSERT INTO order_item (name, extras, created_by, id, food_order_id, menu_item_id) SELECT name, extras, created_by, id, food_order_id, menu_item_id FROM __temp__order_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE __temp__order_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE INDEX IDX_52EA1F09A5D24A7A ON order_item (food_order_id)
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE INDEX IDX_52EA1F099AB44FE0 ON order_item (menu_item_id)
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace DoctrineMigrations;
|
|
||||||
|
|
||||||
use Doctrine\DBAL\Schema\Schema;
|
|
||||||
use Doctrine\Migrations\AbstractMigration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-generated Migration: Please modify to your needs!
|
|
||||||
*/
|
|
||||||
final class Version20250629160639 extends AbstractMigration
|
|
||||||
{
|
|
||||||
public function getDescription(): string
|
|
||||||
{
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function up(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this up() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
ALTER TABLE menu_item ADD COLUMN price_cents INTEGER DEFAULT 0 NOT NULL
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function down(Schema $schema): void
|
|
||||||
{
|
|
||||||
// this down() migration is auto-generated, please modify it to your needs
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TEMPORARY TABLE __temp__menu_item AS SELECT name, deleted_at, id, food_vendor_id, alias_of_id FROM menu_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE menu_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE TABLE menu_item (name VARCHAR(255) NOT NULL, deleted_at DATETIME DEFAULT NULL, id BLOB NOT NULL, food_vendor_id BLOB NOT NULL, alias_of_id BLOB DEFAULT NULL, PRIMARY KEY(id), CONSTRAINT FK_D754D5506EF983E8 FOREIGN KEY (food_vendor_id) REFERENCES food_vendor (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_D754D55061F0AFC5 FOREIGN KEY (alias_of_id) REFERENCES menu_item (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
INSERT INTO menu_item (name, deleted_at, id, food_vendor_id, alias_of_id) SELECT name, deleted_at, id, food_vendor_id, alias_of_id FROM __temp__menu_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
DROP TABLE __temp__menu_item
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE INDEX IDX_D754D5506EF983E8 ON menu_item (food_vendor_id)
|
|
||||||
SQL);
|
|
||||||
$this->addSql(<<<'SQL'
|
|
||||||
CREATE INDEX IDX_D754D55061F0AFC5 ON menu_item (alias_of_id)
|
|
||||||
SQL);
|
|
||||||
}
|
|
||||||
}
|
|
20
php-styler.php
Normal file
20
php-styler.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use PhpStyler\Config;
|
||||||
|
use PhpStyler\Files;
|
||||||
|
use PhpStyler\Styler;
|
||||||
|
|
||||||
|
return new Config(
|
||||||
|
styler: new Styler(lineLen: 79),
|
||||||
|
files: new Files(
|
||||||
|
__DIR__ . '/bin',
|
||||||
|
__DIR__ . '/public',
|
||||||
|
__DIR__ . '/src',
|
||||||
|
__DIR__ . '/config',
|
||||||
|
__DIR__ . '/tests',
|
||||||
|
__DIR__ . '/php-styler.php',
|
||||||
|
__DIR__ . '/ecs.php',
|
||||||
|
__DIR__ . '/rector.php',
|
||||||
|
),
|
||||||
|
cache: __DIR__ . '/.php-styler.cache',
|
||||||
|
);
|
38
phpunit.xml
38
phpunit.xml
|
@ -1,38 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
|
||||||
bootstrap="tests/bootstrap.php"
|
|
||||||
cacheDirectory=".phpunit.cache"
|
|
||||||
executionOrder="depends,defects"
|
|
||||||
shortenArraysForExportThreshold="10"
|
|
||||||
requireCoverageMetadata="false"
|
|
||||||
beStrictAboutCoverageMetadata="true"
|
|
||||||
beStrictAboutOutputDuringTests="true"
|
|
||||||
displayDetailsOnPhpunitDeprecations="true"
|
|
||||||
failOnPhpunitDeprecation="true"
|
|
||||||
failOnRisky="true"
|
|
||||||
failOnWarning="true">
|
|
||||||
<testsuites>
|
|
||||||
<testsuite name="default">
|
|
||||||
<directory>tests/Feature</directory>
|
|
||||||
<directory>tests/Unit</directory>
|
|
||||||
</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
|
|
||||||
<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
|
|
||||||
<include>
|
|
||||||
<directory>src</directory>
|
|
||||||
</include>
|
|
||||||
<exclude>
|
|
||||||
<file>src/Kernel.php</file>
|
|
||||||
<file>src/Service/Favicon.php</file>
|
|
||||||
</exclude>
|
|
||||||
</source>
|
|
||||||
<php>
|
|
||||||
<ini name="display_errors" value="1" />
|
|
||||||
<ini name="error_reporting" value="-1" />
|
|
||||||
<server name="KERNEL_CLASS" value="App\Kernel" />
|
|
||||||
<server name="APP_ENV" value="test" force="true" />
|
|
||||||
<server name="SHELL_VERBOSITY" value="-1" />
|
|
||||||
</php>
|
|
||||||
</phpunit>
|
|
|
@ -6,7 +6,9 @@
|
||||||
backupGlobals="false"
|
backupGlobals="false"
|
||||||
colors="true"
|
colors="true"
|
||||||
bootstrap="tests/bootstrap.php"
|
bootstrap="tests/bootstrap.php"
|
||||||
convertDeprecationsToExceptions="false"
|
convertDeprecationsToExceptions="true"
|
||||||
|
executionOrder="random"
|
||||||
|
resolveDependencies="true"
|
||||||
>
|
>
|
||||||
<php>
|
<php>
|
||||||
<ini name="display_errors" value="1" />
|
<ini name="display_errors" value="1" />
|
||||||
|
@ -14,7 +16,8 @@
|
||||||
<server name="APP_ENV" value="test" force="true" />
|
<server name="APP_ENV" value="test" force="true" />
|
||||||
<server name="SHELL_VERBOSITY" value="-1" />
|
<server name="SHELL_VERBOSITY" value="-1" />
|
||||||
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
||||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
|
<server name="SYMFONY_PHPUNIT_VERSION" value="9.6" />
|
||||||
|
<server name="KERNEL_CLASS" value="App\Kernel" />
|
||||||
</php>
|
</php>
|
||||||
|
|
||||||
<testsuites>
|
<testsuites>
|
||||||
|
|
1
public/static/css/new.min.css
vendored
Normal file
1
public/static/css/new.min.css
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
blockquote,header{background:var(--nc-bg-2)}dt,summary,table caption{font-weight:700}img,pre,textarea{max-width:100%}:root{--nc-font-sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--nc-font-mono:Consolas,monaco,'Ubuntu Mono','Liberation Mono','Courier New',Courier,monospace;--nc-tx-1:#000000;--nc-tx-2:#1A1A1A;--nc-bg-1:#FFFFFF;--nc-bg-2:#F6F8FA;--nc-bg-3:#E5E7EB;--nc-lk-1:#0070F3;--nc-lk-2:#0366D6;--nc-lk-tx:#FFFFFF;--nc-ac-1:#79FFE1;--nc-ac-tx:#0C4047;--nc-d-tx-1:#ffffff;--nc-d-tx-2:#eeeeee;--nc-d-bg-1:#000000;--nc-d-bg-2:#111111;--nc-d-bg-3:#222222;--nc-d-lk-1:#3291FF;--nc-d-lk-2:#0070F3;--nc-d-lk-tx:#FFFFFF;--nc-d-ac-1:#7928CA;--nc-d-ac-tx:#FFFFFF}@media (prefers-color-scheme:dark){:root{--nc-tx-1:var(--nc-d-tx-1);--nc-tx-2:var(--nc-d-tx-2);--nc-bg-1:var(--nc-d-bg-1);--nc-bg-2:var(--nc-d-bg-2);--nc-bg-3:var(--nc-d-bg-3);--nc-lk-1:var(--nc-d-lk-1);--nc-lk-2:var(--nc-d-lk-2);--nc-lk-tx:var(--nc--dlk-tx);--nc-ac-1:var(--nc-d-ac-1);--nc-ac-tx:var(--nc--dac-tx)}}*{margin:0;padding:0}address,area,article,aside,audio,blockquote,datalist,details,dl,fieldset,figure,form,iframe,img,input,meter,nav,ol,optgroup,option,output,p,pre,progress,ruby,section,table,textarea,ul,video{margin-bottom:1rem}button,html,input,select{font-family:var(--nc-font-sans)}body{margin:0 auto;max-width:750px;padding:2rem;border-radius:6px;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word;background:var(--nc-bg-1);color:var(--nc-tx-2);font-size:1.03rem;line-height:1.5}::selection{background:var(--nc-ac-1);color:var(--nc-ac-tx)}h1,h2,h3,h4,h5,h6{line-height:1;color:var(--nc-tx-1);padding-top:.875rem}h1,h2,h3{color:var(--nc-tx-1);padding-bottom:2px;margin-bottom:8px;border-bottom:1px solid var(--nc-bg-2)}h4,h5,h6{margin-bottom:.3rem}h1{font-size:2.25rem}h2{font-size:1.85rem}h3{font-size:1.55rem}h4{font-size:1.25rem}h5{font-size:1rem}h6{font-size:.875rem}a{color:var(--nc-lk-1)}a:hover{color:var(--nc-lk-2)}abbr,abbr:hover{cursor:help}blockquote{padding:1.5rem;border-left:5px solid var(--nc-bg-3)}blockquote :last-child{padding-bottom:0;margin-bottom:0}header{border-bottom:1px solid var(--nc-bg-3);padding:2rem 1.5rem;margin:-2rem calc(50% - 50vw) 2rem;padding-left:calc(50vw - 50%);padding-right:calc(50vw - 50%)}header h1,header h2,header h3{padding-bottom:0;border-bottom:0}header>:first-child{margin-top:0;padding-top:0}a img,details[open]>:last-child,header>:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}a button,button,input[type=button],input[type=reset],input[type=submit]{font-size:1rem;display:inline-block;padding:6px 12px;text-align:center;text-decoration:none;white-space:nowrap;background:var(--nc-lk-1);color:var(--nc-lk-tx);border:0;border-radius:4px;box-sizing:border-box;cursor:pointer;color:var(--nc-lk-tx)}a button[disabled],button[disabled],input[type=button][disabled],input[type=reset][disabled],input[type=submit][disabled]{opacity:.5;cursor:not-allowed}.button:enabled:hover,.button:focus,button:enabled:hover,button:focus,input[type=button]:enabled:hover,input[type=button]:focus,input[type=reset]:enabled:hover,input[type=reset]:focus,input[type=submit]:enabled:hover,input[type=submit]:focus{background:var(--nc-lk-2)}code,details,input,kbd,pre,samp,select,textarea,th,tr:nth-child(2n){background:var(--nc-bg-2)}code,kbd,pre,samp{font-family:var(--nc-font-mono);border:1px solid var(--nc-bg-3);border-radius:4px;padding:3px 6px;font-size:.9em}code pre,pre code{background:inherit;font-size:inherit;color:inherit;border:0;padding:0;margin:0}details,fieldset{border:1px solid var(--nc-bg-3)}kbd{border-bottom:3px solid var(--nc-bg-3)}pre{padding:1rem 1.4rem;overflow:auto}code pre{display:inline}details{padding:.6rem 1rem;border-radius:4px}summary{cursor:pointer}details[open]{padding-bottom:.75rem}details[open] summary{margin-bottom:6px}dd::before{content:'→ '}hr{border:0;border-bottom:1px solid var(--nc-bg-3);margin:1rem auto}fieldset{margin-top:1rem;padding:2rem;border-radius:4px}input,select,td,textarea,th{border:1px solid var(--nc-bg-3)}legend{padding:auto .5rem}table{border-collapse:collapse;width:100%}td,th{text-align:left;padding:.5rem}table caption{margin-bottom:.5rem}ol,ul{padding-left:2rem}li{margin-top:.4rem}mark{padding:3px 6px;background:var(--nc-ac-1);color:var(--nc-ac-tx)}input,select,textarea{padding:6px 12px;margin-bottom:.5rem;color:var(--nc-tx-2);border-radius:4px;box-shadow:none;box-sizing:border-box}
|
1
public/static/css/simple.min.css
vendored
Normal file
1
public/static/css/simple.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/static/css/water.min.css
vendored
Normal file
1
public/static/css/water.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/static/js/htmx.min.js
vendored
Normal file
1
public/static/js/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,6 @@ namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\FoodOrder;
|
use App\Entity\FoodOrder;
|
||||||
use App\Form\FoodOrderType;
|
use App\Form\FoodOrderType;
|
||||||
use App\Form\OrderFinalize;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
use App\Repository\FoodOrderRepository;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
@ -15,6 +14,22 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||||
#[Route('/food/order')]
|
#[Route('/food/order')]
|
||||||
final class FoodOrderController extends AbstractController
|
final class FoodOrderController extends AbstractController
|
||||||
{
|
{
|
||||||
|
#[Route(
|
||||||
|
path: '/list',
|
||||||
|
name: 'app_food_order_index',
|
||||||
|
methods: ['GET']
|
||||||
|
)]
|
||||||
|
public function index(FoodOrderRepository $foodOrderRepository): Response
|
||||||
|
{
|
||||||
|
|
||||||
|
return $this->render('food_order/index.html.twig', [
|
||||||
|
'food_orders' => $foodOrderRepository->findLatestEntries(days: 3),
|
||||||
|
'current_page' => 0,
|
||||||
|
'next_page' => 0,
|
||||||
|
'prev_page' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route(
|
#[Route(
|
||||||
path: '/list/archive/{page}',
|
path: '/list/archive/{page}',
|
||||||
name: 'app_food_order_archive',
|
name: 'app_food_order_archive',
|
||||||
|
@ -45,32 +60,6 @@ final class FoodOrderController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}/close', name: 'app_food_order_close', methods: ['GET'])]
|
|
||||||
public function close(FoodOrder $foodOrder, FoodOrderRepository $repository): Response
|
|
||||||
{
|
|
||||||
$foodOrder->close();
|
|
||||||
$repository->save();
|
|
||||||
return $this->redirectToRoute('app_food_order_show', [
|
|
||||||
'id' => $foodOrder->getId(),
|
|
||||||
], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route(
|
|
||||||
path: '/list',
|
|
||||||
name: 'app_food_order_index',
|
|
||||||
methods: ['GET']
|
|
||||||
)]
|
|
||||||
public function index(FoodOrderRepository $foodOrderRepository): Response
|
|
||||||
{
|
|
||||||
|
|
||||||
return $this->render('food_order/index.html.twig', [
|
|
||||||
'food_orders' => $foodOrderRepository->findLatestEntries(days: 3),
|
|
||||||
'current_page' => 0,
|
|
||||||
'next_page' => 0,
|
|
||||||
'prev_page' => 0,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/new', name: 'app_food_order_new', methods: ['GET', 'POST'])]
|
#[Route('/new', name: 'app_food_order_new', methods: ['GET', 'POST'])]
|
||||||
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
public function new(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
|
@ -95,6 +84,24 @@ final class FoodOrderController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}', name: 'app_food_order_show', methods: ['GET'])]
|
||||||
|
public function show(FoodOrder $foodOrder): Response
|
||||||
|
{
|
||||||
|
return $this->render('food_order/show.html.twig', [
|
||||||
|
'food_order' => $foodOrder,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/close', name: 'app_food_order_close', methods: ['GET'])]
|
||||||
|
public function close(FoodOrder $foodOrder, FoodOrderRepository $repository): Response
|
||||||
|
{
|
||||||
|
$foodOrder->close();
|
||||||
|
$repository->save();
|
||||||
|
return $this->redirectToRoute('app_food_order_show', [
|
||||||
|
'id' => $foodOrder->getId(),
|
||||||
|
], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/{id}/open', name: 'app_food_order_open', methods: ['GET'])]
|
#[Route('/{id}/open', name: 'app_food_order_open', methods: ['GET'])]
|
||||||
public function open(FoodOrder $foodOrder, FoodOrderRepository $repository): Response
|
public function open(FoodOrder $foodOrder, FoodOrderRepository $repository): Response
|
||||||
{
|
{
|
||||||
|
@ -104,22 +111,4 @@ final class FoodOrderController extends AbstractController
|
||||||
'id' => $foodOrder->getId(),
|
'id' => $foodOrder->getId(),
|
||||||
], Response::HTTP_SEE_OTHER);
|
], Response::HTTP_SEE_OTHER);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_food_order_show', methods: ['GET', 'POST'])]
|
|
||||||
public function show(Request $request, FoodOrder $foodOrder, EntityManagerInterface $entityManager): Response
|
|
||||||
{
|
|
||||||
$form = null;
|
|
||||||
if ($foodOrder->isClosed()) {
|
|
||||||
$form = $this->createForm(OrderFinalize::class, $foodOrder);
|
|
||||||
$form->handleRequest($request);
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$entityManager->persist($foodOrder);
|
|
||||||
$entityManager->flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $this->render('food_order/show.html.twig', [
|
|
||||||
'food_order' => $foodOrder,
|
|
||||||
'form' => $form,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,23 +14,6 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||||
#[Route('/food/vendor')]
|
#[Route('/food/vendor')]
|
||||||
final class FoodVendorController extends AbstractController
|
final class FoodVendorController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route('/{id}/edit', name: 'app_food_vendor_edit', methods: ['GET', 'POST'])]
|
|
||||||
public function edit(Request $request, FoodVendor $foodVendor, EntityManagerInterface $entityManager): Response
|
|
||||||
{
|
|
||||||
$form = $this->createForm(FoodVendorType::class, $foodVendor);
|
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$entityManager->flush();
|
|
||||||
|
|
||||||
return $this->redirectToRoute('app_food_vendor_index', [], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render('food_vendor/edit.html.twig', [
|
|
||||||
'form' => $form,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/', name: 'app_food_vendor_index', methods: ['GET'])]
|
#[Route('/', name: 'app_food_vendor_index', methods: ['GET'])]
|
||||||
public function index(FoodVendorRepository $foodVendorRepository): Response
|
public function index(FoodVendorRepository $foodVendorRepository): Response
|
||||||
{
|
{
|
||||||
|
@ -68,4 +51,21 @@ final class FoodVendorController extends AbstractController
|
||||||
'food_vendor' => $foodVendor,
|
'food_vendor' => $foodVendor,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Route('/{id}/edit', name: 'app_food_vendor_edit', methods: ['GET', 'POST'])]
|
||||||
|
public function edit(Request $request, FoodVendor $foodVendor, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(FoodVendorType::class, $foodVendor);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_food_vendor_index', [], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('food_vendor/edit.html.twig', [
|
||||||
|
'form' => $form,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,12 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||||
#[Route('/menu/item')]
|
#[Route('/menu/item')]
|
||||||
final class MenuItemController extends AbstractController
|
final class MenuItemController extends AbstractController
|
||||||
{
|
{
|
||||||
#[Route('/{id}', name: 'app_menu_item_delete', methods: ['POST'])]
|
#[Route('/{id}', name: 'app_menu_item_show', methods: ['GET'])]
|
||||||
public function delete(Request $request, MenuItem $menuItem, EntityManagerInterface $entityManager): Response
|
public function show(MenuItem $menuItem): Response
|
||||||
{
|
{
|
||||||
if ($this->isCsrfTokenValid('delete' . $menuItem->getId(), $request->getPayload()->getString('_token'))) {
|
return $this->render('menu_item/show.html.twig', [
|
||||||
$menuItem->delete();
|
'menu_item' => $menuItem,
|
||||||
$entityManager->flush();
|
]);
|
||||||
}
|
|
||||||
|
|
||||||
return $this->redirectToRoute('app_food_vendor_show', [
|
|
||||||
'id' => $menuItem->getFoodVendor()
|
|
||||||
->getId(),
|
|
||||||
], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}/edit', name: 'app_menu_item_edit', methods: ['GET', 'POST'])]
|
#[Route('/{id}/edit', name: 'app_menu_item_edit', methods: ['GET', 'POST'])]
|
||||||
|
@ -58,11 +52,17 @@ final class MenuItemController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/{id}', name: 'app_menu_item_show', methods: ['GET'])]
|
#[Route('/{id}', name: 'app_menu_item_delete', methods: ['POST'])]
|
||||||
public function show(MenuItem $menuItem): Response
|
public function delete(Request $request, MenuItem $menuItem, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
return $this->render('menu_item/show.html.twig', [
|
if ($this->isCsrfTokenValid('delete' . $menuItem->getId(), $request->getPayload()->getString('_token'))) {
|
||||||
'menu_item' => $menuItem,
|
$menuItem->delete();
|
||||||
]);
|
$entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_food_vendor_show', [
|
||||||
|
'id' => $menuItem->getFoodVendor()
|
||||||
|
->getId(),
|
||||||
|
], Response::HTTP_SEE_OTHER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\FoodOrder;
|
use App\Entity\FoodOrder;
|
||||||
|
use App\Entity\MenuItem;
|
||||||
use App\Entity\OrderItem;
|
use App\Entity\OrderItem;
|
||||||
use App\Form\OrderItemType;
|
use App\Form\OrderItemType;
|
||||||
use App\Repository\MenuItemRepository;
|
use App\Repository\MenuItemRepository;
|
||||||
|
@ -15,6 +16,62 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||||
#[Route('/order/item')]
|
#[Route('/order/item')]
|
||||||
final class OrderItemController extends AbstractController
|
final class OrderItemController extends AbstractController
|
||||||
{
|
{
|
||||||
|
#[Route('/new/{foodOrder}', name: 'app_order_item_new', methods: ['GET', 'POST'])]
|
||||||
|
public function new(Request $request, FoodOrder $foodOrder, EntityManagerInterface $entityManager, MenuItemRepository $menuItemRepository): Response
|
||||||
|
{
|
||||||
|
if ($foodOrder->isClosed()) {
|
||||||
|
return $this->redirectToRoute('app_food_order_show', [
|
||||||
|
'id' => $foodOrder->getId(),
|
||||||
|
], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderItem = new OrderItem;
|
||||||
|
$username = $request->cookies->get('username', 'nobody');
|
||||||
|
$orderItem->setCreatedBy($username);
|
||||||
|
|
||||||
|
$form = $this->createForm(OrderItemType::class, $orderItem);
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$menuItem = $menuItemRepository->findOneBy([
|
||||||
|
'name' => $orderItem->getName(),
|
||||||
|
'foodVendor' => $foodOrder->getFoodVendor(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($menuItem === null) {
|
||||||
|
$menuItem = new MenuItem;
|
||||||
|
$menuItem->setName($orderItem->getName());
|
||||||
|
$menuItem->setFoodVendor($foodOrder->getFoodVendor());
|
||||||
|
$entityManager->persist($menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($menuItem->getAliasOf() !== null) {
|
||||||
|
$menuItem = $menuItem->getAliasOf();
|
||||||
|
$orderItem->setName($menuItem->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderItem->setMenuItem($menuItem);
|
||||||
|
$orderItem->setFoodOrder($foodOrder);
|
||||||
|
$entityManager->persist($orderItem);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('app_food_order_show', [
|
||||||
|
'id' => $foodOrder->getId(),
|
||||||
|
], Response::HTTP_SEE_OTHER);
|
||||||
|
}
|
||||||
|
$menuItems = $menuItemRepository->findBy([
|
||||||
|
'foodVendor' => $foodOrder->getFoodVendor(),
|
||||||
|
'deletedAt' => null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->render('order_item/new.html.twig', [
|
||||||
|
'order_item' => $orderItem,
|
||||||
|
'food_order' => $foodOrder,
|
||||||
|
'form' => $form,
|
||||||
|
'menuItems' => $menuItems,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
#[Route('/{id}/copy', name: 'app_order_item_copy', methods: ['GET'])]
|
#[Route('/{id}/copy', name: 'app_order_item_copy', methods: ['GET'])]
|
||||||
public function copy(OrderItem $orderItem, EntityManagerInterface $entityManager): Response
|
public function copy(OrderItem $orderItem, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
|
@ -38,23 +95,6 @@ final class OrderItemController extends AbstractController
|
||||||
], Response::HTTP_SEE_OTHER);
|
], Response::HTTP_SEE_OTHER);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/delete/{id}', name: 'app_order_item_delete')]
|
|
||||||
public function delete(OrderItem $orderItem, EntityManagerInterface $entityManager): Response
|
|
||||||
{
|
|
||||||
$foodOrder = $orderItem->getFoodOrder();
|
|
||||||
if ($foodOrder->isClosed()) {
|
|
||||||
return $this->redirectToRoute('app_food_order_show', [
|
|
||||||
'id' => $foodOrder->getId(),
|
|
||||||
], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
|
||||||
$entityManager->remove($orderItem);
|
|
||||||
$entityManager->flush();
|
|
||||||
return $this->redirectToRoute('app_food_order_show', [
|
|
||||||
'id' => $orderItem->getFoodOrder()
|
|
||||||
->getId(),
|
|
||||||
], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/{id}/edit', name: 'app_order_item_edit', methods: ['GET', 'POST'])]
|
#[Route('/{id}/edit', name: 'app_order_item_edit', methods: ['GET', 'POST'])]
|
||||||
public function edit(
|
public function edit(
|
||||||
Request $request,
|
Request $request,
|
||||||
|
@ -74,6 +114,19 @@ final class OrderItemController extends AbstractController
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
|
$menuItem = $menuItemRepository->findOneBy([
|
||||||
|
'name' => $orderItem->getName(),
|
||||||
|
'foodVendor' => $foodOrder->getFoodVendor(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($menuItem === null) {
|
||||||
|
$menuItem = new MenuItem;
|
||||||
|
$menuItem->setName($orderItem->getName());
|
||||||
|
$menuItem->setFoodVendor($foodOrder->getFoodVendor());
|
||||||
|
$entityManager->persist($menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderItem->setMenuItem($menuItem);
|
||||||
$orderItem->setFoodOrder($foodOrder);
|
$orderItem->setFoodOrder($foodOrder);
|
||||||
$entityManager->persist($orderItem);
|
$entityManager->persist($orderItem);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
|
@ -90,41 +143,20 @@ final class OrderItemController extends AbstractController
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Route('/new/{foodOrder}', name: 'app_order_item_new', methods: ['GET', 'POST'])]
|
#[Route('/delete/{id}', name: 'app_order_item_delete')]
|
||||||
public function new(Request $request, FoodOrder $foodOrder, EntityManagerInterface $entityManager, MenuItemRepository $menuItemRepository): Response
|
public function delete(OrderItem $orderItem, EntityManagerInterface $entityManager): Response
|
||||||
{
|
{
|
||||||
|
$foodOrder = $orderItem->getFoodOrder();
|
||||||
if ($foodOrder->isClosed()) {
|
if ($foodOrder->isClosed()) {
|
||||||
return $this->redirectToRoute('app_food_order_show', [
|
return $this->redirectToRoute('app_food_order_show', [
|
||||||
'id' => $foodOrder->getId(),
|
'id' => $foodOrder->getId(),
|
||||||
], Response::HTTP_SEE_OTHER);
|
], Response::HTTP_SEE_OTHER);
|
||||||
}
|
}
|
||||||
|
$entityManager->remove($orderItem);
|
||||||
$orderItem = new OrderItem;
|
$entityManager->flush();
|
||||||
$username = $request->cookies->get('username', 'nobody');
|
return $this->redirectToRoute('app_food_order_show', [
|
||||||
$orderItem->setCreatedBy($username);
|
'id' => $orderItem->getFoodOrder()
|
||||||
|
->getId(),
|
||||||
$form = $this->createForm(OrderItemType::class, $orderItem);
|
], Response::HTTP_SEE_OTHER);
|
||||||
$form->handleRequest($request);
|
|
||||||
|
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
|
||||||
$orderItem->setFoodOrder($foodOrder);
|
|
||||||
$entityManager->persist($orderItem);
|
|
||||||
$entityManager->flush();
|
|
||||||
|
|
||||||
return $this->redirectToRoute('app_food_order_show', [
|
|
||||||
'id' => $foodOrder->getId(),
|
|
||||||
], Response::HTTP_SEE_OTHER);
|
|
||||||
}
|
|
||||||
$menuItems = $menuItemRepository->findBy([
|
|
||||||
'foodVendor' => $foodOrder->getFoodVendor(),
|
|
||||||
'deletedAt' => null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $this->render('order_item/new.html.twig', [
|
|
||||||
'order_item' => $orderItem,
|
|
||||||
'food_order' => $foodOrder,
|
|
||||||
'form' => $form,
|
|
||||||
'menuItems' => $menuItems,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,8 @@
|
||||||
|
|
||||||
namespace App\DataFixtures;
|
namespace App\DataFixtures;
|
||||||
|
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use App\Entity\FoodVendor;
|
use App\Entity\FoodVendor;
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
use App\Entity\OrderItem;
|
|
||||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||||
use Doctrine\Persistence\ObjectManager;
|
use Doctrine\Persistence\ObjectManager;
|
||||||
use Override;
|
use Override;
|
||||||
|
@ -16,29 +14,15 @@ final class AppFixtures extends Fixture
|
||||||
{
|
{
|
||||||
private ObjectManager $manager;
|
private ObjectManager $manager;
|
||||||
|
|
||||||
public function addMenuItemsToVendor(FoodVendor $vendor): void
|
#[Override]
|
||||||
|
public function load(ObjectManager $manager): void
|
||||||
{
|
{
|
||||||
$menuItems = [];
|
$this->manager = $manager;
|
||||||
foreach (range(1, 10) as $i) {
|
$vendorA = $this->createVendor('Vendor A');
|
||||||
$item = new MenuItem;
|
$this->addMenuItemsToVendor($vendorA);
|
||||||
$item->setName("{$vendor->getName()} Item {$i}");
|
|
||||||
$item->setFoodVendor($vendor);
|
|
||||||
$this->manager->persist($item);
|
|
||||||
$this->manager->flush();
|
|
||||||
$menuItems[] = $item;
|
|
||||||
}
|
|
||||||
|
|
||||||
$order = new FoodOrder;
|
$vendorB = $this->createVendor('Vendor B');
|
||||||
$order->setFoodVendor($vendor);
|
$this->addMenuItemsToVendor($vendorB);
|
||||||
|
|
||||||
$this->manager->persist($order);
|
|
||||||
foreach ($menuItems as $item) {
|
|
||||||
$orderItem = new OrderItem;
|
|
||||||
$orderItem->setMenuItem($item);
|
|
||||||
$orderItem->setCreatedBy('John');
|
|
||||||
$order->addOrderItem($orderItem);
|
|
||||||
$this->manager->persist($orderItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createVendor(string $name): FoodVendor
|
public function createVendor(string $name): FoodVendor
|
||||||
|
@ -53,14 +37,14 @@ final class AppFixtures extends Fixture
|
||||||
return $vendorA;
|
return $vendorA;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Override]
|
public function addMenuItemsToVendor(FoodVendor $vendor): void
|
||||||
public function load(ObjectManager $manager): void
|
|
||||||
{
|
{
|
||||||
$this->manager = $manager;
|
foreach (range(1, 10) as $i) {
|
||||||
$vendorA = $this->createVendor('Vendor A');
|
$item = new MenuItem;
|
||||||
$this->addMenuItemsToVendor($vendorA);
|
$item->setName("{$vendor->getName()} Item {$i}");
|
||||||
|
$item->setFoodVendor($vendor);
|
||||||
$vendorB = $this->createVendor('Vendor B');
|
$this->manager->persist($item);
|
||||||
$this->addMenuItemsToVendor($vendorB);
|
$this->manager->flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,97 +2,55 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use ApiPlatform\Metadata\Delete;
|
|
||||||
use ApiPlatform\Metadata\Get;
|
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use ApiPlatform\Metadata\Put;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
use App\Repository\FoodOrderRepository;
|
||||||
use App\State\LatestOrderProvider;
|
|
||||||
use App\State\OpenOrdersProvider;
|
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Serializer\Annotation\Groups;
|
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
use function iterator_to_array;
|
use function iterator_to_array;
|
||||||
|
|
||||||
#[ApiResource(
|
|
||||||
operations: [
|
|
||||||
new GetCollection(
|
|
||||||
uriTemplate: 'food_orders/open',
|
|
||||||
description: 'Get only open orders',
|
|
||||||
provider: OpenOrdersProvider::class,
|
|
||||||
),
|
|
||||||
new Get(
|
|
||||||
uriTemplate: 'food_orders/latest',
|
|
||||||
description: 'Get the latest created order',
|
|
||||||
provider: LatestOrderProvider::class,
|
|
||||||
normalizationContext: [
|
|
||||||
'groups' => ['food_order:read', 'food_order:latest'],
|
|
||||||
]
|
|
||||||
),
|
|
||||||
new GetCollection,
|
|
||||||
new Get,
|
|
||||||
new Post,
|
|
||||||
new Put,
|
|
||||||
new Delete,
|
|
||||||
]
|
|
||||||
)]
|
|
||||||
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
|
#[ORM\Entity(repositoryClass: FoodOrderRepository::class)]
|
||||||
class FoodOrder
|
class FoodOrder
|
||||||
{
|
{
|
||||||
#[Groups(['food_order:read'])]
|
|
||||||
#[ORM\Column(nullable: true)]
|
#[ORM\Column(nullable: true)]
|
||||||
private DateTimeImmutable|null $closedAt = null;
|
private DateTimeImmutable|null $closedAt = null;
|
||||||
|
|
||||||
#[Groups(['food_order:read'])]
|
|
||||||
#[ORM\Column(length: 255, options: [
|
|
||||||
'default' => 'nobody',
|
|
||||||
])]
|
|
||||||
private string|null $createdBy = 'nobody';
|
|
||||||
|
|
||||||
#[Groups(['food_order:read', 'food_order:latest'])]
|
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'foodOrders')]
|
#[ORM\ManyToOne(inversedBy: 'foodOrders')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
private FoodVendor|null $foodVendor = null;
|
private FoodVendor|null $foodVendor = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, OrderItem>
|
* @var Collection<int, OrderItem>
|
||||||
*/
|
*/
|
||||||
#[Groups(['food_order:read', 'food_order:latest'])]
|
|
||||||
#[ORM\OneToMany(targetEntity: OrderItem::class, mappedBy: 'foodOrder', orphanRemoval: true)]
|
#[ORM\OneToMany(targetEntity: OrderItem::class, mappedBy: 'foodOrder', orphanRemoval: true)]
|
||||||
private Collection $orderItems;
|
private Collection $orderItems;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, options: [
|
||||||
|
'default' => 'nobody',
|
||||||
|
])]
|
||||||
|
private string|null $createdBy = 'nobody';
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Groups(['food_order:read'])]
|
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
private Ulid|null $id = new Ulid
|
private Ulid|null $id = new Ulid
|
||||||
) {
|
) {
|
||||||
$this->id ??= new Ulid;
|
|
||||||
$this->orderItems = new ArrayCollection;
|
$this->orderItems = new ArrayCollection;
|
||||||
$this->open();
|
$this->open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addOrderItem(OrderItem $orderItem): static
|
public function getId(): Ulid|null
|
||||||
{
|
{
|
||||||
if (! $this->orderItems->contains($orderItem)) {
|
return $this->id;
|
||||||
$this->orderItems->add($orderItem);
|
|
||||||
$orderItem->setFoodOrder($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close(): static
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->setClosedAt(new DateTimeImmutable);
|
return $this->id->getDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getClosedAt(): DateTimeImmutable|null
|
public function getClosedAt(): DateTimeImmutable|null
|
||||||
|
@ -100,15 +58,30 @@ class FoodOrder
|
||||||
return $this->closedAt;
|
return $this->closedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Groups(['food_order:read'])]
|
public function setClosedAt(DateTimeImmutable|null $closedAt = null): static
|
||||||
public function getCreatedAt(): DateTimeImmutable
|
|
||||||
{
|
{
|
||||||
return $this->id->getDateTime();
|
$this->closedAt = $closedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedBy(): string|null
|
public function isClosed(): bool
|
||||||
{
|
{
|
||||||
return $this->createdBy;
|
if (! $this->closedAt instanceof DateTimeImmutable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $this->closedAt < new DateTimeImmutable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close(): static
|
||||||
|
{
|
||||||
|
return $this->setClosedAt(new DateTimeImmutable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function open(): static
|
||||||
|
{
|
||||||
|
$this->closedAt = (new DateTimeImmutable)->add(new DateInterval('PT1H'));
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFoodVendor(): FoodVendor|null
|
public function getFoodVendor(): FoodVendor|null
|
||||||
|
@ -116,9 +89,11 @@ class FoodOrder
|
||||||
return $this->foodVendor;
|
return $this->foodVendor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): Ulid|null
|
public function setFoodVendor(FoodVendor|null $foodVendor): static
|
||||||
{
|
{
|
||||||
return $this->id;
|
$this->foodVendor = $foodVendor;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,17 +121,13 @@ class FoodOrder
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isClosed(): bool
|
public function addOrderItem(OrderItem $orderItem): static
|
||||||
{
|
{
|
||||||
if (! $this->closedAt instanceof DateTimeImmutable) {
|
if (! $this->orderItems->contains($orderItem)) {
|
||||||
return false;
|
$this->orderItems->add($orderItem);
|
||||||
|
$orderItem->setFoodOrder($this);
|
||||||
}
|
}
|
||||||
return $this->closedAt < new DateTimeImmutable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function open(): static
|
|
||||||
{
|
|
||||||
$this->closedAt = (new DateTimeImmutable)->add(new DateInterval('PT1H'));
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,11 +141,9 @@ class FoodOrder
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setClosedAt(DateTimeImmutable|null $closedAt = null): static
|
public function getCreatedBy(): string|null
|
||||||
{
|
{
|
||||||
$this->closedAt = $closedAt;
|
return $this->createdBy;
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setCreatedBy(string $createdBy): static
|
public function setCreatedBy(string $createdBy): static
|
||||||
|
@ -183,11 +152,4 @@ class FoodOrder
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setFoodVendor(FoodVendor|null $foodVendor): static
|
|
||||||
{
|
|
||||||
$this->foodVendor = $foodVendor;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,31 +2,24 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use App\Repository\FoodVendorRepository;
|
use App\Repository\FoodVendorRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use InvalidArgumentException;
|
|
||||||
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Serializer\Annotation\Groups;
|
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
use Symfony\Component\Validator\Constraints\Length;
|
|
||||||
|
|
||||||
use function mb_strlen;
|
|
||||||
|
|
||||||
#[ApiResource]
|
|
||||||
#[ORM\Entity(repositoryClass: FoodVendorRepository::class)]
|
#[ORM\Entity(repositoryClass: FoodVendorRepository::class)]
|
||||||
class FoodVendor
|
class FoodVendor
|
||||||
{
|
{
|
||||||
/**
|
#[ORM\Column(length: 50)]
|
||||||
* String of emojis (max 30 characters)
|
private string|null $name = null;
|
||||||
*/
|
|
||||||
#[Groups(['food_order:latest', 'food_vendor:read'])]
|
#[ORM\Column(length: 50, nullable: true, options: [
|
||||||
#[Length(max: 10)]
|
'default' => '',
|
||||||
#[ORM\Column(length: 30, nullable: true)]
|
])]
|
||||||
private string|null $emojis = null;
|
private string|null $phone = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, FoodOrder>
|
* @var Collection<int, FoodOrder>
|
||||||
|
@ -40,33 +33,45 @@ class FoodVendor
|
||||||
#[ORM\OneToMany(targetEntity: MenuItem::class, mappedBy: 'foodVendor', orphanRemoval: true)]
|
#[ORM\OneToMany(targetEntity: MenuItem::class, mappedBy: 'foodVendor', orphanRemoval: true)]
|
||||||
private Collection $menuItems;
|
private Collection $menuItems;
|
||||||
|
|
||||||
#[Groups(['food_order:latest'])]
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
private string|null $menuLink = null;
|
private string|null $menuLink = null;
|
||||||
|
|
||||||
#[Groups(['food_order:latest', 'food_vendor:read'])]
|
|
||||||
#[ORM\Column(length: 50)]
|
|
||||||
private string|null $name = null;
|
|
||||||
|
|
||||||
#[Groups(['food_order:latest', 'food_vendor:read'])]
|
|
||||||
#[ORM\Column(length: 50, nullable: true, options: [
|
|
||||||
'default' => '',
|
|
||||||
])]
|
|
||||||
private string|null $phone = null;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
#[Groups(['food_order:latest'])]
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
||||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
|
||||||
#[ORM\Id]
|
|
||||||
private Ulid|null $id = new Ulid
|
private Ulid|null $id = new Ulid
|
||||||
) {
|
) {
|
||||||
$this->id ??= new Ulid;
|
|
||||||
$this->foodOrders = new ArrayCollection;
|
$this->foodOrders = new ArrayCollection;
|
||||||
$this->menuItems = new ArrayCollection;
|
$this->menuItems = new ArrayCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getId(): Ulid|null
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string|null
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setName(string $name): static
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, FoodOrder>
|
||||||
|
*/
|
||||||
|
public function getFoodOrders(): Collection
|
||||||
|
{
|
||||||
|
return $this->foodOrders;
|
||||||
|
}
|
||||||
|
|
||||||
public function addFoodOrder(FoodOrder $foodOrder): static
|
public function addFoodOrder(FoodOrder $foodOrder): static
|
||||||
{
|
{
|
||||||
if (! $this->foodOrders->contains($foodOrder)) {
|
if (! $this->foodOrders->contains($foodOrder)) {
|
||||||
|
@ -77,34 +82,16 @@ class FoodVendor
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addMenuItem(MenuItem $menuItem): static
|
public function removeFoodOrder(FoodOrder $foodOrder): static
|
||||||
{
|
{
|
||||||
if (! $this->menuItems->contains($menuItem)) {
|
// set the owning side to null (unless already changed)
|
||||||
$this->menuItems->add($menuItem);
|
if ($this->foodOrders->removeElement($foodOrder)) {
|
||||||
$menuItem->setFoodVendor($this);
|
$foodOrder->setFoodVendor(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEmojis(): string|null
|
|
||||||
{
|
|
||||||
return $this->emojis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, FoodOrder>
|
|
||||||
*/
|
|
||||||
public function getFoodOrders(): Collection
|
|
||||||
{
|
|
||||||
return $this->foodOrders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): Ulid|null
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, MenuItem>
|
* @return Collection<int, MenuItem>
|
||||||
*/
|
*/
|
||||||
|
@ -118,26 +105,11 @@ class FoodVendor
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMenuLink(): string|null
|
public function addMenuItem(MenuItem $menuItem): static
|
||||||
{
|
{
|
||||||
return $this->menuLink;
|
if (! $this->menuItems->contains($menuItem)) {
|
||||||
}
|
$this->menuItems->add($menuItem);
|
||||||
|
$menuItem->setFoodVendor($this);
|
||||||
public function getName(): string|null
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPhone(): string|null
|
|
||||||
{
|
|
||||||
return $this->phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeFoodOrder(FoodOrder $foodOrder): static
|
|
||||||
{
|
|
||||||
// set the owning side to null (unless already changed)
|
|
||||||
if ($this->foodOrders->removeElement($foodOrder)) {
|
|
||||||
$foodOrder->setFoodVendor(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -153,14 +125,9 @@ class FoodVendor
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setEmojis(string|null $emojis): static
|
public function getMenuLink(): string|null
|
||||||
{
|
{
|
||||||
if ($emojis !== null && mb_strlen($emojis) > 30) {
|
return $this->menuLink;
|
||||||
throw new InvalidArgumentException('A maximum of 30 characters is allowed for emojis');
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->emojis = $emojis;
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMenuLink(string|null $menuLink): static
|
public function setMenuLink(string|null $menuLink): static
|
||||||
|
@ -170,11 +137,9 @@ class FoodVendor
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setName(string $name): static
|
public function getPhone(): string|null
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
return $this->phone;
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPhone(string|null $phone): static
|
public function setPhone(string|null $phone): static
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use App\Repository\MenuItemRepository;
|
use App\Repository\MenuItemRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
@ -11,87 +10,39 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
use Symfony\Component\Validator\Constraints\Positive;
|
|
||||||
|
|
||||||
#[ApiResource]
|
|
||||||
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
|
#[ORM\Entity(repositoryClass: MenuItemRepository::class)]
|
||||||
class MenuItem
|
class MenuItem
|
||||||
{
|
{
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private string|null $name = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'menuItems')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private FoodVendor|null $foodVendor = null;
|
||||||
|
|
||||||
|
#[ORM\Column(nullable: true)]
|
||||||
|
private DateTimeImmutable|null $deletedAt = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'aliases')]
|
||||||
|
private self|null $aliasOf = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Collection<int, self>
|
* @var Collection<int, self>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: self::class, mappedBy: 'aliasOf')]
|
#[ORM\OneToMany(targetEntity: self::class, mappedBy: 'aliasOf')]
|
||||||
private Collection $aliases;
|
private Collection $aliases;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'aliases')]
|
|
||||||
private self|null $aliasOf = null;
|
|
||||||
|
|
||||||
#[ORM\Column(nullable: true)]
|
|
||||||
private DateTimeImmutable|null $deletedAt = null;
|
|
||||||
|
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'menuItems')]
|
|
||||||
private FoodVendor|null $foodVendor = null;
|
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
private string|null $name = null;
|
|
||||||
|
|
||||||
#[ORM\Column(type: 'integer', options: [
|
|
||||||
'default' => 0,
|
|
||||||
])]
|
|
||||||
#[Positive]
|
|
||||||
private int $priceCents = 0;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
||||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
|
||||||
#[ORM\Id]
|
|
||||||
private Ulid|null $id = new Ulid
|
private Ulid|null $id = new Ulid
|
||||||
) {
|
) {
|
||||||
$this->id ??= new Ulid;
|
|
||||||
$this->aliases = new ArrayCollection;
|
$this->aliases = new ArrayCollection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addAlias(self $alias): static
|
|
||||||
{
|
|
||||||
if (! $this->aliases->contains($alias)) {
|
|
||||||
$this->aliases->add($alias);
|
|
||||||
$alias->setAliasOf($this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function delete(): static
|
|
||||||
{
|
|
||||||
$this->setDeletedAt(new DateTimeImmutable);
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Collection<int, self>
|
|
||||||
*/
|
|
||||||
public function getAliases(): Collection
|
|
||||||
{
|
|
||||||
return $this->aliases;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAliasOf(): self|null
|
|
||||||
{
|
|
||||||
return $this->aliasOf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDeletedAt(): DateTimeImmutable|null
|
|
||||||
{
|
|
||||||
return $this->deletedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFoodVendor(): FoodVendor|null
|
|
||||||
{
|
|
||||||
return $this->foodVendor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): Ulid|null
|
public function getId(): Ulid|null
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -102,9 +53,23 @@ class MenuItem
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPriceCents(): int
|
public function setName(string $name): static
|
||||||
{
|
{
|
||||||
return $this->priceCents;
|
$this->name = $name;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFoodVendor(): FoodVendor|null
|
||||||
|
{
|
||||||
|
return $this->foodVendor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFoodVendor(FoodVendor|null $foodVendor): static
|
||||||
|
{
|
||||||
|
$this->foodVendor = $foodVendor;
|
||||||
|
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isDeleted(): bool
|
public function isDeleted(): bool
|
||||||
|
@ -112,6 +77,54 @@ class MenuItem
|
||||||
return $this->getDeletedAt() instanceof DateTimeImmutable;
|
return $this->getDeletedAt() instanceof DateTimeImmutable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete(): static
|
||||||
|
{
|
||||||
|
$this->setDeletedAt(new DateTimeImmutable);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDeletedAt(): DateTimeImmutable|null
|
||||||
|
{
|
||||||
|
return $this->deletedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setDeletedAt(DateTimeImmutable|null $deletedAt = new DateTimeImmutable): static
|
||||||
|
{
|
||||||
|
$this->deletedAt = $deletedAt;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAliasOf(): self|null
|
||||||
|
{
|
||||||
|
return $this->aliasOf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAliasOf(self|null $aliasOf): static
|
||||||
|
{
|
||||||
|
$this->aliasOf = $aliasOf;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, self>
|
||||||
|
*/
|
||||||
|
public function getAliases(): Collection
|
||||||
|
{
|
||||||
|
return $this->aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAlias(self $alias): static
|
||||||
|
{
|
||||||
|
if (! $this->aliases->contains($alias)) {
|
||||||
|
$this->aliases->add($alias);
|
||||||
|
$alias->setAliasOf($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function removeAlias(self $alias): static
|
public function removeAlias(self $alias): static
|
||||||
{
|
{
|
||||||
// set the owning side to null (unless already changed)
|
// set the owning side to null (unless already changed)
|
||||||
|
@ -121,38 +134,4 @@ class MenuItem
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAliasOf(self|null $aliasOf): static
|
|
||||||
{
|
|
||||||
$this->aliasOf = $aliasOf;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDeletedAt(DateTimeImmutable|null $deletedAt = new DateTimeImmutable): static
|
|
||||||
{
|
|
||||||
$this->deletedAt = $deletedAt;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFoodVendor(FoodVendor|null $foodVendor): static
|
|
||||||
{
|
|
||||||
$this->foodVendor = $foodVendor;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setName(string $name): static
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setPriceCents(int $priceCents): self
|
|
||||||
{
|
|
||||||
$this->priceCents = $priceCents;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,140 +2,50 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
|
||||||
use App\Repository\OrderItemRepository;
|
use App\Repository\OrderItemRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Serializer\Annotation\Groups;
|
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
#[ApiResource]
|
|
||||||
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
|
#[ORM\Entity(repositoryClass: OrderItemRepository::class)]
|
||||||
class OrderItem
|
class OrderItem
|
||||||
{
|
{
|
||||||
#[Groups(['food_order:latest'])]
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
||||||
|
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
||||||
|
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
||||||
|
private Ulid|null $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255)]
|
||||||
|
private string|null $name = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
private string|null $extras = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'orderItems')]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
private FoodOrder|null $foodOrder = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
private MenuItem|null $menuItem = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, options: [
|
#[ORM\Column(length: 255, options: [
|
||||||
'default' => 'nobody',
|
'default' => 'nobody',
|
||||||
])]
|
])]
|
||||||
private string|null $createdBy = 'nobody';
|
private string|null $createdBy = 'nobody';
|
||||||
|
|
||||||
#[Groups(['food_order:latest'])]
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
|
||||||
private string|null $extras = null;
|
|
||||||
|
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
|
||||||
#[ORM\ManyToOne(inversedBy: 'orderItems')]
|
|
||||||
private FoodOrder|null $foodOrder = null;
|
|
||||||
|
|
||||||
#[Groups('food_order:latest')]
|
|
||||||
#[ORM\Column(type: 'boolean', options: [
|
|
||||||
'default' => false,
|
|
||||||
])]
|
|
||||||
private bool $isPaid = false;
|
|
||||||
|
|
||||||
#[Groups(['food_order:latest'])]
|
|
||||||
#[ORM\JoinColumn(nullable: false)]
|
|
||||||
#[ORM\ManyToOne]
|
|
||||||
private MenuItem|null $menuItem = null;
|
|
||||||
|
|
||||||
#[Groups(['food_order:latest'])]
|
|
||||||
#[ORM\Column(length: 255)]
|
|
||||||
private string|null $name = null;
|
|
||||||
|
|
||||||
#[Groups('food_order:latest')]
|
|
||||||
#[ORM\Column(type: 'integer', options: [
|
|
||||||
'default' => 0,
|
|
||||||
])]
|
|
||||||
#[Positive]
|
|
||||||
private int $priceCents = 0;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
#[Groups(['food_order:latest'])]
|
|
||||||
#[ORM\Column(type: UlidType::NAME, unique: true)]
|
|
||||||
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
|
|
||||||
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
|
|
||||||
#[ORM\Id]
|
|
||||||
private Ulid|null $id = new Ulid
|
|
||||||
) {
|
|
||||||
$this->id ??= new Ulid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedBy(): string|null
|
|
||||||
{
|
|
||||||
return $this->createdBy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getExtras(): string|null
|
|
||||||
{
|
|
||||||
return $this->extras;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getFoodOrder(): FoodOrder|null
|
|
||||||
{
|
|
||||||
return $this->foodOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): Ulid|null
|
public function getId(): Ulid|null
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMenuItem(): MenuItem|null
|
|
||||||
{
|
|
||||||
return $this->menuItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getName(): string|null
|
public function getName(): string|null
|
||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPriceCents(): int
|
|
||||||
{
|
|
||||||
return $this->priceCents;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isPaid(): bool
|
|
||||||
{
|
|
||||||
return $this->isPaid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setCreatedBy(string $createdBy): static
|
|
||||||
{
|
|
||||||
$this->createdBy = $createdBy;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setExtras(string|null $extras): static
|
|
||||||
{
|
|
||||||
$this->extras = $extras;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFoodOrder(FoodOrder|null $foodOrder): static
|
|
||||||
{
|
|
||||||
$this->foodOrder = $foodOrder;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setIsPaid(bool $isPaid): self
|
|
||||||
{
|
|
||||||
$this->isPaid = $isPaid;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setMenuItem(MenuItem|null $menuItem): static
|
|
||||||
{
|
|
||||||
$this->menuItem = $menuItem;
|
|
||||||
$this->name = $menuItem->getName();
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setName(string $name): static
|
public function setName(string $name): static
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
|
@ -143,9 +53,51 @@ class OrderItem
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPriceCents(int $priceCents): self
|
public function getExtras(): string|null
|
||||||
{
|
{
|
||||||
$this->priceCents = $priceCents;
|
return $this->extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setExtras(string|null $extras): static
|
||||||
|
{
|
||||||
|
$this->extras = $extras;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFoodOrder(): FoodOrder|null
|
||||||
|
{
|
||||||
|
return $this->foodOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFoodOrder(FoodOrder|null $foodOrder): static
|
||||||
|
{
|
||||||
|
$this->foodOrder = $foodOrder;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMenuItem(): MenuItem|null
|
||||||
|
{
|
||||||
|
return $this->menuItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMenuItem(MenuItem|null $menuItem): static
|
||||||
|
{
|
||||||
|
$this->menuItem = $menuItem;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreatedBy(): string|null
|
||||||
|
{
|
||||||
|
return $this->createdBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCreatedBy(string $createdBy): static
|
||||||
|
{
|
||||||
|
$this->createdBy = $createdBy;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\EventListener;
|
|
||||||
|
|
||||||
use App\Entity\MenuItem;
|
|
||||||
use App\Entity\OrderItem;
|
|
||||||
use App\Repository\MenuItemRepository;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
|
||||||
use Doctrine\ORM\Event\PreFlushEventArgs;
|
|
||||||
use Doctrine\ORM\Events;
|
|
||||||
use Doctrine\Persistence\ObjectManager;
|
|
||||||
|
|
||||||
#[AsDoctrineListener(event: Events::preFlush)]
|
|
||||||
final readonly class OrderItemPreFlush
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private MenuItemRepository $menuItemRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function preFlush(PreFlushEventArgs $eventArgs): void
|
|
||||||
{
|
|
||||||
foreach (($eventArgs->getObjectManager()->getUnitOfWork()->getIdentityMap()[OrderItem::class] ?? []) as $orderItem) {
|
|
||||||
$this->checkOrderItem($orderItem, $eventArgs->getObjectManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkOrderItem(OrderItem $orderItem, ObjectManager $objectManager): void
|
|
||||||
{
|
|
||||||
$menuItem = $this->menuItemRepository->findOneBy([
|
|
||||||
'name' => $orderItem->getName(),
|
|
||||||
'foodVendor' => $orderItem->getFoodOrder()
|
|
||||||
->getFoodVendor(),
|
|
||||||
]);
|
|
||||||
if ($menuItem === null) {
|
|
||||||
$menuItem = new MenuItem;
|
|
||||||
$menuItem->setName($orderItem->getName());
|
|
||||||
$menuItem->setFoodVendor($orderItem->getFoodOrder()->getFoodVendor());
|
|
||||||
$objectManager->persist($menuItem);
|
|
||||||
}
|
|
||||||
if ($menuItem->getAliasOf() !== null) {
|
|
||||||
$menuItem = $menuItem->getAliasOf();
|
|
||||||
$orderItem->setName($menuItem->getName());
|
|
||||||
}
|
|
||||||
$orderItem->setMenuItem($menuItem);
|
|
||||||
if ($orderItem->getPriceCents() === 0) {
|
|
||||||
$orderItem->setPriceCents($menuItem->getPriceCents());
|
|
||||||
} elseif ($orderItem->getPriceCents() !== $menuItem->getPriceCents()) {
|
|
||||||
$menuItem->setPriceCents($orderItem->getPriceCents());
|
|
||||||
$objectManager->persist($menuItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,7 +17,6 @@ final class FoodVendorType extends AbstractType
|
||||||
->add('name')
|
->add('name')
|
||||||
->add('menuLink')
|
->add('menuLink')
|
||||||
->add('phone')
|
->add('phone')
|
||||||
->add('emojis')
|
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,14 @@ use App\Repository\MenuItemRepository;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Override;
|
use Override;
|
||||||
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
|
use Symfony\Bridge\Doctrine\Types\UlidType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Validator\Constraints\Length;
|
use Symfony\Component\Uid\Ulid;
|
||||||
use Symfony\Component\Validator\Constraints\NotBlank;
|
|
||||||
|
use function array_map;
|
||||||
|
use function assert;
|
||||||
|
|
||||||
final class MenuItemType extends AbstractType
|
final class MenuItemType extends AbstractType
|
||||||
{
|
{
|
||||||
|
@ -21,26 +22,41 @@ final class MenuItemType extends AbstractType
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$item = $options['data'];
|
$item = $options['data'];
|
||||||
|
assert($item instanceof MenuItem); // Ensure it's of the correct type
|
||||||
|
$vendorId = $item->getFoodVendor()?->getId(); // Use safe navigation operator in case FoodVendor is null
|
||||||
|
|
||||||
$builder->add('name', TextType::class, [
|
$builder->add('name'); // Basic field
|
||||||
'constraints' => [
|
|
||||||
new NotBlank,
|
|
||||||
new Length([
|
|
||||||
'min' => 3,
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$builder->add('priceCents', MoneyType::class, [
|
|
||||||
'label' => 'Price',
|
|
||||||
'divisor' => 100,
|
|
||||||
]);
|
|
||||||
$builder->add('aliases', EntityType::class, [
|
$builder->add('aliases', EntityType::class, [
|
||||||
'class' => MenuItem::class,
|
'class' => MenuItem::class,
|
||||||
'choice_label' => 'name',
|
'choice_label' => 'name',
|
||||||
'multiple' => true,
|
'multiple' => true,
|
||||||
'expanded' => true,
|
'expanded' => true,
|
||||||
'query_builder' => static fn(MenuItemRepository $repository): QueryBuilder
|
'query_builder' => static function (MenuItemRepository $repository) use ($item, $vendorId): QueryBuilder {
|
||||||
=> $repository->getSuitableAliasQueryBuilder($item),
|
|
||||||
|
$ids = $repository->createQueryBuilder('m')
|
||||||
|
->select('DISTINCT IDENTITY(m.aliasOf)')
|
||||||
|
->where('m.deletedAt IS NULL')
|
||||||
|
->andWhere('m.aliasOf IS NOT NULL')
|
||||||
|
->getquery();
|
||||||
|
$ids = $ids->getScalarResult();
|
||||||
|
$ids = array_map(static fn(array $id): Ulid => Ulid::fromBinary($id[1]), $ids);
|
||||||
|
|
||||||
|
// Build the main query with a NOT EXISTS constraint
|
||||||
|
$qb = $repository->createQueryBuilder('m');
|
||||||
|
$qb
|
||||||
|
->where('m.foodVendor = :vendorId')
|
||||||
|
->andWhere('m.deletedAt IS NULL')
|
||||||
|
->andWhere('m.id != :id');
|
||||||
|
foreach ($ids as $key => $id) {
|
||||||
|
$qb->andWhere("m.id != :idBy{$key}");
|
||||||
|
$qb->setParameter("idBy{$key}", $id, UlidType::NAME);
|
||||||
|
}
|
||||||
|
$qb
|
||||||
|
->orderBy('m.name', 'ASC')
|
||||||
|
->setParameter('vendorId', $vendorId, UlidType::NAME) // ULID or appropriate type
|
||||||
|
->setParameter('id', $item->getId()); // ULID or appropriate type
|
||||||
|
return $qb;
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Form;
|
|
||||||
|
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
|
|
||||||
final class OrderFinalize extends AbstractType
|
|
||||||
{
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
$builder->add('orderItems', CollectionType::class, [
|
|
||||||
'entry_type' => OrderItemFinalize::class,
|
|
||||||
'entry_options' => [
|
|
||||||
'label' => false,
|
|
||||||
],
|
|
||||||
])
|
|
||||||
->add('save', SubmitType::class)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'data_class' => FoodOrder::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Form;
|
|
||||||
|
|
||||||
use App\Entity\OrderItem;
|
|
||||||
use Symfony\Component\Form\AbstractType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
|
||||||
use Symfony\Component\Form\Extension\Core\Type\MoneyType;
|
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
|
||||||
|
|
||||||
final class OrderItemFinalize extends AbstractType
|
|
||||||
{
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
|
||||||
{
|
|
||||||
$builder
|
|
||||||
->add(child: 'name', options: [
|
|
||||||
'label' => 'order item',
|
|
||||||
'disabled' => true,
|
|
||||||
])
|
|
||||||
->add(child: 'extras', options: [
|
|
||||||
'disabled' => true,
|
|
||||||
])
|
|
||||||
->add(child: 'createdBy', options: [
|
|
||||||
'disabled' => true,
|
|
||||||
])
|
|
||||||
->add(child: 'priceCents', type: MoneyType::class, options: [
|
|
||||||
'label' => 'price',
|
|
||||||
'divisor' => 100,
|
|
||||||
])
|
|
||||||
->add(child: 'isPaid', type: CheckboxType::class, options: [
|
|
||||||
'required' => false,
|
|
||||||
'label' => 'paid?',
|
|
||||||
])
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $resolver): void
|
|
||||||
{
|
|
||||||
$resolver->setDefaults([
|
|
||||||
'data_class' => OrderItem::class,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,15 +8,4 @@ use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||||
final class Kernel extends BaseKernel
|
final class Kernel extends BaseKernel
|
||||||
{
|
{
|
||||||
use MicroKernelTrait;
|
use MicroKernelTrait;
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
protected string $environment,
|
|
||||||
protected bool $debug,
|
|
||||||
) {
|
|
||||||
parent::__construct($environment, $debug);
|
|
||||||
if ($environment === 'test') {
|
|
||||||
$this->debug = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,12 @@ final class FoodOrderRepository extends ServiceEntityRepository
|
||||||
parent::__construct($registry, FoodOrder::class);
|
parent::__construct($registry, FoodOrder::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function save(): void
|
||||||
|
{
|
||||||
|
$this->getEntityManager()
|
||||||
|
->flush();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FoodOrder[]
|
* @return FoodOrder[]
|
||||||
*/
|
*/
|
||||||
|
@ -41,34 +47,4 @@ final class FoodOrderRepository extends ServiceEntityRepository
|
||||||
->filter(static fn(FoodOrder $order): bool => $order->getCreatedAt() >= $date)
|
->filter(static fn(FoodOrder $order): bool => $order->getCreatedAt() >= $date)
|
||||||
->getValues();
|
->getValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findLatestOrder(): FoodOrder|null
|
|
||||||
{
|
|
||||||
return $this->createQueryBuilder('alias')
|
|
||||||
->orderBy('alias.id', 'DESC')
|
|
||||||
->setMaxResults(1)
|
|
||||||
->getQuery()
|
|
||||||
->getOneOrNullResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return FoodOrder[]
|
|
||||||
*/
|
|
||||||
public function findOpenOrders(): array
|
|
||||||
{
|
|
||||||
$now = new DateTimeImmutable;
|
|
||||||
|
|
||||||
return $this->createQueryBuilder('o')
|
|
||||||
->where('o.closedAt IS NULL OR o.closedAt > :now')
|
|
||||||
->setParameter('now', $now)
|
|
||||||
->orderBy('o.id', 'DESC')
|
|
||||||
->getQuery()
|
|
||||||
->getResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save(): void
|
|
||||||
{
|
|
||||||
$this->getEntityManager()
|
|
||||||
->flush();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,7 @@ namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
use Symfony\Bridge\Doctrine\Types\UlidType;
|
|
||||||
use Symfony\Component\Uid\Ulid;
|
|
||||||
|
|
||||||
use function array_map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends ServiceEntityRepository<MenuItem>
|
* @extends ServiceEntityRepository<MenuItem>
|
||||||
|
@ -21,29 +16,28 @@ final class MenuItemRepository extends ServiceEntityRepository
|
||||||
parent::__construct($registry, MenuItem::class);
|
parent::__construct($registry, MenuItem::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSuitableAliasQueryBuilder(MenuItem $menuItem): QueryBuilder
|
// /**
|
||||||
{
|
// * @return MenuItem[] Returns an array of MenuItem objects
|
||||||
$ids = $this->createQueryBuilder('m')
|
// */
|
||||||
->select('DISTINCT IDENTITY(m.aliasOf)')
|
// public function findByExampleField($value): array
|
||||||
->where('m.deletedAt IS NULL')
|
// {
|
||||||
->andWhere('m.aliasOf IS NOT NULL')
|
// return $this->createQueryBuilder('m')
|
||||||
->getquery();
|
// ->andWhere('m.exampleField = :val')
|
||||||
$ids = $ids->getScalarResult();
|
// ->setParameter('val', $value)
|
||||||
$ids = array_map(static fn(array $id): Ulid => Ulid::fromBinary($id[1]), $ids);
|
// ->orderBy('m.id', 'ASC')
|
||||||
|
// ->setMaxResults(10)
|
||||||
|
// ->getQuery()
|
||||||
|
// ->getResult()
|
||||||
|
// ;
|
||||||
|
// }
|
||||||
|
|
||||||
$qb = $this->createQueryBuilder('m');
|
// public function findOneBySomeField($value): ?MenuItem
|
||||||
$qb
|
// {
|
||||||
->where('m.foodVendor = :vendorId')
|
// return $this->createQueryBuilder('m')
|
||||||
->andWhere('m.deletedAt IS NULL')
|
// ->andWhere('m.exampleField = :val')
|
||||||
->andWhere('m.id != :id');
|
// ->setParameter('val', $value)
|
||||||
foreach ($ids as $key => $id) {
|
// ->getQuery()
|
||||||
$qb->andWhere("m.id != :idBy{$key}")
|
// ->getOneOrNullResult()
|
||||||
->setParameter("idBy{$key}", $id, UlidType::NAME);
|
// ;
|
||||||
}
|
// }
|
||||||
$qb
|
|
||||||
->orderBy('m.name', 'ASC')
|
|
||||||
->setParameter('vendorId', $menuItem->getFoodVendor()->getId(), UlidType::NAME)
|
|
||||||
->setParameter('id', $menuItem->getId(), UlidType::NAME);
|
|
||||||
return $qb;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
final readonly class LatestOrderProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private FoodOrderRepository $repository
|
|
||||||
) {}
|
|
||||||
|
|
||||||
#[Override]
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): FoodOrder|null
|
|
||||||
{
|
|
||||||
return $this->repository->findLatestOrder();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\State;
|
|
||||||
|
|
||||||
use ApiPlatform\Metadata\Operation;
|
|
||||||
use ApiPlatform\State\ProviderInterface;
|
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
|
||||||
use Override;
|
|
||||||
|
|
||||||
final readonly class OpenOrdersProvider implements ProviderInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private FoodOrderRepository $repository
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return FoodOrder[]
|
|
||||||
*/
|
|
||||||
#[Override]
|
|
||||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
|
||||||
{
|
|
||||||
return $this->repository->findOpenOrders();
|
|
||||||
}
|
|
||||||
}
|
|
175
symfony.lock
175
symfony.lock
|
@ -1,34 +1,11 @@
|
||||||
{
|
{
|
||||||
"api-platform/symfony": {
|
|
||||||
"version": "4.1",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "4.0",
|
|
||||||
"ref": "e9952e9f393c2d048f10a78f272cd35e807d972b"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/api_platform.yaml",
|
|
||||||
"config/routes/api_platform.yaml",
|
|
||||||
"src/ApiResource/.gitignore"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"doctrine/deprecations": {
|
|
||||||
"version": "1.1",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "1.0",
|
|
||||||
"ref": "87424683adc81d7dc305eefec1fced883084aab9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"doctrine/doctrine-bundle": {
|
"doctrine/doctrine-bundle": {
|
||||||
"version": "2.14",
|
"version": "2.12",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "2.13",
|
"version": "2.12",
|
||||||
"ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb"
|
"ref": "7266981c201efbbe02ae53c87f8bb378e3f825ae"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/doctrine.yaml",
|
"config/packages/doctrine.yaml",
|
||||||
|
@ -37,7 +14,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-fixtures-bundle": {
|
"doctrine/doctrine-fixtures-bundle": {
|
||||||
"version": "4.1",
|
"version": "4.0",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -49,7 +26,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"doctrine/doctrine-migrations-bundle": {
|
"doctrine/doctrine-migrations-bundle": {
|
||||||
"version": "3.4",
|
"version": "3.3",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -61,77 +38,40 @@
|
||||||
"migrations/.gitignore"
|
"migrations/.gitignore"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"liip/test-fixtures-bundle": {
|
|
||||||
"version": "3.4.0"
|
|
||||||
},
|
|
||||||
"nelmio/cors-bundle": {
|
|
||||||
"version": "2.5",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "1.5",
|
|
||||||
"ref": "6bea22e6c564fba3a1391615cada1437d0bde39c"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/nelmio_cors.yaml"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"phpstan/phpstan": {
|
"phpstan/phpstan": {
|
||||||
"version": "1.12",
|
"version": "1.11",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes-contrib",
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
"ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"phpstan.dist.neon"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"phpunit/phpunit": {
|
"phpunit/phpunit": {
|
||||||
"version": "11.5",
|
"version": "9.6",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "11.1",
|
"version": "9.6",
|
||||||
"ref": "c6658a60fc9d594805370eacdf542c3d6b5c0869"
|
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
".env.test",
|
".env.test",
|
||||||
"phpunit.xml.dist",
|
"phpunit.xml.dist",
|
||||||
"tests/bootstrap.php",
|
"tests/bootstrap.php"
|
||||||
"bin/phpunit"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"squizlabs/php_codesniffer": {
|
"squizlabs/php_codesniffer": {
|
||||||
"version": "3.13",
|
"version": "3.10",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes-contrib",
|
"repo": "github.com/symfony/recipes-contrib",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "3.6",
|
"version": "3.6",
|
||||||
"ref": "1019e5c08d4821cb9b77f4891f8e9c31ff20ac6f"
|
"ref": "1019e5c08d4821cb9b77f4891f8e9c31ff20ac6f"
|
||||||
},
|
}
|
||||||
"files": [
|
|
||||||
"phpcs.xml.dist"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symfony/asset-mapper": {
|
|
||||||
"version": "7.3",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "6.4",
|
|
||||||
"ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"assets/app.js",
|
|
||||||
"assets/styles/app.css",
|
|
||||||
"config/packages/asset_mapper.yaml",
|
|
||||||
"importmap.php"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"symfony/console": {
|
"symfony/console": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -143,37 +83,24 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/flex": {
|
"symfony/flex": {
|
||||||
"version": "2.7",
|
"version": "2.4",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "2.4",
|
"version": "1.0",
|
||||||
"ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
|
"ref": "146251ae39e06a95be0fe3d13c807bcf3938b172"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
".env",
|
".env"
|
||||||
".env.dev"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symfony/form": {
|
|
||||||
"version": "7.3",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "7.2",
|
|
||||||
"ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/csrf.yaml"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/framework-bundle": {
|
"symfony/framework-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "7.3",
|
"version": "7.0",
|
||||||
"ref": "5a1497d539f691b96afd45ae397ce5fe30beb4b9"
|
"ref": "6356c19b9ae08e7763e4ba2d9ae63043efc75db5"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/cache.yaml",
|
"config/packages/cache.yaml",
|
||||||
|
@ -183,12 +110,11 @@
|
||||||
"config/services.yaml",
|
"config/services.yaml",
|
||||||
"public/index.php",
|
"public/index.php",
|
||||||
"src/Controller/.gitignore",
|
"src/Controller/.gitignore",
|
||||||
"src/Kernel.php",
|
"src/Kernel.php"
|
||||||
".editorconfig"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/maker-bundle": {
|
"symfony/maker-bundle": {
|
||||||
"version": "1.63",
|
"version": "1.60",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -196,32 +122,23 @@
|
||||||
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
"ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"symfony/monolog-bundle": {
|
"symfony/phpunit-bridge": {
|
||||||
"version": "3.10",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "3.7",
|
"version": "6.3",
|
||||||
"ref": "aff23899c4440dd995907613c1dd709b6f59503f"
|
"ref": "a411a0480041243d97382cac7984f7dce7813c08"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/monolog.yaml"
|
".env.test",
|
||||||
]
|
"bin/phpunit",
|
||||||
},
|
"phpunit.xml.dist",
|
||||||
"symfony/property-info": {
|
"tests/bootstrap.php"
|
||||||
"version": "7.3",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "7.3",
|
|
||||||
"ref": "dae70df71978ae9226ae915ffd5fad817f5ca1f7"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/property_info.yaml"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/routing": {
|
"symfony/routing": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -233,21 +150,8 @@
|
||||||
"config/routes.yaml"
|
"config/routes.yaml"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/security-bundle": {
|
|
||||||
"version": "7.3",
|
|
||||||
"recipe": {
|
|
||||||
"repo": "github.com/symfony/recipes",
|
|
||||||
"branch": "main",
|
|
||||||
"version": "6.4",
|
|
||||||
"ref": "2ae08430db28c8eb4476605894296c82a642028f"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"config/packages/security.yaml",
|
|
||||||
"config/routes/security.yaml"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"symfony/twig-bundle": {
|
"symfony/twig-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -260,7 +164,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/uid": {
|
"symfony/uid": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -269,7 +173,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"symfony/validator": {
|
"symfony/validator": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
|
@ -281,19 +185,16 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"symfony/web-profiler-bundle": {
|
"symfony/web-profiler-bundle": {
|
||||||
"version": "7.3",
|
"version": "7.1",
|
||||||
"recipe": {
|
"recipe": {
|
||||||
"repo": "github.com/symfony/recipes",
|
"repo": "github.com/symfony/recipes",
|
||||||
"branch": "main",
|
"branch": "main",
|
||||||
"version": "7.3",
|
"version": "6.1",
|
||||||
"ref": "5b2b543e13942495c0003f67780cb4448af9e606"
|
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"config/packages/web_profiler.yaml",
|
"config/packages/web_profiler.yaml",
|
||||||
"config/routes/web_profiler.yaml"
|
"config/routes/web_profiler.yaml"
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"twig/extra-bundle": {
|
|
||||||
"version": "v3.21.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
{{ form_start(form) }}
|
||||||
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
{{ form_widget(form) }}
|
||||||
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -2,62 +2,37 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="color-scheme" content="dark light">
|
|
||||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||||
<link rel="icon" type="image/svg+xml"
|
<link rel="icon" type="image/svg+xml"
|
||||||
href="{{ favicon }}" />
|
href="{{ favicon }}" />
|
||||||
{% block javascripts %}
|
{% set currentDate = "now"|date("d") %}
|
||||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
{% if currentDate % 3 == 0 %}
|
||||||
{% endblock %}
|
<link rel="stylesheet" href="/static/css/new.min.css">
|
||||||
|
{% elseif currentDate % 3 == 1 %}
|
||||||
|
<link rel="stylesheet" href="/static/css/simple.min.css">
|
||||||
|
{% else %}
|
||||||
|
<link rel="stylesheet" href="/static/css/water.min.css">
|
||||||
|
{% endif %}
|
||||||
|
<style>
|
||||||
|
label{
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="/static/js/htmx.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="mb-4">
|
<header>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<p>Hello {{ app.request.cookies.get('username', 'nobody') }} - <a href="{{ path('username') }}">change name</a></p>
|
||||||
<div class="container-fluid">
|
<nav>
|
||||||
<span class="navbar-brand">Futtern</span>
|
<a href="{{ path('app_food_order_index') }}">Orders</a> /
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
<a href="{{ path('app_food_vendor_index') }}">Vendors</a> /
|
||||||
<span class="navbar-toggler-icon"></span>
|
<a
|
||||||
</button>
|
href="https://git.hannover.ccc.de/lubiana/futtern/issues/new"
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
target="_blank"
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
>Create Issue</a>
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ path('app_food_order_index') }}">Orders</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="{{ path('app_food_vendor_index') }}">Vendors</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="/api">API</a></li>
|
|
||||||
<li class="nav-item"><a class="nav-link" href="https://git.hannover.ccc.de/lubiana/futtern/issues/new" target="_blank">Create Issue</a></li>
|
|
||||||
</ul>
|
|
||||||
<div class="btn-group ms-auto" role="group" aria-label="Mode selection">
|
|
||||||
<input type="radio" class="btn-check" name="mode" id="normal" autocomplete="off" checked>
|
|
||||||
<label class="btn btn-outline-primary" for="normal">
|
|
||||||
Normal
|
|
||||||
<span class="emoji-normal">😌</span>
|
|
||||||
<span class="emoji-enhanced">😌🍑</span>
|
|
||||||
<span class="emoji-bonkers">😌🍑🍆💦👅💋😈🏳️🌈✨</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="mode" id="enhanced" autocomplete="off">
|
|
||||||
<label class="btn btn-outline-primary" for="enhanced">
|
|
||||||
Enhanced
|
|
||||||
<span class="emoji-normal">✨</span>
|
|
||||||
<span class="emoji-enhanced">✨🍑🍆</span>
|
|
||||||
<span class="emoji-bonkers">✨🍑🍆💦👅💋😈🏳️🌈</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<input type="radio" class="btn-check" name="mode" id="bonkers" autocomplete="off">
|
|
||||||
<label class="btn btn-outline-primary" for="bonkers">
|
|
||||||
Bonkers
|
|
||||||
<span class="emoji-normal">💦</span>
|
|
||||||
<span class="emoji-enhanced">💦🍑🍆</span>
|
|
||||||
<span class="emoji-bonkers">💦🍑🍆👅💋😈🏳️🌈✨🥵😳🤤😍🥴💕💗💘💝💞💟💌💏💑</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<span class="navbar-text">
|
|
||||||
Hello {{ app.request.cookies.get('username', 'nobody') }} - <a class="text-light" href="{{ path('username') }}">change name</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main class="container pb-5">
|
<main>
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -3,12 +3,10 @@
|
||||||
{% block title %}Edit FoodOrder{% endblock %}
|
{% block title %}Edit FoodOrder{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Edit FoodOrder</h1>
|
<h1>Edit FoodOrder</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
{{ include('_form.html.twig', {'button_label': 'Update'}) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_order_index') }}">back to list</a>
|
<a href="{{ path('app_food_order_index') }}">back to list</a>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,18 +3,17 @@
|
||||||
{% block title %}FoodOrder index{% endblock %}
|
{% block title %}FoodOrder index{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">FoodOrder index</h1>
|
<h1>FoodOrder index</h1>
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
|
||||||
hx-get="{{ path('app_food_order_new') }}"
|
hx-get="{{ path('app_food_order_new') }}"
|
||||||
hx-trigger="click"
|
hx-trigger="click"
|
||||||
hx-target="closest div"
|
hx-target="closest div"
|
||||||
>Create new</button>
|
>Create new</button>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<table class="table table-striped table-hover">
|
<table class="table">
|
||||||
<thead class="table-light">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>CreatedBy</th>
|
<th>CreatedBy</th>
|
||||||
<th>Vendor</th>
|
<th>Vendor</th>
|
||||||
|
@ -29,7 +28,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if food_orders|length < 10 %}
|
{% if food_orders|length < 10 %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center text-muted">
|
<td colspan="5">
|
||||||
check the <a href="{{ path('app_food_order_archive') }}">archive</a>
|
check the <a href="{{ path('app_food_order_archive') }}">archive</a>
|
||||||
for older orders
|
for older orders
|
||||||
</td>
|
</td>
|
||||||
|
@ -37,12 +36,10 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="d-flex gap-2">
|
{% if prev_page > 0 %}
|
||||||
{% if prev_page > 0 %}
|
<a href="{{ path('app_food_order_archive', {'page': prev_page}) }}">previous page</a> |
|
||||||
<a class="btn btn-outline-secondary btn-sm" href="{{ path('app_food_order_archive', {'page': prev_page}) }}">previous page</a>
|
{% endif %}
|
||||||
{% endif %}
|
{% if next_page > current_page %}
|
||||||
{% if next_page > current_page %}
|
<a href="{{ path('app_food_order_archive', {'page': next_page}) }}">next page</a>
|
||||||
<a class="btn btn-outline-secondary btn-sm" href="{{ path('app_food_order_archive', {'page': next_page}) }}">next page</a>
|
{% endif %}
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
<div class="mb-4">
|
{{ include('_form.html.twig') }}
|
||||||
{{ include('_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
{% block title %}FoodOrder{% endblock %}
|
{% block title %}FoodOrder{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">FoodOrder</h1>
|
<h1>FoodOrder</h1>
|
||||||
|
|
||||||
<table class="table table-bordered table-striped w-auto">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Vendor</th>
|
<th>Vendor</th>
|
||||||
|
@ -29,52 +29,17 @@
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div class="mb-3 d-flex gap-2">
|
<a class="button" href="{{ path('app_food_order_index') }}">back to list</a>
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_order_index') }}">back to list</a>
|
{% if(food_order.isClosed) %}
|
||||||
{% if(food_order.isClosed) %}
|
<a class="button" href="{{ path('app_food_order_open', {'id': food_order.id}) }}">reopen</a>
|
||||||
<a class="btn btn-warning" href="{{ path('app_food_order_open', {'id': food_order.id}) }}">reopen</a>
|
|
||||||
{% else %}
|
|
||||||
<a class="btn btn-success" href="{{ path('app_food_order_close', {'id': food_order.id}) }}">close</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="mt-5">Items</h2>
|
|
||||||
{% if (food_order.isClosed and form) %}
|
|
||||||
{{ form_start(form) }}
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>By</th>
|
|
||||||
<th>item</th>
|
|
||||||
<th>extras</th>
|
|
||||||
<th>price</th>
|
|
||||||
<th>is paid</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for itemForm in form.orderItems %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ field_value(itemForm.createdBy) }}</td>
|
|
||||||
<td>{{ field_value(itemForm.name) }}</td>
|
|
||||||
<td>{{ field_value(itemForm.extras) }}</td>
|
|
||||||
<td>{{ form_widget(itemForm.priceCents) }}</td>
|
|
||||||
<td>{{ form_widget(itemForm.isPaid) }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="4"></td>
|
|
||||||
<td>{{ form_row(form.save) }} {{ form_row(form._token) }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{{ form_end(form, {render_rest: false}) }}
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<table class="table table-hover">
|
<a class="button" href="{{ path('app_food_order_close', {'id': food_order.id}) }}">close</a>
|
||||||
<thead class="table-light">
|
{% endif %}
|
||||||
|
|
||||||
|
<h2>Items</h2>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Index</th>
|
|
||||||
<th>username</th>
|
<th>username</th>
|
||||||
<th>name</th>
|
<th>name</th>
|
||||||
<th>extras</th>
|
<th>extras</th>
|
||||||
|
@ -84,23 +49,21 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for item in food_order.orderItemsSortedByName %}
|
{% for item in food_order.orderItemsSortedByName %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ loop.index }}</td>
|
|
||||||
<td>{{ item.createdBy }}</td>
|
<td>{{ item.createdBy }}</td>
|
||||||
<td>{{ item.name }}</td>
|
<td>{{ item.name }}</td>
|
||||||
<td>{{ item.extras }}</td>
|
<td>{{ item.extras }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if(food_order.isClosed) %}
|
{% if(food_order.isClosed) %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a class="btn btn-sm btn-outline-primary me-1" href="{{ path('app_order_item_edit', {'id': item.id}) }}">edit</a>
|
<a href="{{ path('app_order_item_edit', {'id': item.id}) }}">edit</a>
|
||||||
<a class="btn btn-sm btn-outline-secondary me-1" href="{{ path('app_order_item_copy', {'id': item.id}) }}">copy</a>
|
<a href="{{ path('app_order_item_copy', {'id': item.id}) }}">copy</a>
|
||||||
<a class="btn btn-sm btn-outline-danger" href="{{ path('app_order_item_delete', {'id': item.id}) }}">remove</a>
|
<a href="{{ path('app_order_item_delete', {'id': item.id}) }}">remove</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endif %}
|
<a class="button" href="{{ path('app_order_item_new', {'foodOrder': food_order.id}) }}">New Item</a>
|
||||||
<a class="btn btn-primary mt-2" href="{{ path('app_order_item_new', {'foodOrder': food_order.id}) }}">New Item</a>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
{% set opacity = food_order.isClosed ? 'opacity-25' : 'opacity-100' %}
|
<tr>
|
||||||
<tr class="{{ opacity }}">
|
<td>{{ food_order.createdBy }}</td>
|
||||||
|
<td>{{ food_order.foodVendor.name }}</td>
|
||||||
|
<td>{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
||||||
|
<td>{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}</td>
|
||||||
<td>
|
<td>
|
||||||
{{ food_order.createdBy }}
|
<a href="{{ path('app_food_order_show', {'id': food_order.id}) }}">show</a>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ food_order.foodVendor.name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ food_order.createdAt ? food_order.createdAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ food_order.closedAt ? food_order.closedAt|date('Y-m-d H:i:s', 'Europe/Berlin') : '' }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a class="btn btn-sm btn-outline-info" href="{{ path('app_food_order_show', {'id': food_order.id}) }}">show</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
|
@ -1,4 +1,4 @@
|
||||||
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
{{ form_start(form) }}
|
||||||
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
{{ form_widget(form) }}
|
||||||
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -3,127 +3,9 @@
|
||||||
{% block title %}Edit FoodVendor{% endblock %}
|
{% block title %}Edit FoodVendor{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Edit FoodVendor</h1>
|
<h1>Edit FoodVendor</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('food_vendor/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
{{ include('food_vendor/_form.html.twig', {'button_label': 'Update'}) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4 emoji-buttons" data-role="emoji-selector">
|
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
|
||||||
<button class="btn btn-primary">🍕</button>
|
|
||||||
<button class="btn btn-primary">🍔</button>
|
|
||||||
<button class="btn btn-primary">🌮</button>
|
|
||||||
<button class="btn btn-primary">🍜</button>
|
|
||||||
<button class="btn btn-primary">🥗</button>
|
|
||||||
<button class="btn btn-primary">☕</button>
|
|
||||||
<button class="btn btn-primary">🍣</button>
|
|
||||||
<button class="btn btn-primary">🍤</button>
|
|
||||||
<button class="btn btn-primary">🍦</button>
|
|
||||||
<button class="btn btn-primary">🍩</button>
|
|
||||||
<button class="btn btn-primary">🍪</button>
|
|
||||||
<button class="btn btn-primary">🍰</button>
|
|
||||||
<button class="btn btn-primary">🍫</button>
|
|
||||||
<button class="btn btn-primary">🍿</button>
|
|
||||||
<button class="btn btn-primary">🍩</button>
|
|
||||||
<button class="btn btn-primary">🍭</button>
|
|
||||||
<button class="btn btn-primary">🍮</button>
|
|
||||||
<button class="btn btn-primary">🍯</button>
|
|
||||||
<button class="btn btn-primary">🍎</button>
|
|
||||||
<button class="btn btn-primary">🍊</button>
|
|
||||||
<button class="btn btn-primary">🍋</button>
|
|
||||||
<button class="btn btn-primary">🍌</button>
|
|
||||||
<button class="btn btn-primary">🍉</button>
|
|
||||||
<button class="btn btn-primary">🍇</button>
|
|
||||||
<button class="btn btn-primary">🍓</button>
|
|
||||||
<button class="btn btn-primary">🍈</button>
|
|
||||||
<button class="btn btn-primary">🍒</button>
|
|
||||||
<button class="btn btn-primary">🍍</button>
|
|
||||||
<button class="btn btn-primary">🥭</button>
|
|
||||||
<button class="btn btn-primary">🥥</button>
|
|
||||||
<button class="btn btn-primary">🥝</button>
|
|
||||||
<button class="btn btn-primary">🍅</button>
|
|
||||||
<button class="btn btn-primary">🍆</button>
|
|
||||||
<button class="btn btn-primary">🥑</button>
|
|
||||||
<button class="btn btn-primary">🥦</button>
|
|
||||||
<button class="btn btn-primary">🥒</button>
|
|
||||||
<button class="btn btn-primary">🌽</button>
|
|
||||||
<button class="btn btn-primary">🥕</button>
|
|
||||||
<button class="btn btn-primary">🧄</button>
|
|
||||||
<button class="btn btn-primary">🧅</button>
|
|
||||||
<button class="btn btn-primary">🥔</button>
|
|
||||||
<button class="btn btn-primary">🍠</button>
|
|
||||||
<button class="btn btn-primary">🥐</button>
|
|
||||||
<button class="btn btn-primary">🥯</button>
|
|
||||||
<button class="btn btn-primary">🍞</button>
|
|
||||||
<button class="btn btn-primary">🥖</button>
|
|
||||||
<button class="btn btn-primary">🥨</button>
|
|
||||||
<button class="btn btn-primary">🧀</button>
|
|
||||||
<button class="btn btn-primary">🥚</button>
|
|
||||||
<button class="btn btn-primary">🍳</button>
|
|
||||||
<button class="btn btn-primary">🥞</button>
|
|
||||||
<button class="btn btn-primary">🧇</button>
|
|
||||||
<button class="btn btn-primary">🥓</button>
|
|
||||||
<button class="btn btn-primary">🥩</button>
|
|
||||||
<button class="btn btn-primary">🍗</button>
|
|
||||||
<button class="btn btn-primary">🍖</button>
|
|
||||||
<button class="btn btn-primary">🌭</button>
|
|
||||||
<button class="btn btn-primary">🍔</button>
|
|
||||||
<button class="btn btn-primary">🍟</button>
|
|
||||||
<button class="btn btn-primary">🍕</button>
|
|
||||||
<button class="btn btn-primary">🥪</button>
|
|
||||||
<button class="btn btn-primary">🌮</button>
|
|
||||||
<button class="btn btn-primary">🌯</button>
|
|
||||||
<button class="btn btn-primary">🥙</button>
|
|
||||||
<button class="btn btn-primary">🧆</button>
|
|
||||||
<button class="btn btn-primary">🥘</button>
|
|
||||||
<button class="btn btn-primary">🍲</button>
|
|
||||||
<button class="btn btn-primary">🥣</button>
|
|
||||||
<button class="btn btn-primary">🥗</button>
|
|
||||||
<button class="btn btn-primary">🍿</button>
|
|
||||||
<button class="btn btn-primary">🧈</button>
|
|
||||||
<button class="btn btn-primary">🧂</button>
|
|
||||||
<button class="btn btn-primary">🥫</button>
|
|
||||||
<button class="btn btn-primary">🍱</button>
|
|
||||||
<button class="btn btn-primary">🍛</button>
|
|
||||||
<button class="btn btn-primary">🍚</button>
|
|
||||||
<button class="btn btn-primary">🍙</button>
|
|
||||||
<button class="btn btn-primary">🍘</button>
|
|
||||||
<button class="btn btn-primary">🍢</button>
|
|
||||||
<button class="btn btn-primary">🍡</button>
|
|
||||||
<button class="btn btn-primary">🍧</button>
|
|
||||||
<button class="btn btn-primary">🍨</button>
|
|
||||||
<button class="btn btn-primary">🍦</button>
|
|
||||||
<button class="btn btn-primary">🥧</button>
|
|
||||||
<button class="btn btn-primary">🍰</button>
|
|
||||||
<button class="btn btn-primary">🎂</button>
|
|
||||||
<button class="btn btn-primary">🍮</button>
|
|
||||||
<button class="btn btn-primary">🍭</button>
|
|
||||||
<button class="btn btn-primary">🍬</button>
|
|
||||||
<button class="btn btn-primary">🍫</button>
|
|
||||||
<button class="btn btn-primary">🍿</button>
|
|
||||||
<button class="btn btn-primary">🍩</button>
|
|
||||||
<button class="btn btn-primary">🍪</button>
|
|
||||||
<button class="btn btn-primary">🌰</button>
|
|
||||||
<button class="btn btn-primary">🥜</button>
|
|
||||||
<button class="btn btn-primary">🍯</button>
|
|
||||||
<button class="btn btn-primary">🥛</button>
|
|
||||||
<button class="btn btn-primary">🍼</button>
|
|
||||||
<button class="btn btn-primary">☕</button>
|
|
||||||
<button class="btn btn-primary">🍵</button>
|
|
||||||
<button class="btn btn-primary">🍶</button>
|
|
||||||
<button class="btn btn-primary">🍾</button>
|
|
||||||
<button class="btn btn-primary">🍷</button>
|
|
||||||
<button class="btn btn-primary">🍸</button>
|
|
||||||
<button class="btn btn-primary">🍹</button>
|
|
||||||
<button class="btn btn-primary">🍺</button>
|
|
||||||
<button class="btn btn-primary">🍻</button>
|
|
||||||
<button class="btn btn-primary">🥂</button>
|
|
||||||
<button class="btn btn-primary">🥃</button>
|
|
||||||
<button class="btn btn-primary">🥤</button>
|
|
||||||
<button class="btn btn-primary">🧃</button>
|
|
||||||
<button class="btn btn-primary">🧉</button>
|
|
||||||
<button class="btn btn-primary">🧊</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_vendor_index') }}">back to list</a>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
{% block title %}FoodVendor index{% endblock %}
|
{% block title %}FoodVendor index{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">FoodVendor index</h1>
|
<h1>FoodVendor index</h1>
|
||||||
|
|
||||||
<table class="table table-striped table-hover">
|
<table class="table">
|
||||||
<thead class="table-light">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>actions</th>
|
<th>actions</th>
|
||||||
|
@ -17,17 +17,17 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ food_vendor.name }}</td>
|
<td>{{ food_vendor.name }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm btn-outline-info me-1" href="{{ path('app_food_vendor_show', {'id': food_vendor.id}) }}">show</a>
|
<a href="{{ path('app_food_vendor_show', {'id': food_vendor.id}) }}">show</a>
|
||||||
<a class="btn btn-sm btn-outline-primary" href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-center text-muted">no records found</td>
|
<td colspan="3">no records found</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a class="btn btn-primary" href="{{ path('app_food_vendor_new') }}">Create new</a>
|
<a href="{{ path('app_food_vendor_new') }}">Create new</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
{% block title %}New FoodVendor{% endblock %}
|
{% block title %}New FoodVendor{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Create new FoodVendor</h1>
|
<h1>Create new FoodVendor</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('food_vendor/_form.html.twig') }}
|
||||||
{{ include('food_vendor/_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_vendor_index') }}">back to list</a>
|
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
{% block title %}FoodVendor{% endblock %}
|
{% block title %}FoodVendor{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">FoodVendor</h1>
|
<h1>FoodVendor</h1>
|
||||||
|
|
||||||
<table class="table table-bordered w-auto">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
@ -18,22 +18,23 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<section class="mb-4">
|
<section>
|
||||||
<h2>known menuitems</h2>
|
<h2>known menuitems</h2>
|
||||||
<ul class="list-group list-group-flush">
|
<ul>
|
||||||
{% for item in food_vendor.menuItems %}
|
{% for item in food_vendor.menuItems %}
|
||||||
<li class="list-group-item">
|
<li>
|
||||||
<a href="{{ path('app_menu_item_show', {'id': item.id}) }}">{{ item.name }}</a>
|
<a href="{{ path('app_menu_item_show', {'id': item.id}) }}">{{ item.name }}</a>
|
||||||
{% if(item.aliasOf) %}
|
{% if(item.aliasOf) %}
|
||||||
<span class="text-muted">(alias of: {{ item.aliasOf.name }})</span>
|
(alias of: {{ item.aliasOf.name }})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_vendor_index') }}">back to list</a>
|
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
|
||||||
<a class="btn btn-primary" href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
|
||||||
</div>
|
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<form method="post" action="{{ path('app_menu_item_delete', {'id': menu_item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
<form method="post" action="{{ path('app_menu_item_delete', {'id': menu_item.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
|
||||||
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ menu_item.id) }}">
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ menu_item.id) }}">
|
||||||
<button class="btn btn-danger btn-sm">Delete</button>
|
<button class="btn">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
{{ form_start(form) }}
|
||||||
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
{{ form_widget(form) }}
|
||||||
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -3,13 +3,9 @@
|
||||||
{% block title %}Edit MenuItem{% endblock %}
|
{% block title %}Edit MenuItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Edit MenuItem</h1>
|
<h1>Edit MenuItem</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('menu_item/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
{{ include('menu_item/_form.html.twig', {'button_label': 'Update'}) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
{{ include('menu_item/_delete_form.html.twig') }}
|
||||||
{{ include('menu_item/_delete_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
{% block title %}MenuItem index{% endblock %}
|
{% block title %}MenuItem index{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">MenuItem index</h1>
|
<h1>MenuItem index</h1>
|
||||||
|
|
||||||
<table class="table table-striped table-hover">
|
<table class="table">
|
||||||
<thead class="table-light">
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
@ -19,17 +19,17 @@
|
||||||
<td>{{ menu_item.id }}</td>
|
<td>{{ menu_item.id }}</td>
|
||||||
<td>{{ menu_item.name }}</td>
|
<td>{{ menu_item.name }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm btn-outline-info me-1" href="{{ path('app_menu_item_show', {'id': menu_item.id}) }}">show</a>
|
<a href="{{ path('app_menu_item_show', {'id': menu_item.id}) }}">show</a>
|
||||||
<a class="btn btn-sm btn-outline-primary" href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
<a href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="3" class="text-center text-muted">no records found</td>
|
<td colspan="3">no records found</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<a class="btn btn-primary" href="{{ path('app_menu_item_new') }}">Create new</a>
|
<a href="{{ path('app_menu_item_new') }}">Create new</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
{% block title %}New MenuItem{% endblock %}
|
{% block title %}New MenuItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Create new MenuItem</h1>
|
<h1>Create new MenuItem</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('menu_item/_form.html.twig') }}
|
||||||
{{ include('menu_item/_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_menu_item_index') }}">back to list</a>
|
<a href="{{ path('app_menu_item_index') }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
{% block title %}MenuItem{% endblock %}
|
{% block title %}MenuItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">MenuItem</h1>
|
<h1>MenuItem</h1>
|
||||||
|
|
||||||
<table class="table table-bordered w-auto">
|
<table class="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Id</th>
|
<th>Id</th>
|
||||||
|
@ -15,34 +15,18 @@
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<td>{{ menu_item.name }}</td>
|
<td>{{ menu_item.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th>Price</th>
|
|
||||||
<td>{{ (menu_item.priceCents / 100)|format_currency('EUR') }}</td>
|
|
||||||
</tr>
|
|
||||||
{% if(menu_item.aliasOf) %}
|
{% if(menu_item.aliasOf) %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Alias of</th>
|
<th>Alias of</th>
|
||||||
<td>{{ menu_item.aliasOf.name }}</td>
|
<td>{{ menu_item.aliasOf.name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if(menu_item.aliases|length > 0) %}
|
|
||||||
<tr>
|
|
||||||
<th>Aliases</th>
|
|
||||||
<td>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
{% for alias in menu_item.aliases %}
|
|
||||||
<li class="list-group-item">{{ alias.name }}</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<a href="{{ path('app_food_vendor_show', { 'id': menu_item.foodVendor.id}) }}">back to list</a>
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_vendor_show', { 'id': menu_item.foodVendor.id}) }}">back to list</a>
|
|
||||||
<a class="btn btn-primary" href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
<a href="{{ path('app_menu_item_edit', {'id': menu_item.id}) }}">edit</a>
|
||||||
{{ include('menu_item/_delete_form.html.twig') }}
|
|
||||||
</div>
|
{{ include('menu_item/_delete_form.html.twig') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{ form_start(form, {'attr': {'class': 'mb-3'}}) }}
|
{{ form_start(form) }}
|
||||||
{{ form_widget(form, {'attr': {'class': 'form-control'}}) }}
|
{{ form_widget(form) }}
|
||||||
<button class="btn btn-primary mt-2">{{ button_label|default('Save') }}</button>
|
<button class="btn">{{ button_label|default('Save') }}</button>
|
||||||
{{ form_end(form) }}
|
{{ form_end(form) }}
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
{% block title %}Edit OrderItem{% endblock %}
|
{% block title %}Edit OrderItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Edit OrderItem</h1>
|
<h1>Edit OrderItem</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('order_item/_form.html.twig', {'button_label': 'Update'}) }}
|
||||||
{{ include('order_item/_form.html.twig', {'button_label': 'Update'}) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_order_show', {'id': order_item.foodOrder.id}) }}">back to list</a>
|
<a href="{{ path('app_food_order_show', {'id': order_item.foodOrder.id}) }}">back to list</a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,32 +3,30 @@
|
||||||
{% block title %}New OrderItem{% endblock %}
|
{% block title %}New OrderItem{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Create new OrderItem</h1>
|
<h1>Create new OrderItem</h1>
|
||||||
|
|
||||||
<div class="mb-4">
|
{{ include('order_item/_form.html.twig') }}
|
||||||
{{ include('order_item/_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{% if food_order.foodVendor.menuLink != '' %}
|
{% if food_order.foodVendor.menuLink != '' %}
|
||||||
<a class="btn btn-outline-secondary mb-3" href="{{ food_order.foodVendor.menuLink }}" target="_blank">
|
<a href="{{ food_order.foodVendor.menuLink }}" target="_blank">
|
||||||
External link to Menu
|
External link to Menu
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="mb-2">
|
<div>
|
||||||
<b>click a button to select a given menuitem</b>
|
<b>click a button to select a given menuitem</b>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3 d-flex flex-wrap gap-2">
|
<div>
|
||||||
{% for menuItem in menuItems %}
|
{% for menuItem in menuItems %}
|
||||||
<a class="btn btn-outline-info btn-sm" href="#" data-menu-item>{{ menuItem.name }}</a>
|
<a href="#" data-menu-item>{{ menuItem.name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<a class="btn btn-secondary" href="{{ path('app_food_order_show', { 'id': food_order.id}) }}">back to list</a>
|
<a class="button" href="{{ path('app_food_order_show', { 'id': food_order.id}) }}">back to list</a>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.querySelectorAll('[data-menu-item]').forEach(function(element) {
|
document.querySelectorAll('[data-menu-item]').forEach(function(element) {
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
{% block title %}Tell me your name{% endblock %}
|
{% block title %}Tell me your name{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<h1 class="mb-4">Tell me your name</h1>
|
<h1>Tell me your name</h1>
|
||||||
<p class="mb-3">By submitting the form, you agree that your username will be stored as a cookie.</p>
|
<p>By submitting the form, you agree that your username will be stored as a cookie.</p>
|
||||||
<div class="mb-4">
|
{{ include('_form.html.twig') }}
|
||||||
{{ include('_form.html.twig') }}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,40 +1,45 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Tests\Feature\Controller;
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
use App\Controller\FoodOrderController;
|
|
||||||
use App\Entity\FoodOrder;
|
use App\Entity\FoodOrder;
|
||||||
use App\Entity\FoodVendor;
|
use App\Entity\FoodVendor;
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
use App\Entity\OrderItem;
|
use App\Entity\OrderItem;
|
||||||
use App\Form\FoodOrderType;
|
use App\Tests\DbWebTest;
|
||||||
use App\Repository\FoodOrderRepository;
|
use Override;
|
||||||
use App\Repository\FoodVendorRepository;
|
|
||||||
use Symfony\Component\BrowserKit\Cookie;
|
use Symfony\Component\BrowserKit\Cookie;
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
use Symfony\Component\DomCrawler\Crawler;
|
||||||
|
|
||||||
use function assert;
|
use function assert;
|
||||||
use function describe;
|
|
||||||
use function pest;
|
|
||||||
use function range;
|
use function range;
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function str_ends_with;
|
use function str_ends_with;
|
||||||
use function test;
|
|
||||||
|
|
||||||
pest()
|
final class FoodOrderControllerTest extends DbWebTest
|
||||||
->beforeEach(function (): void {
|
{
|
||||||
$this->setEntityClass(FoodOrder::class);
|
private string $path = '/food/order/';
|
||||||
$this->setPath('/food/order/');
|
private FoodVendor $vendor;
|
||||||
$this->repository = $this->manager->getRepository($this->entityClass);
|
|
||||||
|
#[Override]
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
$this->vendor = new FoodVendor;
|
$this->vendor = new FoodVendor;
|
||||||
$this->vendor->setName('Food Vendor');
|
$this->vendor->setName('Food Vendor');
|
||||||
|
|
||||||
$this->manager->persist($this->vendor);
|
$this->manager->persist($this->vendor);
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
});
|
}
|
||||||
|
|
||||||
describe(FoodOrderController::class, function (): void {
|
#[Override]
|
||||||
test('index', function (): void {
|
public function getEntityClass(): string
|
||||||
|
{
|
||||||
|
return FoodOrder::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndex(): void
|
||||||
|
{
|
||||||
$order = new FoodOrder;
|
$order = new FoodOrder;
|
||||||
$order->setFoodVendor($this->vendor);
|
$order->setFoodVendor($this->vendor);
|
||||||
|
|
||||||
|
@ -43,16 +48,17 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list");
|
$crawler = $this->client->request('GET', "{$this->path}list");
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('FoodOrder index');
|
self::assertPageTitleContains('FoodOrder index');
|
||||||
$this->assertCount(
|
$this->assertCount(
|
||||||
1,
|
1,
|
||||||
$crawler->filter('td')
|
$crawler->filter('td')
|
||||||
->reduce(fn(Crawler $node, $i): bool => $node->text() === $this->vendor->getName()),
|
->reduce(fn(Crawler $node, $i): bool => $node->text() === $this->vendor->getName()),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
test('orderedItems', function (): void {
|
public function testOrderedItems(): void
|
||||||
|
{
|
||||||
$order = new FoodOrder;
|
$order = new FoodOrder;
|
||||||
$order->setFoodVendor($this->vendor);
|
$order->setFoodVendor($this->vendor);
|
||||||
|
|
||||||
|
@ -98,33 +104,27 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$crawler = $this->client->request('GET', "{$this->path}{$order->getId()}");
|
$crawler = $this->client->request('GET', "{$this->path}{$order->getId()}");
|
||||||
$this->assertResponseIsSuccessful();
|
self::assertResponseIsSuccessful();
|
||||||
$tdContent = $crawler->filter(
|
$tdContent = $crawler->filter(
|
||||||
'table.table-hover tbody tr:nth-child(1) td:nth-child(3)'
|
'table.table:nth-child(6) > tbody:nth-child(2) > tr:nth-child(1) > td:nth-child(2)'
|
||||||
)->text();
|
)->text();
|
||||||
$this->assertEquals('A', $tdContent);
|
$this->assertEquals('A', $tdContent);
|
||||||
$tdContent = $crawler->filter(
|
$tdContent = $crawler->filter(
|
||||||
'table.table-hover tbody tr:nth-child(2) td:nth-child(3)'
|
'table.table:nth-child(6) > tbody:nth-child(2) > tr:nth-child(2) > td:nth-child(2)'
|
||||||
)->text();
|
)->text();
|
||||||
$this->assertEquals('B', $tdContent);
|
$this->assertEquals('B', $tdContent);
|
||||||
$tdContent = $crawler->filter(
|
$tdContent = $crawler->filter(
|
||||||
'table.table-hover tbody tr:nth-child(3) td:nth-child(3)'
|
'table.table:nth-child(6) > tbody:nth-child(2) > tr:nth-child(3) > td:nth-child(2)'
|
||||||
)->text();
|
)->text();
|
||||||
$this->assertEquals('C', $tdContent);
|
$this->assertEquals('C', $tdContent);
|
||||||
});
|
}
|
||||||
|
|
||||||
test('paginatedIndex', function (): void {
|
public function testPaginatedIndex(): void
|
||||||
foreach (range(1, 35) as $i) {
|
{
|
||||||
$order = new FoodOrder($this->generateOldUlid());
|
$this->generatePaginatedOrders();
|
||||||
$order->setFoodVendor($this->vendor);
|
|
||||||
$order->close();
|
|
||||||
$this->manager->persist($order);
|
|
||||||
|
|
||||||
}
|
|
||||||
$this->manager->flush();
|
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list");
|
$crawler = $this->client->request('GET', "{$this->path}list");
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('FoodOrder index');
|
self::assertPageTitleContains('FoodOrder index');
|
||||||
$this->assertElementContainsCount(
|
$this->assertElementContainsCount(
|
||||||
$crawler,
|
$crawler,
|
||||||
'td',
|
'td',
|
||||||
|
@ -137,20 +137,20 @@ describe(FoodOrderController::class, function (): void {
|
||||||
0,
|
0,
|
||||||
'next page'
|
'next page'
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
test('paginatedFirstPage', function (int $page, int $prevPage, int $nextPage, int $items = 10): void {
|
/**
|
||||||
foreach (range(1, 35) as $i) {
|
* @testWith [1, 0, 2]
|
||||||
$order = new FoodOrder($this->generateOldUlid());
|
* [2, 1, 3]
|
||||||
$order->setFoodVendor($this->vendor);
|
* [3, 2, 4]
|
||||||
$order->close();
|
* [4, 3, 0, 5]
|
||||||
$this->manager->persist($order);
|
*/
|
||||||
|
public function testPaginatedFirstPage(int $page, int $prevPage, int $nextPage, int $items = 10): void
|
||||||
}
|
{
|
||||||
$this->manager->flush();
|
$this->generatePaginatedOrders();
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list/archive/{$page}");
|
$crawler = $this->client->request('GET', "{$this->path}list/archive/{$page}");
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('FoodOrder index');
|
self::assertPageTitleContains('FoodOrder index');
|
||||||
$this->assertElementContainsCount(
|
$this->assertElementContainsCount(
|
||||||
$crawler,
|
$crawler,
|
||||||
'td',
|
'td',
|
||||||
|
@ -172,36 +172,30 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$target = $node->attr('href');
|
$target = $node->attr('href');
|
||||||
$this->assertTrue(str_ends_with((string) $target, "/{$nextPage}"));
|
$this->assertTrue(str_ends_with((string) $target, "/{$nextPage}"));
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
->with(
|
|
||||||
[
|
|
||||||
[1, 0, 2],
|
|
||||||
[2, 1, 3],
|
|
||||||
[3, 2, 4],
|
|
||||||
[4, 3, 0, 5],
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
test('new', function (): void {
|
public function testNew(): void
|
||||||
|
{
|
||||||
$this->client->getCookieJar()
|
$this->client->getCookieJar()
|
||||||
->set(new Cookie('username', 'Testing-1'));
|
->set(new Cookie('username', 'Testing-1'));
|
||||||
$this->client->request('GET', sprintf('%snew', $this->path));
|
$this->client->request('GET', sprintf('%snew', $this->path));
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
$this->client->submitForm('Save', [
|
$this->client->submitForm('Save', [
|
||||||
'food_order[foodVendor]' => $this->vendor->getId(),
|
'food_order[foodVendor]' => $this->vendor->getId(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertResponseRedirects("{$this->path}list");
|
self::assertResponseRedirects("{$this->path}list");
|
||||||
$this->assertSame(1, $this->repository->count([]));
|
self::assertSame(1, $this->repository->count([]));
|
||||||
$order = $this->repository->findOneBy([
|
$order = $this->repository->findOneBy([
|
||||||
'createdBy' => 'Testing-1',
|
'createdBy' => 'Testing-1',
|
||||||
]);
|
]);
|
||||||
assert($order instanceof FoodOrder);
|
assert($order instanceof FoodOrder);
|
||||||
});
|
}
|
||||||
|
|
||||||
test('open', function (): void {
|
public function testOpen(): void
|
||||||
|
{
|
||||||
$order = new FoodOrder;
|
$order = new FoodOrder;
|
||||||
$order->setFoodVendor($this->vendor);
|
$order->setFoodVendor($this->vendor);
|
||||||
$order->close();
|
$order->close();
|
||||||
|
@ -211,12 +205,13 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%s%s/open', $this->path, $order->getId()));
|
$this->client->request('GET', sprintf('%s%s/open', $this->path, $order->getId()));
|
||||||
$this->assertResponseRedirects("{$this->path}{$order->getId()}");
|
self::assertResponseRedirects("{$this->path}{$order->getId()}");
|
||||||
$openOrder = $this->repository->find($order->getId());
|
$openOrder = $this->repository->find($order->getId());
|
||||||
$this->assertFalse($openOrder->isClosed());
|
$this->assertFalse($openOrder->isClosed());
|
||||||
});
|
}
|
||||||
|
|
||||||
test('close', function (): void {
|
public function testClose(): void
|
||||||
|
{
|
||||||
$order = new FoodOrder;
|
$order = new FoodOrder;
|
||||||
$order->setClosedAt();
|
$order->setClosedAt();
|
||||||
$order->setFoodVendor($this->vendor);
|
$order->setFoodVendor($this->vendor);
|
||||||
|
@ -226,43 +221,20 @@ describe(FoodOrderController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%s%s/close', $this->path, $order->getId()));
|
$this->client->request('GET', sprintf('%s%s/close', $this->path, $order->getId()));
|
||||||
$this->assertResponseRedirects("{$this->path}{$order->getId()}");
|
self::assertResponseRedirects("{$this->path}{$order->getId()}");
|
||||||
$openOrder = $this->repository->find($order->getId());
|
$openOrder = $this->repository->find($order->getId());
|
||||||
$this->assertTrue($openOrder->isClosed());
|
$this->assertTrue($openOrder->isClosed());
|
||||||
});
|
}
|
||||||
|
|
||||||
test('orderRowOpacity', function (): void {
|
private function generatePaginatedOrders(): void
|
||||||
// Create an open order
|
{
|
||||||
$openOrder = new FoodOrder;
|
foreach (range(1, 35) as $i) {
|
||||||
$openOrder->setFoodVendor($this->vendor);
|
$order = new FoodOrder($this->generateOldUlid());
|
||||||
|
$order->setFoodVendor($this->vendor);
|
||||||
|
$order->close();
|
||||||
|
$this->manager->persist($order);
|
||||||
|
|
||||||
// Create a closed order
|
}
|
||||||
$closedOrder = new FoodOrder;
|
|
||||||
$closedOrder->setFoodVendor($this->vendor);
|
|
||||||
$closedOrder->close();
|
|
||||||
|
|
||||||
$this->manager->persist($openOrder);
|
|
||||||
$this->manager->persist($closedOrder);
|
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
}
|
||||||
$crawler = $this->client->request('GET', "{$this->path}list");
|
}
|
||||||
$this->assertResponseIsSuccessful();
|
|
||||||
|
|
||||||
// In a real environment, closed orders would be displayed with opacity-25 class
|
|
||||||
// and open orders with opacity-100 class as defined in the table_row.html.twig template.
|
|
||||||
// However, in the test environment, we can only verify that the orders are displayed.
|
|
||||||
|
|
||||||
// Verify that we have the expected number of table rows (2 orders + 1 archive link row)
|
|
||||||
$this->assertCount(1, $crawler->filter('tr.opacity-100'));
|
|
||||||
});
|
|
||||||
})
|
|
||||||
->covers(
|
|
||||||
FoodOrderController::class,
|
|
||||||
FoodOrder::class,
|
|
||||||
FoodVendor::class,
|
|
||||||
FoodOrderRepository::class,
|
|
||||||
MenuItem::class,
|
|
||||||
OrderItem::class,
|
|
||||||
FoodOrderType::class,
|
|
||||||
FoodVendorRepository::class
|
|
||||||
);
|
|
|
@ -1,56 +1,41 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Tests\Feature\Controller;
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
use App\Controller\FoodVendorController;
|
|
||||||
use App\Entity\FoodOrder;
|
|
||||||
use App\Entity\FoodVendor;
|
use App\Entity\FoodVendor;
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
use App\Entity\OrderItem;
|
use App\Tests\DbWebTest;
|
||||||
use App\Form\FoodOrderType;
|
use Override;
|
||||||
use App\Form\FoodVendorType;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
|
||||||
use App\Repository\FoodVendorRepository;
|
|
||||||
|
|
||||||
use function describe;
|
|
||||||
use function pest;
|
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function test;
|
|
||||||
|
|
||||||
pest()
|
final class FoodVendorControllerTest extends DbWebTest
|
||||||
->beforeEach(function (): void {
|
{
|
||||||
$this->setEntityClass(FoodVendor::class);
|
private string $path = '/food/vendor/';
|
||||||
$this->setPath('/food/vendor/');
|
|
||||||
$this->repository = $this->manager->getRepository($this->entityClass);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe(FoodVendorController::class, function (): void {
|
public function testIndex(): void
|
||||||
test('index', function (): void {
|
{
|
||||||
$this->client->request('GET', $this->path);
|
$this->client->request('GET', $this->path);
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
$this->assertPageTitleContains('FoodVendor index');
|
self::assertPageTitleContains('FoodVendor index');
|
||||||
});
|
}
|
||||||
|
|
||||||
test('new', function (): void {
|
public function testNew(): void
|
||||||
$this->assertSame(0, $this->repository->count([]));
|
{
|
||||||
$this->client->request('GET', sprintf('%snew', $this->path));
|
$this->client->request('GET', sprintf('%snew', $this->path));
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
$this->client->submitForm('Save', [
|
$this->client->submitForm('Save', [
|
||||||
'food_vendor[name]' => 'TestingNew',
|
'food_vendor[name]' => 'Testing',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$newVendor = $this->repository->findOneBy([
|
self::assertSame(1, $this->repository->count([]));
|
||||||
'name' => 'TestingNew',
|
}
|
||||||
]);
|
|
||||||
$this->assertInstanceof(FoodVendor::class, $newVendor);
|
|
||||||
$this->assertSame(1, $this->repository->count([]));
|
|
||||||
|
|
||||||
});
|
public function testShow(): void
|
||||||
|
{
|
||||||
test('show', function (): void {
|
|
||||||
$fixture = new FoodVendor;
|
$fixture = new FoodVendor;
|
||||||
$fixture->setName('My Title');
|
$fixture->setName('My Title');
|
||||||
$fixture->setMenuLink('https://example.com/');
|
$fixture->setMenuLink('https://example.com/');
|
||||||
|
@ -72,9 +57,10 @@ describe(FoodVendorController::class, function (): void {
|
||||||
)->text();
|
)->text();
|
||||||
$this->assertSame('My Title', $nameNode);
|
$this->assertSame('My Title', $nameNode);
|
||||||
$this->assertSame('https://example.com/', $menuLinkNode);
|
$this->assertSame('https://example.com/', $menuLinkNode);
|
||||||
});
|
}
|
||||||
|
|
||||||
test('show with menu items', function (): void {
|
public function testShowMenuItems(): void
|
||||||
|
{
|
||||||
$fixture = new FoodVendor;
|
$fixture = new FoodVendor;
|
||||||
$fixture->setName('My Title');
|
$fixture->setName('My Title');
|
||||||
|
|
||||||
|
@ -119,12 +105,13 @@ describe(FoodVendorController::class, function (): void {
|
||||||
)->text();
|
)->text();
|
||||||
$this->assertSame('My Title', $nameNode);
|
$this->assertSame('My Title', $nameNode);
|
||||||
|
|
||||||
$itemNodes = $crawler->filter('ul.list-group li.list-group-item');
|
$itemNodes = $crawler->filter('li');
|
||||||
|
|
||||||
$this->assertCount(4, $itemNodes);
|
$this->assertCount(4, $itemNodes);
|
||||||
});
|
}
|
||||||
|
|
||||||
test('edit', function (): void {
|
public function testEdit(): void
|
||||||
|
{
|
||||||
$fixture = new FoodVendor;
|
$fixture = new FoodVendor;
|
||||||
$fixture->setName('Value');
|
$fixture->setName('Value');
|
||||||
$fixture->setMenuLink('Value');
|
$fixture->setMenuLink('Value');
|
||||||
|
@ -159,26 +146,18 @@ describe(FoodVendorController::class, function (): void {
|
||||||
'food_vendor[phone]' => '1234567890',
|
'food_vendor[phone]' => '1234567890',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertResponseRedirects('/food/vendor/');
|
self::assertResponseRedirects('/food/vendor/');
|
||||||
|
|
||||||
$fixture = $this->repository->findAll();
|
$fixture = $this->repository->findAll();
|
||||||
|
|
||||||
$this->assertSame('Something New', $fixture[0]->getName());
|
self::assertSame('Something New', $fixture[0]->getName());
|
||||||
$this->assertSame('https://example.com/', $fixture[0]->getMenuLink());
|
self::assertSame('https://example.com/', $fixture[0]->getMenuLink());
|
||||||
$this->assertSame('1234567890', $fixture[0]->getPhone());
|
self::assertSame('1234567890', $fixture[0]->getPhone());
|
||||||
});
|
}
|
||||||
|
|
||||||
})
|
#[Override]
|
||||||
->covers(
|
public function getEntityClass(): string
|
||||||
FoodOrder::class,
|
{
|
||||||
FoodVendor::class,
|
return FoodVendor::class;
|
||||||
FoodOrderRepository::class,
|
}
|
||||||
MenuItem::class,
|
}
|
||||||
OrderItem::class,
|
|
||||||
FoodOrderType::class,
|
|
||||||
FoodVendorRepository::class,
|
|
||||||
FoodVendorController::class,
|
|
||||||
FoodVendorType::class
|
|
||||||
)
|
|
||||||
|
|
||||||
;
|
|
76
tests/Controller/HomeControllerTest.php
Normal file
76
tests/Controller/HomeControllerTest.php
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
|
use App\Tests\DbWebTest;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
final class HomeControllerTest extends DbWebTest
|
||||||
|
{
|
||||||
|
public function testIndex(): void
|
||||||
|
{
|
||||||
|
$this->client->request(
|
||||||
|
'GET',
|
||||||
|
'/'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(302);
|
||||||
|
self::assertResponseHeaderSame('Location', '/food/order/list');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSetUsername(): void
|
||||||
|
{
|
||||||
|
$this->client->request(
|
||||||
|
'GET',
|
||||||
|
'/username',
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
|
$this->client->submitForm('Save', [
|
||||||
|
'user_name_form[username]' => 'Testing-1',
|
||||||
|
]);
|
||||||
|
self::assertResponseStatusCodeSame(302);
|
||||||
|
self::assertResponseHeaderSame('Location', '/food/order/list');
|
||||||
|
self::assertResponseCookieValueSame('username', 'Testing-1');
|
||||||
|
|
||||||
|
$crawler = $this->client->request(
|
||||||
|
'GET',
|
||||||
|
'/username',
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
$crawler->filter('#user_name_form_username')
|
||||||
|
->last()
|
||||||
|
->attr('value', ''),
|
||||||
|
'Testing-1'
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRemoveUsername(): void
|
||||||
|
{
|
||||||
|
$this->client->request(
|
||||||
|
'GET',
|
||||||
|
'/username',
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
|
$this->client->submitForm('Save', [
|
||||||
|
'user_name_form[username]' => '',
|
||||||
|
]);
|
||||||
|
self::assertResponseStatusCodeSame(302);
|
||||||
|
self::assertResponseHeaderSame('Location', '/food/order/list');
|
||||||
|
self::assertResponseCookieValueSame('username', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function getEntityClass(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
113
tests/Controller/MenuItemControllerTest.php
Normal file
113
tests/Controller/MenuItemControllerTest.php
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
|
use App\Entity\FoodOrder;
|
||||||
|
use App\Entity\FoodVendor;
|
||||||
|
use App\Entity\MenuItem;
|
||||||
|
use App\Tests\DbWebTest;
|
||||||
|
use Override;
|
||||||
|
|
||||||
|
use function sprintf;
|
||||||
|
|
||||||
|
final class MenuItemControllerTest extends DbWebTest
|
||||||
|
{
|
||||||
|
private string $path = '/menu/item/';
|
||||||
|
private FoodVendor $vendor;
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->vendor = new FoodVendor;
|
||||||
|
$this->vendor->setName('Food Vendor');
|
||||||
|
|
||||||
|
$this->manager->persist($this->vendor);
|
||||||
|
$this->manager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function getEntityClass(): string
|
||||||
|
{
|
||||||
|
return MenuItem::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShow(): void
|
||||||
|
{
|
||||||
|
$menuItem = new MenuItem;
|
||||||
|
$menuItem->setName('Testing 1 2');
|
||||||
|
|
||||||
|
$this->vendor->addMenuItem($menuItem);
|
||||||
|
$this->manager->persist($this->vendor);
|
||||||
|
$this->manager->persist($menuItem);
|
||||||
|
$this->manager->flush();
|
||||||
|
|
||||||
|
$crawler = $this->client->request('GET', "{$this->path}{$menuItem->getId()}");
|
||||||
|
$idValue = $crawler->filter(
|
||||||
|
'.table > tbody:nth-child(1) > tr:nth-child(1) > td:nth-child(2)'
|
||||||
|
)->text();
|
||||||
|
$nameValue = $crawler->filter(
|
||||||
|
'.table > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2)'
|
||||||
|
)->text();
|
||||||
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
$this->assertEquals($idValue, $menuItem->getId());
|
||||||
|
$this->assertEquals($nameValue, $menuItem->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEdit(): void
|
||||||
|
{
|
||||||
|
$menuItem = new MenuItem;
|
||||||
|
$menuItem->setName('Testing 1 2');
|
||||||
|
|
||||||
|
$this->vendor->addMenuItem($menuItem);
|
||||||
|
$this->manager->persist($this->vendor);
|
||||||
|
$this->manager->persist($menuItem);
|
||||||
|
$this->manager->flush();
|
||||||
|
|
||||||
|
$crawler = $this->client->request('GET', sprintf('%s%s/edit', $this->path, $menuItem->getId()));
|
||||||
|
$nameElem = $crawler->filter('#menu_item_name');
|
||||||
|
$this->assertEquals(
|
||||||
|
'Testing 1 2',
|
||||||
|
$nameElem->attr('value')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->client->submitForm('Update', [
|
||||||
|
'menu_item[name]' => 'Testing-1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertResponseRedirects(sprintf('/menu/item/%s', $menuItem->getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete(): void
|
||||||
|
{
|
||||||
|
$menuItem = new MenuItem;
|
||||||
|
$menuItem->setName('Testing 1 2');
|
||||||
|
|
||||||
|
$this->vendor->addMenuItem($menuItem);
|
||||||
|
$this->manager->persist($this->vendor);
|
||||||
|
$this->manager->persist($menuItem);
|
||||||
|
|
||||||
|
$order = new FoodOrder;
|
||||||
|
$order->setFoodVendor($this->vendor);
|
||||||
|
|
||||||
|
$this->manager->persist($order);
|
||||||
|
$this->manager->flush();
|
||||||
|
$this->assertFalse($menuItem->isDeleted());
|
||||||
|
|
||||||
|
$this->client->request('GET', "{$this->path}{$menuItem->getId()}");
|
||||||
|
$this->client->submitForm('Delete', []);
|
||||||
|
|
||||||
|
$menuItem = $this->repository->find($menuItem->getId());
|
||||||
|
|
||||||
|
$this->assertTrue($menuItem->isDeleted());
|
||||||
|
|
||||||
|
$crawler = $this->client->request('GET', '/order/item/new/' . $order->getId());
|
||||||
|
$count = $crawler->filter('body > main:nth-child(2) > div:nth-child(5)')
|
||||||
|
->children()
|
||||||
|
->count();
|
||||||
|
$this->assertSame(0, $count);
|
||||||
|
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,30 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Tests\Feature\Controller;
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
use App\Controller\OrderItemController;
|
|
||||||
use App\Entity\FoodOrder;
|
use App\Entity\FoodOrder;
|
||||||
use App\Entity\FoodVendor;
|
use App\Entity\FoodVendor;
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
use App\Entity\OrderItem;
|
use App\Entity\OrderItem;
|
||||||
use App\Form\OrderItemType;
|
|
||||||
use App\Repository\FoodOrderRepository;
|
|
||||||
use App\Repository\MenuItemRepository;
|
use App\Repository\MenuItemRepository;
|
||||||
use App\Repository\OrderItemRepository;
|
use App\Tests\DbWebTest;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
|
use Override;
|
||||||
|
|
||||||
use function describe;
|
|
||||||
use function pest;
|
|
||||||
use function sprintf;
|
use function sprintf;
|
||||||
use function test;
|
|
||||||
|
|
||||||
pest()
|
final class OrderItemControllerTest extends DbWebTest
|
||||||
->beforeEach(function (): void {
|
{
|
||||||
$this->setEntityClass(OrderItem::class);
|
public FoodVendor $vendor;
|
||||||
$this->setPath('/order/item/');
|
public FoodOrder $order;
|
||||||
$this->repository = $this->manager->getRepository($this->entityClass);
|
public Menuitem $menuItem;
|
||||||
|
public MenuItemRepository $menuItemRepository;
|
||||||
|
private string $path = '/order/item/';
|
||||||
|
|
||||||
|
#[Override]
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
$this->vendor = new FoodVendor;
|
$this->vendor = new FoodVendor;
|
||||||
$this->vendor->setName('Food Vendor');
|
$this->vendor->setName('Food Vendor');
|
||||||
|
|
||||||
|
@ -50,11 +51,11 @@ pest()
|
||||||
$this->manager->persist($this->menuItem);
|
$this->manager->persist($this->menuItem);
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$this->menuItemRepository = $this::getContainer()->get(MenuItemRepository::class);
|
$this->menuItemRepository = static::getContainer()->get(MenuItemRepository::class);
|
||||||
});
|
}
|
||||||
|
|
||||||
describe(OrderItemController::class, function (): void {
|
public function testNew(): void
|
||||||
test('new', function (): void {
|
{
|
||||||
$crawler = $this->client->request(
|
$crawler = $this->client->request(
|
||||||
'GET',
|
'GET',
|
||||||
sprintf('%snew/%s', $this->path, $this->order->getId())
|
sprintf('%snew/%s', $this->path, $this->order->getId())
|
||||||
|
@ -65,22 +66,23 @@ describe(OrderItemController::class, function (): void {
|
||||||
|
|
||||||
$this->assertCount(1, $children);
|
$this->assertCount(1, $children);
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
$this->client->submitForm('Save', [
|
$this->client->submitForm('Save', [
|
||||||
'order_item[name]' => 'Testing',
|
'order_item[name]' => 'Testing',
|
||||||
'order_item[extras]' => 'Testing',
|
'order_item[extras]' => 'Testing',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
|
|
||||||
$this->assertSame(1, $this->repository->count([]));
|
self::assertSame(1, $this->repository->count([]));
|
||||||
$this->assertSame(1, $this->menuItemRepository->count([
|
self::assertSame(1, $this->menuItemRepository->count([
|
||||||
'foodVendor' => $this->vendor->getId(),
|
'foodVendor' => $this->vendor->getId(),
|
||||||
]));
|
]));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('new order closed', function (): void {
|
public function testNewOrderClosed(): void
|
||||||
|
{
|
||||||
|
|
||||||
$this->order->setClosedAt(new DateTimeImmutable('-1 Hour'));
|
$this->order->setClosedAt(new DateTimeImmutable('-1 Hour'));
|
||||||
$this->manager->persist($this->order);
|
$this->manager->persist($this->order);
|
||||||
|
@ -91,31 +93,33 @@ describe(OrderItemController::class, function (): void {
|
||||||
sprintf('%snew/%s', $this->path, $this->order->getId())
|
sprintf('%snew/%s', $this->path, $this->order->getId())
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
|
|
||||||
$this->assertSame(0, $this->repository->count([]));
|
self::assertSame(0, $this->repository->count([]));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('new create menu item', function (): void {
|
public function testNewCreateMenuItem(): void
|
||||||
|
{
|
||||||
$this->client->request(
|
$this->client->request(
|
||||||
'GET',
|
'GET',
|
||||||
sprintf('%snew/%s', $this->path, $this->order->getId())
|
sprintf('%snew/%s', $this->path, $this->order->getId())
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertResponseStatusCodeSame(200);
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
|
||||||
$this->client->submitForm('Save', [
|
$this->client->submitForm('Save', [
|
||||||
'order_item[name]' => 'Testing-1',
|
'order_item[name]' => 'Testing-1',
|
||||||
'order_item[extras]' => 'Testing-1',
|
'order_item[extras]' => 'Testing-1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
|
|
||||||
$this->assertSame(1, $this->repository->count([]));
|
self::assertSame(1, $this->repository->count([]));
|
||||||
$this->assertSame(3, $this->menuItemRepository->count([]));
|
self::assertSame(3, $this->menuItemRepository->count([]));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('remove', function (): void {
|
public function testRemove(): void
|
||||||
|
{
|
||||||
$fixture = new OrderItem;
|
$fixture = new OrderItem;
|
||||||
$fixture->setName('Testing');
|
$fixture->setName('Testing');
|
||||||
$fixture->setExtras('Value');
|
$fixture->setExtras('Value');
|
||||||
|
@ -128,11 +132,12 @@ describe(OrderItemController::class, function (): void {
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%sdelete/%s', $this->path, $fixture->getId()));
|
$this->client->request('GET', sprintf('%sdelete/%s', $this->path, $fixture->getId()));
|
||||||
|
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
$this->assertSame(0, $this->repository->count([]));
|
self::assertSame(0, $this->repository->count([]));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('order closed', function (): void {
|
public function testOrderClosed(): void
|
||||||
|
{
|
||||||
$fixture = new OrderItem;
|
$fixture = new OrderItem;
|
||||||
$fixture->setName('Testing');
|
$fixture->setName('Testing');
|
||||||
$fixture->setExtras('Value');
|
$fixture->setExtras('Value');
|
||||||
|
@ -147,11 +152,12 @@ describe(OrderItemController::class, function (): void {
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%sdelete/%s', $this->path, $fixture->getId()));
|
$this->client->request('GET', sprintf('%sdelete/%s', $this->path, $fixture->getId()));
|
||||||
|
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
$this->assertSame(1, $this->repository->count([]));
|
self::assertSame(1, $this->repository->count([]));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('edit', function (): void {
|
public function testEdit(): void
|
||||||
|
{
|
||||||
$orderItem = new OrderItem;
|
$orderItem = new OrderItem;
|
||||||
$orderItem->setName('Testing');
|
$orderItem->setName('Testing');
|
||||||
$orderItem->setExtras('My Extra');
|
$orderItem->setExtras('My Extra');
|
||||||
|
@ -179,14 +185,15 @@ describe(OrderItemController::class, function (): void {
|
||||||
'order_item[extras]' => 'Testing-1',
|
'order_item[extras]' => 'Testing-1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
|
|
||||||
$this->assertSame(1, $this->repository->count([]));
|
self::assertSame(1, $this->repository->count([]));
|
||||||
$this->assertSame(3, $this->menuItemRepository->count([]));
|
self::assertSame(3, $this->menuItemRepository->count([]));
|
||||||
|
|
||||||
});
|
}
|
||||||
|
|
||||||
test('edit order closed', function (): void {
|
public function testEditOrderClosed(): void
|
||||||
|
{
|
||||||
$orderItem = new OrderItem;
|
$orderItem = new OrderItem;
|
||||||
$orderItem->setName('Testing');
|
$orderItem->setName('Testing');
|
||||||
$orderItem->setExtras('My Extra');
|
$orderItem->setExtras('My Extra');
|
||||||
|
@ -200,10 +207,11 @@ describe(OrderItemController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%s%s/edit', $this->path, $orderItem->getId()));
|
$this->client->request('GET', sprintf('%s%s/edit', $this->path, $orderItem->getId()));
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('copy', function (): void {
|
public function testCopy(): void
|
||||||
|
{
|
||||||
$orderItem = new OrderItem;
|
$orderItem = new OrderItem;
|
||||||
$orderItem->setName('My Title');
|
$orderItem->setName('My Title');
|
||||||
$orderItem->setExtras('My Title');
|
$orderItem->setExtras('My Title');
|
||||||
|
@ -214,7 +222,7 @@ describe(OrderItemController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%s%s/copy', $this->path, $orderItem->getId()));
|
$this->client->request('GET', sprintf('%s%s/copy', $this->path, $orderItem->getId()));
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
|
|
||||||
$result = $this->repository->findBy([
|
$result = $this->repository->findBy([
|
||||||
'foodOrder' => $this->order->getId(),
|
'foodOrder' => $this->order->getId(),
|
||||||
|
@ -224,9 +232,10 @@ describe(OrderItemController::class, function (): void {
|
||||||
$this->assertSame($orderItem->getName(), $item->getName());
|
$this->assertSame($orderItem->getName(), $item->getName());
|
||||||
$this->assertSame($orderItem->getExtras(), $item->getExtras());
|
$this->assertSame($orderItem->getExtras(), $item->getExtras());
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
test('copy order closed', function (): void {
|
public function testCopyOrderClosed(): void
|
||||||
|
{
|
||||||
$orderItem = new OrderItem;
|
$orderItem = new OrderItem;
|
||||||
$orderItem->setName('My Title');
|
$orderItem->setName('My Title');
|
||||||
$orderItem->setExtras('My Title');
|
$orderItem->setExtras('My Title');
|
||||||
|
@ -239,23 +248,17 @@ describe(OrderItemController::class, function (): void {
|
||||||
$this->manager->flush();
|
$this->manager->flush();
|
||||||
|
|
||||||
$this->client->request('GET', sprintf('%s%s/copy', $this->path, $orderItem->getId()));
|
$this->client->request('GET', sprintf('%s%s/copy', $this->path, $orderItem->getId()));
|
||||||
$this->assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
self::assertResponseRedirects(sprintf('/food/order/%s', $this->order->getId()));
|
||||||
|
|
||||||
$result = $this->repository->findBy([
|
$result = $this->repository->findBy([
|
||||||
'foodOrder' => $this->order->getId(),
|
'foodOrder' => $this->order->getId(),
|
||||||
]);
|
]);
|
||||||
$this->assertCount(1, $result);
|
$this->assertCount(1, $result);
|
||||||
});
|
}
|
||||||
|
|
||||||
})
|
#[Override]
|
||||||
->covers(
|
public function getEntityClass(): string
|
||||||
OrderItemController::class,
|
{
|
||||||
MenuItemRepository::class,
|
return OrderItem::class;
|
||||||
OrderItemRepository::class,
|
}
|
||||||
OrderItemType::class,
|
}
|
||||||
FoodOrder::class,
|
|
||||||
FoodVendor::class,
|
|
||||||
MenuItem::class,
|
|
||||||
OrderItem::class,
|
|
||||||
FoodOrderRepository::class,
|
|
||||||
);
|
|
|
@ -17,10 +17,10 @@ use function str_contains;
|
||||||
|
|
||||||
abstract class DbWebTest extends WebTestCase
|
abstract class DbWebTest extends WebTestCase
|
||||||
{
|
{
|
||||||
|
abstract public function getEntityClass(): string;
|
||||||
|
|
||||||
protected KernelBrowser $client;
|
protected KernelBrowser $client;
|
||||||
protected string $entityClass = '';
|
|
||||||
protected EntityManagerInterface $manager;
|
protected EntityManagerInterface $manager;
|
||||||
protected string $path = '';
|
|
||||||
protected EntityRepository $repository;
|
protected EntityRepository $repository;
|
||||||
|
|
||||||
#[Override]
|
#[Override]
|
||||||
|
@ -33,6 +33,17 @@ abstract class DbWebTest extends WebTestCase
|
||||||
->getAllMetadata();
|
->getAllMetadata();
|
||||||
$schemaTool->dropDatabase();
|
$schemaTool->dropDatabase();
|
||||||
$schemaTool->updateSchema($metadata);
|
$schemaTool->updateSchema($metadata);
|
||||||
|
|
||||||
|
if ($this->getEntityClass() !== '') {
|
||||||
|
$this->repository = $this->manager->getRepository($this->getEntityClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateOldUlid(int $daysToSubtract = 10): Ulid
|
||||||
|
{
|
||||||
|
$date = (new DateTimeImmutable)->sub(new DateInterval('P' . $daysToSubtract . 'D'));
|
||||||
|
$ulidString = Ulid::generate($date);
|
||||||
|
return Ulid::fromString($ulidString);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertElementContainsCount(Crawler $crawler, string $element, int $count, string $text): void
|
protected function assertElementContainsCount(Crawler $crawler, string $element, int $count, string $text): void
|
||||||
|
@ -45,22 +56,4 @@ abstract class DbWebTest extends WebTestCase
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateOldUlid(int $daysToSubtract = 10): Ulid
|
|
||||||
{
|
|
||||||
$date = (new DateTimeImmutable)->sub(new DateInterval('P' . $daysToSubtract . 'D'));
|
|
||||||
$ulidString = Ulid::generate($date);
|
|
||||||
return Ulid::fromString($ulidString);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setEntityClass(string $entityClass): void
|
|
||||||
{
|
|
||||||
$this->entityClass = $entityClass;
|
|
||||||
$this->repository = $this->manager->getRepository($this->entityClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function setPath(string $path): void
|
|
||||||
{
|
|
||||||
$this->path = $path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
24
tests/Entity/FoodOrderTest.php
Normal file
24
tests/Entity/FoodOrderTest.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Entity;
|
||||||
|
|
||||||
|
use App\Entity\FoodOrder;
|
||||||
|
use App\Entity\OrderItem;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class FoodOrderTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testFoodOrder(): void
|
||||||
|
{
|
||||||
|
$order = new FoodOrder;
|
||||||
|
$orderItem = new OrderItem;
|
||||||
|
$this->assertCount(0, $order->getOrderItems());
|
||||||
|
$order->addOrderItem($orderItem);
|
||||||
|
$order->addOrderItem($orderItem);
|
||||||
|
$this->assertCount(1, $order->getOrderItems());
|
||||||
|
$this->assertSame($order, $orderItem->getFoodOrder());
|
||||||
|
$order->removeOrderItem($orderItem);
|
||||||
|
$this->assertCount(0, $order->getOrderItems());
|
||||||
|
$this->assertNull($orderItem->getFoodOrder());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,17 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Tests\Unit\Entity;
|
namespace App\Tests\Entity;
|
||||||
|
|
||||||
use App\Entity\FoodOrder;
|
use App\Entity\FoodOrder;
|
||||||
use App\Entity\FoodVendor;
|
use App\Entity\FoodVendor;
|
||||||
use App\Entity\MenuItem;
|
use App\Entity\MenuItem;
|
||||||
use InvalidArgumentException;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\Uid\Ulid;
|
use Symfony\Component\Uid\Ulid;
|
||||||
|
|
||||||
use function describe;
|
final class FoodVendorTest extends TestCase
|
||||||
use function test;
|
{
|
||||||
|
public function testFoodVendor(): void
|
||||||
describe(FoodVendor::class, function (): void {
|
{
|
||||||
test('FoodVendor entity', function (): void {
|
|
||||||
$vendor = new FoodVendor;
|
$vendor = new FoodVendor;
|
||||||
$vendor->setName('Test');
|
$vendor->setName('Test');
|
||||||
$this->assertEquals('Test', $vendor->getName());
|
$this->assertEquals('Test', $vendor->getName());
|
||||||
|
@ -21,17 +20,6 @@ describe(FoodVendor::class, function (): void {
|
||||||
$vendor->setPhone('1234567890');
|
$vendor->setPhone('1234567890');
|
||||||
$this->assertEquals('1234567890', $vendor->getPhone());
|
$this->assertEquals('1234567890', $vendor->getPhone());
|
||||||
|
|
||||||
// Test emojis field
|
|
||||||
$this->assertNull($vendor->getEmojis());
|
|
||||||
$emojis = '😀😂🎉👍❤️';
|
|
||||||
$vendor->setEmojis($emojis);
|
|
||||||
$this->assertEquals($emojis, $vendor->getEmojis());
|
|
||||||
|
|
||||||
// Test emojis validation
|
|
||||||
$tooManyEmojis = '😀😂🎉👍❤️🚀🎈🎁🎊🎋🎍🎎🎏🎐🎑🎒🎓🎔🎕🎖🎗🎘🎙🎚🎛🎜🎝🎞🎟🎠🎡🎢';
|
|
||||||
$this->expectException(InvalidArgumentException::class);
|
|
||||||
$vendor->setEmojis($tooManyEmojis);
|
|
||||||
|
|
||||||
$this->assertCount(0, $vendor->getFoodOrders());
|
$this->assertCount(0, $vendor->getFoodOrders());
|
||||||
$order1 = new FoodOrder;
|
$order1 = new FoodOrder;
|
||||||
$vendor->addFoodOrder($order1);
|
$vendor->addFoodOrder($order1);
|
||||||
|
@ -41,9 +29,11 @@ describe(FoodVendor::class, function (): void {
|
||||||
$vendor->removeFoodOrder($order1);
|
$vendor->removeFoodOrder($order1);
|
||||||
$this->assertCount(0, $vendor->getFoodOrders());
|
$this->assertCount(0, $vendor->getFoodOrders());
|
||||||
$this->assertNull($order1->getFoodVendor());
|
$this->assertNull($order1->getFoodVendor());
|
||||||
});
|
|
||||||
|
|
||||||
test('MenutItem entity', function (): void {
|
}
|
||||||
|
|
||||||
|
public function testMenuItem(): void
|
||||||
|
{
|
||||||
$vendor = new FoodVendor;
|
$vendor = new FoodVendor;
|
||||||
$menuItem1 = new MenuItem;
|
$menuItem1 = new MenuItem;
|
||||||
$menuItem2 = new MenuItem;
|
$menuItem2 = new MenuItem;
|
||||||
|
@ -59,9 +49,10 @@ describe(FoodVendor::class, function (): void {
|
||||||
$vendor->addMenuItem($menuItem2);
|
$vendor->addMenuItem($menuItem2);
|
||||||
$this->assertCount(1, $vendor->getMenuItems());
|
$this->assertCount(1, $vendor->getMenuItems());
|
||||||
$this->assertCount(2, $vendor->getMenuItems(true));
|
$this->assertCount(2, $vendor->getMenuItems(true));
|
||||||
});
|
}
|
||||||
|
|
||||||
test('remove foreign menu item', function (): void {
|
public function testRemoveForeignMenuItem(): void
|
||||||
|
{
|
||||||
$vendor1 = new FoodVendor;
|
$vendor1 = new FoodVendor;
|
||||||
$vendor2 = new FoodVendor;
|
$vendor2 = new FoodVendor;
|
||||||
$item1 = new MenuItem;
|
$item1 = new MenuItem;
|
||||||
|
@ -71,6 +62,5 @@ describe(FoodVendor::class, function (): void {
|
||||||
$vendor2->removeMenuItem($item1);
|
$vendor2->removeMenuItem($item1);
|
||||||
$this->assertCount(1, $vendor1->getMenuItems());
|
$this->assertCount(1, $vendor1->getMenuItems());
|
||||||
$this->assertSame($vendor1, $item1->getFoodVendor());
|
$this->assertSame($vendor1, $item1->getFoodVendor());
|
||||||
});
|
}
|
||||||
|
}
|
||||||
})->covers(FoodVendor::class, FoodOrder::class, MenuItem::class);
|
|
30
tests/Entity/MenuItemTest.php
Normal file
30
tests/Entity/MenuItemTest.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Entity;
|
||||||
|
|
||||||
|
use App\Entity\FoodVendor;
|
||||||
|
use App\Entity\MenuItem;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class MenuItemTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testMenuItem(): void
|
||||||
|
{
|
||||||
|
$item = new MenuItem;
|
||||||
|
$item->setName('Test');
|
||||||
|
$this->assertEquals('Test', $item->getName());
|
||||||
|
|
||||||
|
$vendor = new FoodVendor;
|
||||||
|
$vendor->setName('Test');
|
||||||
|
|
||||||
|
$item->setFoodVendor($vendor);
|
||||||
|
$this->assertEquals($vendor, $item->getFoodVendor());
|
||||||
|
|
||||||
|
$this->assertFalse($item->isDeleted());
|
||||||
|
$this->assertNull($item->getDeletedAt());
|
||||||
|
$item->delete();
|
||||||
|
$this->assertTrue($item->isDeleted());
|
||||||
|
$this->assertInstanceOf(DateTimeImmutable::class, $item->getDeletedAt());
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue