WIP: Implement frontend

This commit is contained in:
Björn Fromme
2023-09-18 13:10:20 +02:00
parent bfc5e5e242
commit ce97017ac4
70 changed files with 8751 additions and 337 deletions
+1
View File
@@ -1,3 +1,4 @@
import './bootstrap.js';
/* /*
* Welcome to your app's main JavaScript file! * Welcome to your app's main JavaScript file!
* *
+10
View File
@@ -0,0 +1,10 @@
import { startStimulusApp } from '@symfony/stimulus-bridge';
// Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
true,
/\.[jt]sx?$/
));
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);
+4
View File
@@ -0,0 +1,4 @@
{
"controllers": [],
"entrypoints": []
}
@@ -0,0 +1,88 @@
import { Controller } from '@hotwired/stimulus'
import { useFetch } from '../mixins/use_fetch'
export default class extends Controller {
static targets = [ 'title', 'content', 'spinner', 'backbutton' ]
static values = { loading: Boolean }
connect() {
useFetch(this)
}
show({ title, url }) {
this.titleTarget.innerHTML = title
this.element.classList.remove('hidden')
this.loadContent(url)
}
hide() {
this.element.classList.add('hidden')
}
loadContent(url) {
this.loadingValue = true
fetch(url, this.getInitObject())
.then(this.checkStatus)
.then(this.parseJSON)
.then(response => {
this.handleResponse(response)
})
.catch(error => {
this.handleResponse(this.createErrorResponse(error))
})
.finally(() => {
this.loadingValue = false
})
}
submitForm(event) {
event.preventDefault()
this.loadingValue = true
const form = event.target
const formData = new FormData(form)
if (this.hasBackbuttonTarget && event.submitter === this.backbuttonTarget) {
formData.append(this.backbuttonTarget.name, this.backbuttonTarget.value)
}
fetch(form.action, this.getInitObject('post', formData))
.then(this.checkStatus)
.then(this.parseJSON)
.then(response => {
this.handleResponse(response)
})
.catch(error => {
this.handleResponse(this.createErrorResponse(error))
})
.finally(() => {
this.loadingValue = false
})
}
handleResponse(modalResponse) {
if (modalResponse.redirect) {
window.location = modalResponse.redirect
return
}
if (true === modalResponse.close) {
this.element.classList.add('hidden')
return
}
this.contentTarget.innerHTML = modalResponse.content
}
createErrorResponse(error) {
return {
close: false,
redirect: null,
content: error.message,
}
}
loadingValueChanged() {
this.spinnerTarget.classList.toggle('hidden', false === this.loadingValue)
this.contentTarget.classList.toggle('hidden', true === this.loadingValue)
}
}
@@ -0,0 +1,78 @@
import { Controller } from '@hotwired/stimulus'
import { useFetch } from '../mixins/use_fetch'
import Autocomplete from '@trevoreyre/autocomplete-js'
export default class extends Controller {
static targets = [ 'input', 'field', 'resetButton', 'searchIcon' ]
static values = { url: String, choices: Array, selected: Boolean }
initialize() {
useFetch(this)
}
connect() {
const searchHandler = input => {
return new Promise(resolve => {
// Check input minimum length
if (input.length < 2) {
return resolve([])
}
// Search choices if provided
if (this.choicesValue.length > 0) {
return resolve(this.choicesValue.filter(choice => {
return -1 !== choice.text.toLowerCase().search(input.toLowerCase())
}))
}
// Use API otherwise
let data = { search: input }
fetch(this.urlValue, { method: 'POST', body: JSON.stringify(data), credentials: 'include' })
.then(this.checkStatus)
.then(this.parseJSON)
.then(data => {
resolve(data)
})
})
}
const submitHandler = result => {
this.fieldTarget.value = result.value
this.selectedValue = true
this.element.dispatchEvent(new CustomEvent('select', {
detail: { result }
}))
}
new Autocomplete(this.element, {
search: searchHandler,
getResultValue: result => result.text,
onSubmit: submitHandler,
debounceTime: 250,
})
if (this.fieldTarget.value) {
this.selectedValue = true
}
this.element.addEventListener('keypress', e => {
if (e.key === 'Enter') {
e.preventDefault()
}
})
}
reset() {
this.inputTarget.value = null
this.fieldTarget.value = null
this.selectedValue = false
this.element.dispatchEvent(new CustomEvent('reset'))
}
selectedValueChanged() {
this.resetButtonTarget.classList.toggle('hidden', false === this.selectedValue)
this.searchIconTarget.classList.toggle('hidden', this.selectedValue)
}
}
@@ -0,0 +1,17 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = [ 'form', 'title', 'content' ]
show({ action, title, content }) {
this.formTarget.action = action
this.titleTarget.innerText = title
this.contentTarget.innerHTML = content
this.element.classList.remove('hidden')
}
hide() {
this.element.classList.add('hidden')
}
}
@@ -0,0 +1,57 @@
import { Controller } from '@hotwired/stimulus'
import flatpickr from 'flatpickr';
import { German } from 'flatpickr/dist/l10n/de';
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = [ 'field' ]
static values = { format: String, minDate: String, maxDate: String, disableWeekends: Boolean }
initialize() {
flatpickr.localize(German);
flatpickr.setDefaults({
dateFormat: 'd.m.Y',
allowInput: true
});
}
connect() {
const dateFormat = this.formatValue || 'd.m.Y'
const minDate = this.minDateValue
const maxDate = this.maxDateValue
const options = {
dateFormat: 'Y-m-d',
altInput: true,
altFormat: dateFormat,
minDate,
maxDate,
allowInput: true,
onClose(dates, currentdatestring, instance) {
instance.setDate(instance.altInput.value, true, instance.config.altFormat)
this.element.dispatchEvent(new CustomEvent('datepicker:closed'))
},
onChange(dates, currentdatestring, instance) {
this.element.dispatchEvent(new CustomEvent('datepicker:picked', {
detail: {
date: instance.altInput.value
}
}))
},
}
if (true === this.disableWeekendsValue) {
options['disable'] = [
function(date) {
return (date.getDay() === 0 || date.getDay() === 6);
}
]
}
this.picker = flatpickr(this.fieldTarget, options)
}
disconnect() {
this.picker.destroy()
}
}
@@ -0,0 +1,41 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static targets = [ 'fields', 'field', 'addButton' ]
static values = {
prototype: String,
maxItems: Number,
itemsCount: Number,
}
connect () {
this.index = this.itemsCountValue = this.fieldTargets.length
}
addItem() {
let prototype = JSON.parse(this.prototypeValue)
const newField = prototype.replace(/__name__/g, this.index)
this.fieldsTarget.insertAdjacentHTML('beforeend', newField)
this.index++
this.itemsCountValue++
}
removeItem(event) {
this.fieldTargets.forEach(element => {
if (element.contains(event.target)) {
element.remove()
this.itemsCountValue--
}
})
}
itemsCountValueChanged() {
if (false === this.hasAddButtonTarget || 0 === this.maxItemsValue) {
return
}
const maxItemsReached = this.itemsCountValue >= this.maxItemsValue
this.addButtonTarget.classList.toggle('hidden', maxItemsReached)
}
}
@@ -0,0 +1,16 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static outlets = [ 'mobilenav' ]
static classes = [ 'closed' ]
trigger({ params: { mode } }) {
this.mobilenavOutlet.toggle(mode)
}
toggle(mode) {
// Add animation classes only now to avoid fouc
this.element.classList.add('transition-transform', 'duration-300')
this.element.classList.toggle(this.closedClass, 'close' === mode)
}
}
@@ -0,0 +1,20 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static outlets = [ 'confirmation-modal', 'ajax-modal' ]
confirmation({ params: { title, action, content } }) {
this.confirmationModalOutlet.show({
title: title,
action: action,
content: content,
})
}
ajax({ params: { title, url } }) {
this.ajaxModalOutlet.show({
title: title,
url: url,
})
}
}
+32
View File
@@ -0,0 +1,32 @@
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
static classes = [ 'buttonActive' ]
static targets = [ 'button', 'tab' ]
static values = { activeTab: Number }
select({ params: { tab } }) {
let selectedTab = tab
this.activeTabValue = selectedTab
this.element.dispatchEvent(new CustomEvent('select', {
detail: {
tab: selectedTab
}
}))
}
activeTabValueChanged(activeTab) {
this.tabTargets.forEach((element, index) => {
element.classList.toggle('hidden', activeTab !== index)
})
this.buttonTargets.forEach((element, index) => {
if (this.hasButtonActiveClass) {
element.classList.toggle(this.buttonActiveClass, activeTab === index)
}
})
}
}
+18
View File
@@ -0,0 +1,18 @@
import { Controller } from '@hotwired/stimulus'
import Toastify from 'toastify-js'
export default class extends Controller {
static values = { text: String, class: String }
connect() {
Toastify({
duration: 5000,
text: this.textValue,
gravity: 'top',
position: 'right',
className: this.classValue,
close: true,
}).showToast()
}
}
+152
View File
@@ -0,0 +1,152 @@
import { Controller } from '@hotwired/stimulus'
import Dropzone from 'dropzone'
Dropzone.autoDiscover = false
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = [
'dropzone',
'queue',
'previewTemplate',
'fieldsTemplate',
'fields',
'error',
'errorMessage',
'upload',
]
static values = {
endpoint: String,
maxFiles: Number,
maxFilesize: Number,
acceptedFiles: String,
chunking: Boolean,
language: Object,
params: Object,
uploads: Array,
error: String,
formName: String,
}
initialize() {
this.fieldsTemplate = this.fieldsTemplateTarget.innerHTML
}
connect () {
const dropzone = new Dropzone(this.dropzoneTarget, {
url: this.endpointValue,
withCredentials: true,
maxFiles: this.maxFilesValue,
maxFilesize: this.maxFilesizeValue,
acceptedFiles: this.acceptedFilesValue,
chunking: this.chunkingValue,
chunkSize: 10000000,
parallelUploads: 3,
retryChunks: true,
previewsContainer: this.queueTarget,
previewTemplate: this.previewTemplateTarget.innerHTML,
createImageThumbnails: true,
dictFileTooBig: this.languageValue.fileTooBig,
dictInvalidFileType: this.languageValue.invalidFileType,
dictMaxFilesExceeded: this.languageValue.maxFilesExceeded,
})
dropzone.on('addedfile', file => {
if (this.maxFilesValue > this.uploadsValue.length) {
this._removeFile(file.id)
return
}
this.errorValue = ''
const uploads = this.uploadsValue;
uploads.pop()
this.uploadsValue = uploads
})
dropzone.on('removedfile', file => {
this._removeFile(file.upload.uuid)
})
dropzone.on('maxfilesexceeded', file => {
this.errorValue = dropzone.options.dictMaxFilesExceeded
dropzone.removeFile(file);
})
dropzone.on('error', (file, errorMessage) => {
this.errorValue = errorMessage;
dropzone.removeFile(file)
})
dropzone.on('dragenter', () => {
this.errorValue = ''
})
dropzone.on('success', file => {
const response = JSON.parse(file.xhr.response)
const uploads = this.uploadsValue
uploads.push({
filename: response.filename,
originalFilename: response.originalFilename,
uploaderType: response.uploaderType,
size: response.size,
mimeType: response.mimeType,
fileId: file.upload.uuid
})
this.uploadsValue = uploads
window.dispatchEvent(new CustomEvent('dropzone', {
detail: 'success'
}))
})
dropzone.on('sending', () => {
window.dispatchEvent(new CustomEvent('dropzone', {
detail: 'uploading'
}))
})
}
errorValueChanged () {
if ('' === this.errorValue) {
this.errorTarget.classList.add('hidden')
} else {
this.errorMessageTarget.innerText = this.errorValue
this.errorTarget.classList.remove('hidden')
}
}
uploadsValueChanged (uploads) {
this.fieldsTarget.innerHTML = ''
uploads.forEach((upload, index) => {
const field = this.fieldsTemplate
.replace(/__formName__/g, this.formNameValue)
.replace(/__index__/g, index)
.replace(/__fileId__/g, upload.fileId)
.replace(/__originalFilename__/g, upload.originalFilename)
.replace(/__filename__/g, upload.filename)
.replace(/__mimeType__/g, upload.mimeType)
.replace(/__size__/g, upload.size)
this.fieldsTarget.insertAdjacentHTML('beforeend', field)
})
}
removeFile ({ params }) {
const fileId = params.fileid
this._removeFile(fileId)
}
_removeFile(fileId) {
this.uploadsValue = this.uploadsValue.filter(upload => {
return upload.fileId !== fileId
})
}
removeUpload (event) {
event.preventDefault()
this.uploadTargets.forEach(element => {
if (element.contains(event.target)) {
element.remove()
}
})
}
}
+181
View File
@@ -0,0 +1,181 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-login" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
</symbol>
<symbol id="icon-logout" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
</symbol>
<symbol id="icon-support" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.712 4.33a9.027 9.027 0 011.652 1.306c.51.51.944 1.064 1.306 1.652M16.712 4.33l-3.448 4.138m3.448-4.138a9.014 9.014 0 00-9.424 0M19.67 7.288l-4.138 3.448m4.138-3.448a9.014 9.014 0 010 9.424m-4.138-5.976a3.736 3.736 0 00-.88-1.388 3.737 3.737 0 00-1.388-.88m2.268 2.268a3.765 3.765 0 010 2.528m-2.268-4.796a3.765 3.765 0 00-2.528 0m4.796 4.796c-.181.506-.475.982-.88 1.388a3.736 3.736 0 01-1.388.88m2.268-2.268l4.138 3.448m0 0a9.027 9.027 0 01-1.306 1.652c-.51.51-1.064.944-1.652 1.306m0 0l-3.448-4.138m3.448 4.138a9.014 9.014 0 01-9.424 0m5.976-4.138a3.765 3.765 0 01-2.528 0m0 0a3.736 3.736 0 01-1.388-.88 3.737 3.737 0 01-.88-1.388m2.268 2.268L7.288 19.67m0 0a9.024 9.024 0 01-1.652-1.306 9.027 9.027 0 01-1.306-1.652m0 0l4.138-3.448M4.33 16.712a9.014 9.014 0 010-9.424m4.138 5.976a3.765 3.765 0 010-2.528m0 0c.181-.506.475-.982.88-1.388a3.736 3.736 0 011.388-.88m-2.268 2.268L4.33 7.288m6.406 1.18L7.288 4.33m0 0a9.024 9.024 0 00-1.652 1.306A9.025 9.025 0 004.33 7.288" />
</symbol>
<symbol id="icon-alert" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
</symbol>
<symbol id="icon-close" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</symbol>
<symbol id="icon-back" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 15L3 9m0 0l6-6M3 9h12a6 6 0 010 12h-3" />
</symbol>
<symbol id="icon-menu" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</symbol>
<symbol id="icon-edit" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</symbol>
<symbol id="icon-plus" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</symbol>
<symbol id="icon-search" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</symbol>
<symbol id="icon-user" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
</symbol>
<symbol id="icon-users" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z" />
</symbol>
<symbol id="icon-user-add" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19 7.5v3m0 0v3m0-3h3m-3 0h-3m-2.25-4.125a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zM4 19.235v-.11a6.375 6.375 0 0112.75 0v.109A12.318 12.318 0 0110.374 21c-2.331 0-4.512-.645-6.374-1.766z" />
</symbol>
<symbol id="icon-lab" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
</symbol>
<symbol id="icon-delete" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</symbol>
<symbol id="icon-check" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</symbol>
<symbol id="icon-upload" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor">
<path d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
</symbol>
<symbol id="icon-download" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
</symbol>
<symbol id="icon-sort" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
</symbol>
<symbol id="icon-computer" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25"/>
</symbol>
<symbol id="icon-arrow-left" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 12h-15m0 0l6.75 6.75M4.5 12l6.75-6.75" />
</symbol>
<symbol id="icon-arrow-right" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12h15m0 0l-6.75-6.75M19.5 12l-6.75 6.75" />
</symbol>
<symbol id="icon-document" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" />
</symbol>
<symbol id="icon-filter" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
</symbol>
<symbol id="icon-refresh" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
</symbol>
<symbol id="icon-settings" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.107-1.204l-.527-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</symbol>
<symbol id="icon-at" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" d="M16.5 12a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 10-2.636 6.364M16.5 12V8.25" />
</symbol>
<symbol id="icon-mail" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" />
</symbol>
<symbol id="icon-phone" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 4.5v2.25z" />
</symbol>
<symbol id="icon-calendar" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5m-9-6h.008v.008H12v-.008zM12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zM9.75 15h.008v.008H9.75V15zm0 2.25h.008v.008H9.75v-.008zM7.5 15h.008v.008H7.5V15zm0 2.25h.008v.008H7.5v-.008zm6.75-4.5h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V15zm0 2.25h.008v.008h-.008v-.008zm2.25-4.5h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5V15z" />
</symbol>
<symbol id="icon-photo" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" />
</symbol>
<symbol id="icon-print" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0m10.5 0V3.375c0-.621-.504-1.125-1.125-1.125h-8.25c-.621 0-1.125.504-1.125 1.125v3.659M18 10.5h.008v.008H18V10.5zm-3 0h.008v.008H15V10.5z" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="icon-uturn-up" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 9l6-6m0 0l6 6m-6-6v12a6 6 0 01-12 0v-3"/>
</symbol>
<symbol id="icon-archive" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="icon-todo" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0118 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3l1.5 1.5 3-3.75"/>
</symbol>
<symbol id="icon-mask" fill="currentColor" stroke="none" viewBox="0 0 576 512">
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M288 64C39.52 64 0 182.1 0 273.5C0 379.5 78.8 448 176 448c27.33 0 51.21-6.516 66.11-36.79l19.93-40.5C268.3 358.6 278.1 352.4 288 352.1c9.9 .3711 19.7 6.501 25.97 18.63l19.93 40.5C348.8 441.5 372.7 448 400 448c97.2 0 176-68.51 176-174.5C576 182.1 536.5 64 288 64zM400 400c-18.12 0-19.56-2.924-23.04-9.986l-20.35-41.33c-13.87-26.86-38.85-43.53-66.82-44.57L288 304L286.2 304.1c-27.96 1.049-52.94 17.71-67.24 45.41l-19.93 40.49C195.6 397.1 194.1 400 176 400c-75.36 0-128-52.04-128-126.5C48 229.3 48 112 288 112s240 117.3 240 161.5C528 347.1 475.4 400 400 400zM160 192C124.7 192 96 220.7 96 256s28.65 64 64 64c35.35 0 64-28.65 64-64S195.3 192 160 192zM416 192c-35.35 0-64 28.65-64 64s28.65 64 64 64c35.35 0 64-28.65 64-64S451.3 192 416 192z"/>
</symbol>
<symbol id="icon-excel" fill="currentColor" stroke="none" viewBox="0 0 384 512">
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M365.3 93.38l-74.63-74.64C278.6 6.742 262.3 0 245.4 0H64C28.65 0 0 28.65 0 64l.0065 384c0 35.34 28.65 64 64 64H320c35.2 0 64-28.8 64-64V138.6C384 121.7 377.3 105.4 365.3 93.38zM336 448c0 8.836-7.164 16-16 16H64.02c-8.838 0-16-7.164-16-16L48 64.13c0-8.836 7.164-16 16-16h160L224 128c0 17.67 14.33 32 32 32h79.1V448zM229.1 233.3L192 280.9L154.9 233.3C146.8 222.8 131.8 220.9 121.3 229.1C110.8 237.2 108.9 252.3 117.1 262.8L161.6 320l-44.53 57.25c-8.156 10.47-6.25 25.56 4.188 33.69C125.7 414.3 130.8 416 135.1 416c7.156 0 14.25-3.188 18.97-9.25L192 359.1l37.06 47.65C233.8 412.8 240.9 416 248 416c5.125 0 10.31-1.656 14.72-5.062c10.44-8.125 12.34-23.22 4.188-33.69L222.4 320l44.53-57.25c8.156-10.47 6.25-25.56-4.188-33.69C252.2 220.9 237.2 222.8 229.1 233.3z"/>
</symbol>
<symbol id="icon-pdf" fill="currentColor" stroke="none" viewBox="0 0 384 512">
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M365.3 93.38l-74.63-74.64C278.6 6.742 262.3 0 245.4 0H64C28.65 0 0 28.65 0 64l.0065 384c0 35.34 28.65 64 64 64H320c35.2 0 64-28.8 64-64V138.6C384 121.7 377.3 105.4 365.3 93.38zM336 448c0 8.836-7.164 16-16 16H64.02c-8.838 0-16-7.164-16-16L48 64.13c0-8.836 7.164-16 16-16h160L224 128c0 17.67 14.33 32 32 32h79.1V448zM202 286.1c.877-2.688 1.74-5.398 2.582-8.145c1.434-5.762 7.488-31.54 7.488-52.47C212.1 207 197.1 192 178.6 192C160.1 192 145.1 207 145.1 225.5c0 .2969 .1641 28.81 13.85 62.3c-7.035 19.36-15.57 38.8-25.41 57.93c-21.49 10.11-39.24 22.23-52.8 36.07c-6.234 6.438-9.367 14.74-9.367 24.72c0 18.45 15.01 33.46 33.46 33.46c10.8 0 20.98-5.227 27.22-13.98c7.322-10.28 18.38-26.9 30.47-48.95c15.8-6.352 33.88-11.72 53.88-16c13.55 9.578 28.9 17.29 45.71 22.95c4.527 1.551 9.402 2.348 14.43 2.348c20.26 0 36.13-16.19 36.13-36.86c0-20.33-16.54-36.87-36.87-36.87h-3.705c-2.727 .125-20.51 1.141-45.37 5.367C216.9 308.9 208.6 298.3 202 286.1zM110.2 410.4c-3.273 4.688-12.03 2.777-12.03-5.312c0-1.754 .6289-3.43 1.729-4.555c9.02-9.219 19.94-17.05 31.85-23.72C122.3 393.1 114.3 404.7 110.2 410.4zM178.6 218.8c3.693 0 6.703 3.008 6.703 6.703c0 15.21-4.109 34.84-5.746 42.1C172.1 245 171.9 227.2 171.9 225.5C171.9 221.8 174.9 218.8 178.6 218.8zM162.3 348.3c6.611-13.48 13.22-28.46 19.38-44.7c6.389 10.92 14.56 21.86 24.96 31.97C192.6 338.8 177.4 342.9 162.3 348.3zM272.4 339.5h3.352c5.539 0 10.05 4.5 10.05 10.79c0 5.129-4.176 9.32-9.32 9.32c-2.029 0-4.059-.3164-5.852-.9414c-12.33-4.137-23.11-9.32-32.54-15.19C258.3 340.3 272.1 339.5 272.4 339.5z"/>
</symbol>
<symbol id="icon-file" fill="currentColor" stroke="none" viewBox="0 0 384 512">
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M365.3 93.38l-74.63-74.64C278.6 6.743 262.3 0 245.4 0L64-.0001c-35.35 0-64 28.65-64 64l.0065 384c0 35.35 28.65 64 64 64H320c35.2 0 64-28.8 64-64V138.6C384 121.7 377.3 105.4 365.3 93.38zM320 464H64.02c-8.836 0-15.1-7.163-16-15.1L48 64.13c-.0004-8.837 7.163-16 16-16h160L224 128c0 17.67 14.33 32 32 32h79.1v288C336 456.8 328.8 464 320 464z"/>
</symbol>
<symbol id="icon-sigma" fill="currentColor" stroke="none" viewBox="0 0 384 512">
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M335.1 136l.0005-56H83.59l165 158.7C253.3 243.2 255.1 249.5 255.1 256s-2.656 12.78-7.375 17.31L83.59 432h252.4l-.0005-56c0-13.25 10.75-24 24-24C373.2 352 384 362.8 384 376v80c0 13.25-10.75 24-24 24H23.99c-9.782 0-18.59-5.938-22.25-15.03s-1.438-19.47 5.625-26.28L197.4 256L7.364 73.31C.3015 66.5-1.917 56.13 1.739 47.03S14.21 32 23.99 32h336C373.2 32 384 42.75 384 56v80C384 149.3 373.2 160 359.1 160C346.7 160 335.1 149.3 335.1 136z"/>
</symbol>
<symbol id="icon-percent" fill="currentColor" stroke="none" viewBox="0 0 384 512">
<!--! Font Awesome Pro 6.2.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="M376.1 71.03c-9.375-9.375-24.56-9.375-33.94 0l-336 336c-9.375 9.375-9.375 24.56 0 33.94C11.72 445.7 17.84 448 24 448s12.28-2.344 16.97-7.031l336-336C386.3 95.59 386.3 80.41 376.1 71.03zM64 176c26.51 0 48-21.49 48-48S90.51 80 64 80C37.49 80 16 101.5 16 128S37.49 176 64 176zM320 336c-26.51 0-48 21.49-48 48s21.49 48 48 48c26.51 0 48-21.49 48-48S346.5 336 320 336z"/>
</symbol>
<symbol id="icon-info" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</symbol>
<symbol id="icon-cancel" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</symbol>
<symbol id="icon-bell" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
</symbol>
<symbol id="icon-folder" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
<path d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<symbol id="icon-spinner" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
<g fill="none" fill-rule="evenodd" stroke-width="2">
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="0s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="0s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="-0.9s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="-0.9s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
</g>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 24 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 243 74" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><g><path d="M161.8,41.6c2.2,0 3.8,1.6 3.8,3.8c0,2 -1.3,3.3 -2.8,3.7l3.3,5.8l-1.8,0l-3.1,-5.6l-3.7,0l0,5.6l-1.6,0l0,-13.3l5.9,0Zm-4.2,6.3l4.2,0c1.2,0 2.2,-1 2.2,-2.5c0,-1.6 -1.1,-2.5 -2.6,-2.5l-3.8,0l0,5Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M181.9,41.6l0,1.4l-7.5,0l0,4.8l6.5,0l0,1.4l-6.5,0l0,4.4l7.7,0l0,1.3l-9.3,0l0,-13.3l9.1,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><rect x="189.1" y="41.6" width="1.6" height="13.3" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M197.7,54.1l0.4,-1.3c0.5,0.2 2.3,0.9 4.1,0.9c2.2,0 3.3,-0.9 3.3,-2.2c0,-1.7 -1.7,-2.1 -3.4,-2.6c-2.1,-0.6 -4.3,-1.3 -4.3,-3.9c0,-2.4 1.8,-3.6 4.5,-3.6c1.6,0 3.3,0.5 4.2,0.9l-0.4,1.3c0,0 -2,-0.8 -3.7,-0.8c-1.8,0 -3,0.9 -3,2.2c0,1.6 1.8,2.1 3.4,2.6c2.1,0.6 4.3,1.3 4.3,3.9c0,2.5 -2,3.6 -4.7,3.6c-2.1,0 -3.9,-0.6 -4.7,-1Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M223,41.6l0,1.4l-7.5,0l0,4.8l6.5,0l0,1.4l-6.5,0l0,4.4l7.7,0l0,1.3l-9.3,0l0,-13.3l9.1,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M231.8,41.6l4.2,6.3l2.9,4.5l-0.1,-4.5l0,-6.3l1.6,0l0,13.3l-1.7,0l-4.2,-6.5l-2.8,-4.4l0.1,4.5l0,6.4l-1.6,0l0,-13.3l1.6,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M165.1,60.1l0,1.4l-7.5,0l0,4.8l6.5,0l0,1.4l-6.5,0l0,4.4l7.7,0l0,1.4l-9.3,0l0,-13.4l9.1,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M171.2,60.1l4,11.3l4,-11.3l1.7,0l-4.8,13.3l-1.8,0l-4.8,-13.3l1.7,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M195.1,60.1l0,1.4l-7.5,0l0,4.8l6.5,0l0,1.4l-6.5,0l0,4.4l7.7,0l0,1.4l-9.3,0l0,-13.4l9.1,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M202.6,60.1l4.2,6.3l2.9,4.5l-0.1,-4.5l0,-6.3l1.6,0l0,13.3l-1.7,0l-4.2,-6.5l-2.8,-4.4l0.1,4.5l0,6.4l-1.6,0l0,-13.3l1.6,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M226.9,60.1l0,1.4l-4.5,0l0,11.9l-1.6,0l0,-12l-4.5,0l0,-1.4l10.6,0l0,0.1Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M231,72.5l0.4,-1.3c0.5,0.2 2.3,0.9 4.1,0.9c2.2,0 3.3,-0.9 3.3,-2.2c0,-1.7 -1.7,-2.1 -3.4,-2.6c-2.1,-0.6 -4.3,-1.3 -4.3,-3.9c0,-2.4 1.8,-3.6 4.5,-3.6c1.6,0 3.3,0.5 4.2,0.9l-0.4,1.3c0,0 -2,-0.8 -3.7,-0.8c-1.8,0 -3,0.9 -3,2.2c0,1.6 1.8,2.1 3.4,2.6c2.1,0.6 4.3,1.3 4.3,3.9c0,2.5 -2,3.6 -4.7,3.6c-2,0 -3.9,-0.6 -4.7,-1Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M178.7,0.9l0,5.3l-16.2,0l0,8.3l13.8,0l0,5.4l-13.8,0l0,7.6l16.5,0l0,5.3l-22.9,0l0,-31.9l22.6,0Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M203.6,7.9c0,2.7 -1.6,5.7 -5.2,7.9l5.5,6.4c0,0 1.1,-2 1.1,-4.6l4.8,0c-0.1,4.9 -2.4,8.7 -2.4,8.7l5.9,6.8l-7.1,0l-2.4,-2.8c-2.2,1.9 -5.1,3 -8.4,3c-6,0 -11.4,-3.5 -11.4,-10.1c0,-4.3 2.8,-7 5.7,-9c-1.6,-2.1 -2.8,-3.5 -2.8,-6.3c0,-5.5 4.6,-7.6 8.2,-7.6c3.6,0 8.5,2.1 8.5,7.6Zm-13.7,15.2c0,3 2.2,4.9 5.7,4.9c1.8,0 3.4,-0.5 4.7,-1.6l-7,-8.1c-2,1.1 -3.4,2.6 -3.4,4.8Zm2.9,-14.9c0,1.6 1,2.5 2.1,3.7c1.8,-0.8 3.1,-2.2 3.1,-3.8c0,-1.9 -1.3,-2.6 -2.6,-2.6c-1.7,0 -2.6,1.1 -2.6,2.7Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M233,0.9c5.4,0 9.8,4.5 9.8,10.2c0,6.1 -4.4,10.4 -9.7,10.4l-7.7,0l0,11.4l-6.4,0l0,-32l14,0Zm-7.6,15.2l6.4,0c3,0 4.7,-2.2 4.7,-5c0,-3.5 -2.7,-4.8 -5.2,-4.8l-5.9,0l0,9.8Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M107,0.3c-36.4,5.5 -81.6,19.1 -81.6,38.2c0,17.7 41.8,13.2 41.8,13.2c0,0 -67,22.3 -67,-7c0,-33.2 84.5,-44.4 106.8,-44.4Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M17,73.5c36.4,-5.4 81.6,-19.1 81.6,-38.2c0,-17.7 -41.8,-13.2 -41.8,-13.2c0,0 67,-22.3 67,7c0,33.3 -84.5,44.4 -106.8,44.4Z" style="fill:#f8c62f;fill-rule:nonzero;"/><g><path d="M60.7,34.3l-12.8,4l12.2,0.8l-0.4,-2.4l1,-2.4Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M75.9,35.2l-9.2,2.9l-3.5,9.7l-1.7,-11.3l4,-10.7l1.7,7.9l8.7,1.5Z" style="fill:#f8c62f;fill-rule:nonzero;"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

+29
View File
@@ -0,0 +1,29 @@
export const useFetch = controller => {
Object.assign(controller, {
checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
const error = new Error(response.statusText)
error.response = response
throw error
}
},
parseJSON(response) {
return response.json()
},
parseHTML(response) {
return response.text()
},
getInitObject(method = 'get', body = null) {
return {
method,
body,
credentials: 'include',
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
}
}
})
}
+5 -7
View File
@@ -1,7 +1,5 @@
.btn { @import "components/autocomplete.css";
@apply inline-flex justify-center rounded-lg text-sm font-semibold py-2.5 px-4 bg-primary text-white hover:bg-primary/80 w-full; @import "components/button.css";
} @import "components/datepicker.css";
@import "components/menu.css";
.btn--secondary { @import "components/toast.css";
@apply bg-secondary text-gray-800 hover:bg-secondary/80;
}
+33
View File
@@ -0,0 +1,33 @@
.autocomplete {
@apply relative w-full;
}
.autocomplete[data-loading="true"]:after {
@apply block absolute top-0 right-0;
@apply absolute w-4 h-4 rounded-full right-0 mr-4;
content: "";
border: 3px solid rgba(0, 0, 0, 0.12);
border-right: 3px solid rgba(0, 0, 0, 0.48);
top: 50%;
transform: translateY(-50%);
animation: rotate 1s infinite linear;
}
.autocomplete-icon {
@apply absolute top-1/2 right-0 transform -translate-y-1/2 mr-4;
}
.autocomplete[data-loading="true"] .autocomplete-icon {
@apply hidden;
}
.autocomplete-result-list {
@apply bg-white shadow-md;
}
.autocomplete-result {
@apply p-2;
}
.autocomplete-result:hover,
.autocomplete-result[aria-selected="true"] {
@apply bg-gray-200;
}
+7
View File
@@ -0,0 +1,7 @@
.btn {
@apply inline-flex justify-center rounded-lg text-sm uppercase font-semibold py-2.5 px-4 bg-primary text-white hover:bg-primary/80 w-full;
}
.btn--secondary {
@apply bg-secondary text-gray-800 hover:bg-secondary/80;
}
+13
View File
@@ -0,0 +1,13 @@
@import "flatpickr/dist/flatpickr.css";
.flatpickr-day.selected,
.flatpickr-day.selected:hover,
.flatpickr-day.selected:focus {
background-color: theme('colors.secondary');
border-color: theme('colors.secondary');
}
.flatpickr-monthDropdown-months,
.flatpickr-current-month .flatpickr-monthDropdown-months {
@apply form-select pt-0 pb-0 focus:outline-none focus:shadow-none;
}
+28
View File
@@ -0,0 +1,28 @@
.menu {
@apply flex items-center space-x-4 h-8;
}
.menu--mobile {
@apply flex-col space-x-0 h-auto divide-y divide-gray-200;
}
.menu--mobile li {
@apply py-4 w-full;
}
.menu a {
@apply uppercase hover:text-slate-700 text-slate-900 text-sm lg:text-base;
}
.menu--mobile a {
@apply text-lg;
}
.menu .current > a,
.menu .active > a {
@apply text-slate-900 font-bold;
}
.menu .icon-link {
@apply flex items-center space-x-2;
}
+20
View File
@@ -0,0 +1,20 @@
@import "toastify-js/src/toastify.css";
.toastify {
@apply shadow-lg rounded;
}
.toastify--success {
@apply text-gray-100;
background: theme('colors.emerald.600');
}
.toastify--warning {
@apply text-gray-100;
background: theme('colors.red.600');
}
.toastify--info {
@apply text-gray-100;
background: theme('colors.blue.600');
}
+3
View File
@@ -11,6 +11,8 @@
"doctrine/doctrine-bundle": "^2.10", "doctrine/doctrine-bundle": "^2.10",
"doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.15", "doctrine/orm": "^2.15",
"knplabs/knp-menu-bundle": "^3.2",
"nesbot/carbon": "^2.70",
"phpdocumentor/reflection-docblock": "^5.3", "phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.22", "phpstan/phpdoc-parser": "^1.22",
"symfony/apache-pack": "^1.0", "symfony/apache-pack": "^1.0",
@@ -34,6 +36,7 @@
"symfony/runtime": "6.3.*", "symfony/runtime": "6.3.*",
"symfony/security-bundle": "6.3.*", "symfony/security-bundle": "6.3.*",
"symfony/serializer": "6.3.*", "symfony/serializer": "6.3.*",
"symfony/stimulus-bundle": "^2.11",
"symfony/string": "6.3.*", "symfony/string": "6.3.*",
"symfony/translation": "6.3.*", "symfony/translation": "6.3.*",
"symfony/twig-bundle": "6.3.*", "symfony/twig-bundle": "6.3.*",
Generated
+313 -2
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "360b5c9cbf15687cb3c71a043aa7dd52", "content-hash": "c7bd8e1f662fa638390820a36f96a7fd",
"packages": [ "packages": [
{ {
"name": "doctrine/cache", "name": "doctrine/cache",
@@ -1386,6 +1386,143 @@
], ],
"time": "2023-01-14T14:17:03+00:00" "time": "2023-01-14T14:17:03+00:00"
}, },
{
"name": "knplabs/knp-menu",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/KnpMenu.git",
"reference": "bf7d89a7ef406fd2ec1aae6f30f722e844bf6d31"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/KnpMenu/zipball/bf7d89a7ef406fd2ec1aae6f30f722e844bf6d31",
"reference": "bf7d89a7ef406fd2ec1aae6f30f722e844bf6d31",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"conflict": {
"twig/twig": "<1.42.3 || >=2,<2.9"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.6",
"psr/container": "^1.0",
"symfony/http-foundation": "^5.4 || ^6.0",
"symfony/phpunit-bridge": "^6.2",
"symfony/routing": "^5.4 || ^6.0",
"twig/twig": "^2.9 || ^3.0"
},
"suggest": {
"twig/twig": "for the TwigRenderer and the integration with your templates"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Knp\\Menu\\": "src/Knp/Menu"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "KnpLabs",
"homepage": "https://knplabs.com"
},
{
"name": "Christophe Coevoet",
"email": "[email protected]"
},
{
"name": "The Community",
"homepage": "https://github.com/KnpLabs/KnpMenu/contributors"
}
],
"description": "An object oriented menu library",
"homepage": "https://knplabs.com",
"keywords": [
"menu",
"tree"
],
"support": {
"issues": "https://github.com/KnpLabs/KnpMenu/issues",
"source": "https://github.com/KnpLabs/KnpMenu/tree/v3.4.0"
},
"time": "2023-05-17T18:48:46+00:00"
},
{
"name": "knplabs/knp-menu-bundle",
"version": "v3.2.0",
"source": {
"type": "git",
"url": "https://github.com/KnpLabs/KnpMenuBundle.git",
"reference": "a0b4224f872d74ae939589eb1ccf0e11291370a9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/KnpLabs/KnpMenuBundle/zipball/a0b4224f872d74ae939589eb1ccf0e11291370a9",
"reference": "a0b4224f872d74ae939589eb1ccf0e11291370a9",
"shasum": ""
},
"require": {
"knplabs/knp-menu": "^3.1",
"php": "^7.2 || ^8.0",
"symfony/framework-bundle": "^3.4 | ^4.4 | ^5.0 | ^6.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5 | ^9.5",
"symfony/expression-language": "^3.4 | ^4.4 | ^5.0 | ^6.0",
"symfony/phpunit-bridge": "^5.2 | ^6.0",
"symfony/templating": "^3.4 | ^4.4 | ^5.0 | ^6.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Knp\\Bundle\\MenuBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Knplabs",
"homepage": "http://knplabs.com"
},
{
"name": "Christophe Coevoet",
"email": "[email protected]"
},
{
"name": "Symfony Community",
"homepage": "https://github.com/KnpLabs/KnpMenuBundle/contributors"
}
],
"description": "This bundle provides an integration of the KnpMenu library",
"keywords": [
"menu"
],
"support": {
"issues": "https://github.com/KnpLabs/KnpMenuBundle/issues",
"source": "https://github.com/KnpLabs/KnpMenuBundle/tree/v3.2.0"
},
"time": "2021-10-24T07:53:34+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "3.4.0", "version": "3.4.0",
@@ -1487,6 +1624,112 @@
], ],
"time": "2023-06-21T08:46:11+00:00" "time": "2023-06-21T08:46:11+00:00"
}, },
{
"name": "nesbot/carbon",
"version": "2.70.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "d3298b38ea8612e5f77d38d1a99438e42f70341d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/d3298b38ea8612e5f77d38d1a99438e42f70341d",
"reference": "d3298b38ea8612e5f77d38d1a99438e42f70341d",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.1.8 || ^8.0",
"psr/clock": "^1.0",
"symfony/polyfill-mbstring": "^1.0",
"symfony/polyfill-php80": "^1.16",
"symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
"doctrine/dbal": "^2.0 || ^3.1.4",
"doctrine/orm": "^2.7",
"friendsofphp/php-cs-fixer": "^3.0",
"kylekatarnls/multi-tester": "^2.0",
"ondrejmirtes/better-reflection": "*",
"phpmd/phpmd": "^2.9",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12.99 || ^1.7.14",
"phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
"phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
"squizlabs/php_codesniffer": "^3.4"
},
"bin": [
"bin/carbon"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-3.x": "3.x-dev",
"dev-master": "2.x-dev"
},
"laravel": {
"providers": [
"Carbon\\Laravel\\ServiceProvider"
]
},
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"autoload": {
"psr-4": {
"Carbon\\": "src/Carbon/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Brian Nesbitt",
"email": "[email protected]",
"homepage": "https://markido.com"
},
{
"name": "kylekatarnls",
"homepage": "https://github.com/kylekatarnls"
}
],
"description": "An API extension for DateTime that supports 281 different languages.",
"homepage": "https://carbon.nesbot.com",
"keywords": [
"date",
"datetime",
"time"
],
"support": {
"docs": "https://carbon.nesbot.com/docs",
"issues": "https://github.com/briannesbitt/Carbon/issues",
"source": "https://github.com/briannesbitt/Carbon"
},
"funding": [
{
"url": "https://github.com/sponsors/kylekatarnls",
"type": "github"
},
{
"url": "https://opencollective.com/Carbon#sponsor",
"type": "opencollective"
},
{
"url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
"type": "tidelift"
}
],
"time": "2023-09-07T16:43:50+00:00"
},
{ {
"name": "phpdocumentor/reflection-common", "name": "phpdocumentor/reflection-common",
"version": "2.2.0", "version": "2.2.0",
@@ -6195,6 +6438,74 @@
], ],
"time": "2023-05-23T14:45:45+00:00" "time": "2023-05-23T14:45:45+00:00"
}, },
{
"name": "symfony/stimulus-bundle",
"version": "v2.11.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/stimulus-bundle.git",
"reference": "e0e19de8df4d5b2bed57328ae69ef7904df660c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/e0e19de8df4d5b2bed57328ae69ef7904df660c7",
"reference": "e0e19de8df4d5b2bed57328ae69ef7904df660c7",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/config": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/finder": "^5.4|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
"twig/twig": "^2.15.3|^3.4.3"
},
"require-dev": {
"symfony/asset-mapper": "^6.3",
"symfony/framework-bundle": "^5.4|^6.0",
"symfony/phpunit-bridge": "^5.4|^6.0",
"symfony/twig-bundle": "^5.4|^6.0",
"zenstruck/browser": "^1.4"
},
"type": "symfony-bundle",
"autoload": {
"psr-4": {
"Symfony\\UX\\StimulusBundle\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Integration with your Symfony app & Stimulus!",
"keywords": [
"symfony-ux"
],
"support": {
"source": "https://github.com/symfony/stimulus-bundle/tree/v2.11.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-08-28T18:00:50+00:00"
},
{ {
"name": "symfony/stopwatch", "name": "symfony/stopwatch",
"version": "v6.3.0", "version": "v6.3.0",
@@ -9734,5 +10045,5 @@
"ext-simplexml": "*" "ext-simplexml": "*"
}, },
"platform-dev": [], "platform-dev": [],
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }
+2
View File
@@ -12,4 +12,6 @@ return [
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true], Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
Knp\Bundle\MenuBundle\KnpMenuBundle::class => ['all' => true],
]; ];
+3
View File
@@ -0,0 +1,3 @@
knp_menu:
twig:
template: _partials/_menu.html.twig
+18 -12
View File
@@ -1,6 +1,7 @@
monolog: monolog:
channels: channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists - deprecation
- bpn
when@dev: when@dev:
monolog: monolog:
@@ -9,19 +10,17 @@ when@dev:
type: stream type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log" path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug level: debug
channels: ["!event"] channels: ["!event", "!bpn"]
# uncomment to get logging in your browser bpn:
# you may have to allow bigger header sizes in your Web server configuration channels: ["bpn"]
#firephp: type: rotating_file
# type: firephp path: '%kernel.logs_dir%/%kernel.environment%.bpn.log'
# level: info max_files: 5
#chromephp: level: info
# type: chromephp
# level: info
console: console:
type: console type: console
process_psr_3_messages: false process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"] channels: ["!event", "!bpn", "!doctrine", "!console"]
when@test: when@test:
monolog: monolog:
@@ -46,15 +45,22 @@ when@prod:
handler: nested handler: nested
excluded_http_codes: [404, 405] excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks buffer_size: 50 # How many messages should be saved? Prevent memory leaks
channels: ["!bpn"]
nested: nested:
type: stream type: stream
path: php://stderr path: php://stderr
level: debug level: debug
formatter: monolog.formatter.json formatter: monolog.formatter.json
bpn:
channels: ["bpn"]
type: rotating_file
path: '%kernel.logs_dir%/%kernel.environment%.bpn.log'
max_files: 5
level: info
console: console:
type: console type: console
process_psr_3_messages: false process_psr_3_messages: false
channels: ["!event", "!doctrine"] channels: ["!event", "!doctrine", "!bpn"]
deprecation: deprecation:
type: stream type: stream
channels: [deprecation] channels: [deprecation]
+14 -1
View File
@@ -29,8 +29,21 @@ services:
App\BusProNet\ApiClient: App\BusProNet\ApiClient:
arguments: arguments:
$logger: '@monolog.logger.bpn'
$options: { 'bpn_username': '%bpn_username%', 'bpn_password': '%bpn_password%', 'bpn_url': '%bpn_url%' } $options: { 'bpn_username': '%bpn_username%', 'bpn_password': '%bpn_password%', 'bpn_url': '%bpn_url%' }
App\BusProNet\ResponseParser: App\BusProNet\ResponseParser:
arguments: arguments:
$options: { 'bpn_crm_id_admin': '%bpn_crm_id_admin%', 'bpn_crm_id_manager': '%bpn_crm_id_manager%', 'bpn_crm_id_teamer': '%bpn_crm_id_teamer%' } $options: { 'bpn_crm_id_admin': '%bpn_crm_id_admin%', 'bpn_crm_id_manager': '%bpn_crm_id_manager%', 'bpn_crm_id_teamer': '%bpn_crm_id_teamer%' }
App\Twig\AppRuntime:
arguments:
$environment: '%kernel.environment%'
App\Menu\MenuBuilder:
arguments:
$factory: '@knp_menu.factory'
tags:
- name: knp_menu.menu_builder
method: createMainMenu
alias: main
+6728 -269
View File
File diff suppressed because it is too large Load Diff
+10 -2
View File
@@ -2,19 +2,27 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.0", "@babel/core": "^7.17.0",
"@babel/preset-env": "^7.16.0", "@babel/preset-env": "^7.16.0",
"@hotwired/stimulus": "^3.0.0",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/webpack-encore": "^4.0.0", "@symfony/webpack-encore": "^4.0.0",
"@tailwindcss/forms": "^0.5.6", "@tailwindcss/forms": "^0.5.6",
"@tailwindcss/typography": "^0.5.10", "@tailwindcss/typography": "^0.5.10",
"@trevoreyre/autocomplete-js": "^2.4.1",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"core-js": "^3.23.0", "core-js": "^3.23.0",
"dropzone": "^6.0.0-beta.2",
"file-loader": "^6.2.0",
"flatpickr": "^4.6.13",
"postcss": "^8.4.29", "postcss": "^8.4.29",
"postcss-import": "^15.1.0", "postcss-import": "^15.1.0",
"postcss-loader": "^7.3.3", "postcss-loader": "^7.3.3",
"pretty-bytes": "^6.1.1",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tippy.js": "^6.3.7",
"toastify-js": "^1.12.0",
"webpack": "^5.74.0", "webpack": "^5.74.0",
"webpack-cli": "^4.10.0", "webpack-cli": "^4.10.0"
"webpack-notifier": "^1.15.0"
}, },
"license": "UNLICENSED", "license": "UNLICENSED",
"private": true, "private": true,
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 124 74" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><g><path d="M107,0.3c-36.4,5.5 -81.6,19.1 -81.6,38.2c0,17.7 41.8,13.2 41.8,13.2c0,0 -67,22.3 -67,-7c0,-33.2 84.5,-44.4 106.8,-44.4Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M17,73.5c36.4,-5.4 81.6,-19.1 81.6,-38.2c0,-17.7 -41.8,-13.2 -41.8,-13.2c0,0 67,-22.3 67,7c0,33.3 -84.5,44.4 -106.8,44.4Z" style="fill:#f8c62f;fill-rule:nonzero;"/><g><path d="M60.7,34.3l-12.8,4l12.2,0.8l-0.4,-2.4l1,-2.4Z" style="fill:#0084d4;fill-rule:nonzero;"/><path d="M75.9,35.2l-9.2,2.9l-3.5,9.7l-1.7,-11.3l4,-10.7l1.7,7.9l8.7,1.5Z" style="fill:#f8c62f;fill-rule:nonzero;"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 995 B

+6
View File
@@ -0,0 +1,6 @@
{
"icons": [
{ "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
]
}
+7
View File
@@ -4,6 +4,7 @@ namespace App\BusProNet;
use App\BusProNet\Model\BaseResponse; use App\BusProNet\Model\BaseResponse;
use App\Entity\User; use App\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CacheInterface;
@@ -19,6 +20,7 @@ class ApiClient
private readonly SerializerInterface $serializer, private readonly SerializerInterface $serializer,
private readonly ResponseParser $responseParser, private readonly ResponseParser $responseParser,
private readonly CacheInterface $cache, private readonly CacheInterface $cache,
private readonly LoggerInterface $logger,
array $options array $options
) { ) {
$this->config = $this->resolveOptions($options); $this->config = $this->resolveOptions($options);
@@ -57,6 +59,7 @@ class ApiClient
} catch (\Throwable $e) { } catch (\Throwable $e) {
} }
$this->logger->error('API error', ['error' => $e->getMessage()]);
throw new ApiClientException($e->getMessage()); throw new ApiClientException($e->getMessage());
} }
@@ -96,6 +99,7 @@ class ApiClient
} catch (\Throwable $e) { } catch (\Throwable $e) {
} }
$this->logger->error('API error', ['error' => $e->getMessage()]);
throw new ApiClientException($e->getMessage()); throw new ApiClientException($e->getMessage());
} }
@@ -128,6 +132,7 @@ class ApiClient
} catch (\Throwable $e) { } catch (\Throwable $e) {
} }
$this->logger->error('API error', ['error' => $e->getMessage()]);
throw new ApiClientException($e->getMessage()); throw new ApiClientException($e->getMessage());
} }
@@ -164,6 +169,7 @@ class ApiClient
} catch (\Throwable $e) { } catch (\Throwable $e) {
} }
$this->logger->error('API error', ['error' => $e->getMessage()]);
throw new ApiClientException($e->getMessage()); throw new ApiClientException($e->getMessage());
} }
@@ -197,6 +203,7 @@ class ApiClient
} catch (\Throwable $e) { } catch (\Throwable $e) {
} }
$this->logger->error('API error', ['error' => $e->getMessage()]);
throw new ApiClientException($e->getMessage()); throw new ApiClientException($e->getMessage());
}); });
} }
+4 -2
View File
@@ -6,7 +6,7 @@ use App\BusProNet\ApiClient;
use App\BusProNet\ApiClientException; use App\BusProNet\ApiClientException;
use App\Entity\Teamer; use App\Entity\Teamer;
use App\Entity\User; use App\Entity\User;
use App\Form\ProfileType; use App\Form\TeamerProfileType;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@@ -36,7 +36,7 @@ class ProfileController extends AbstractController
$this->entityManager->persist($teamer); $this->entityManager->persist($teamer);
} }
$form = $this->createForm(ProfileType::class, $teamer); $form = $this->createForm(TeamerProfileType::class, $teamer);
$form->handleRequest($request); $form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) { if ($form->isSubmitted() && $form->isValid()) {
@@ -47,6 +47,8 @@ class ProfileController extends AbstractController
} catch (ApiClientException $e) { } catch (ApiClientException $e) {
} }
$this->addFlash('success', 'Deine Daten wurden aktualisiert');
return $this->redirectToRoute('app_teamer_profile'); return $this->redirectToRoute('app_teamer_profile');
} }
+3 -3
View File
@@ -11,15 +11,15 @@ use Symfony\Component\Validator\Constraints as Assert;
class Address class Address
{ {
#[ORM\Column(type: 'string', nullable: true)] #[ORM\Column(type: 'string', nullable: true)]
#[Assert\NotBlank(message: 'required_input')] #[Assert\NotBlank(message: 'Bitte angeben')]
protected ?string $street = null; protected ?string $street = null;
#[ORM\Column(type: 'string', nullable: true)] #[ORM\Column(type: 'string', nullable: true)]
#[Assert\NotBlank(message: 'required_input')] #[Assert\NotBlank(message: 'Bitte angeben')]
protected ?string $postCode = null; protected ?string $postCode = null;
#[ORM\Column(type: 'string', nullable: true)] #[ORM\Column(type: 'string', nullable: true)]
#[Assert\NotBlank(message: 'required_input')] #[Assert\NotBlank(message: 'Bitte angeben')]
protected ?string $city = null; protected ?string $city = null;
#[ORM\Column(type: 'string', nullable: true)] #[ORM\Column(type: 'string', nullable: true)]
+6
View File
@@ -3,20 +3,26 @@
namespace App\Entity\Embeddable; namespace App\Entity\Embeddable;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Embeddable] #[ORM\Embeddable]
class BankAccount class BankAccount
{ {
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
#[Assert\Iban(message: 'Diese IBAN ist ungültig')]
private ?string $iban = null; private ?string $iban = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $bic = null; private ?string $bic = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $bank = null; private ?string $bank = null;
#[ORM\Column(nullable: true)] #[ORM\Column(nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $holder = null; private ?string $holder = null;
public function getIban(bool $obfuscated = false): ?string public function getIban(bool $obfuscated = false): ?string
+3 -1
View File
@@ -13,10 +13,12 @@ class Communication
protected ?string $phone = null; protected ?string $phone = null;
#[ORM\Column(type: 'string', nullable: true)] #[ORM\Column(type: 'string', nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
protected ?string $mobile = null; protected ?string $mobile = null;
#[ORM\Column(type: 'string', nullable: true)] #[ORM\Column(type: 'string', nullable: true)]
#[Assert\Email(mode: 'strict')] #[Assert\Email(mode: 'strict', message: 'Diese E-Mail-Adresse ist ungültig')]
#[Assert\NotBlank(message: 'Bitte angeben')]
protected ?string $email = null; protected ?string $email = null;
public function toPayload(): array public function toPayload(): array
+15 -7
View File
@@ -32,15 +32,19 @@ class Teamer implements TimestampableEntityInterface
private string $uuid; private string $uuid;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $firstName = null; private ?string $firstName = null;
#[ORM\Column(length: 255)] #[ORM\Column(length: 255)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $lastName = null; private ?string $lastName = null;
#[ORM\Column(length: 1)] #[ORM\Column(length: 1)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $gender = null; private ?string $gender = null;
#[ORM\Column(type: Types::DATE_IMMUTABLE)] #[ORM\Column(type: Types::DATE_IMMUTABLE)]
#[Assert\NotNull(message: 'Bitte angeben')]
private ?\DateTimeImmutable $dateOfBirth = null; private ?\DateTimeImmutable $dateOfBirth = null;
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 255, nullable: true)]
@@ -50,6 +54,7 @@ class Teamer implements TimestampableEntityInterface
private ?string $salutation = null; private ?string $salutation = null;
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 255, nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $nationality = null; private ?string $nationality = null;
#[ORM\Embedded(class: Address::class)] #[ORM\Embedded(class: Address::class)]
@@ -65,13 +70,15 @@ class Teamer implements TimestampableEntityInterface
private ?BankAccount $bankAccount = null; private ?BankAccount $bankAccount = null;
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 255, nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $taxId = null; private ?string $taxId = null;
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 255, nullable: true)]
#[Assert\NotBlank(message: 'Bitte angeben')]
private ?string $healthInsuranceCompany = null; private ?string $healthInsuranceCompany = null;
#[ORM\Column(length: 32)] #[ORM\Column(length: 32)]
private ?string $status = null; private ?string $status;
#[ORM\Column(type: Types::TEXT, nullable: true)] #[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $remarks = null; private ?string $remarks = null;
@@ -121,7 +128,7 @@ class Teamer implements TimestampableEntityInterface
{ {
// Transform gender value // Transform gender value
$gender = strtoupper($this->getGender()); $gender = strtoupper($this->getGender());
$gender = 'F' ? 'W' : $gender; $gender = 'F' === $gender ? 'W' : $gender;
// Ensure date of birth is populated // Ensure date of birth is populated
if (null === $dob = $this->getDateOfBirth()) { if (null === $dob = $this->getDateOfBirth()) {
@@ -130,6 +137,7 @@ class Teamer implements TimestampableEntityInterface
return [ return [
'geburtsdatum' => $dob->format('d.m.Y'), 'geburtsdatum' => $dob->format('d.m.Y'),
'anrede' => $this->getSalutation(),
'geschlecht' => $gender, 'geschlecht' => $gender,
'titel' => $this->getAcademicTitle(), 'titel' => $this->getAcademicTitle(),
'vorname' => $this->getFirstName(), 'vorname' => $this->getFirstName(),
@@ -178,7 +186,7 @@ class Teamer implements TimestampableEntityInterface
return $this->firstName; return $this->firstName;
} }
public function setFirstName(string $firstName): static public function setFirstName(?string $firstName): static
{ {
$this->firstName = $firstName; $this->firstName = $firstName;
@@ -190,7 +198,7 @@ class Teamer implements TimestampableEntityInterface
return $this->lastName; return $this->lastName;
} }
public function setLastName(string $lastName): static public function setLastName(?string $lastName): static
{ {
$this->lastName = $lastName; $this->lastName = $lastName;
@@ -202,7 +210,7 @@ class Teamer implements TimestampableEntityInterface
return $this->gender; return $this->gender;
} }
public function setGender(string $gender): static public function setGender(?string $gender): static
{ {
$this->gender = $gender; $this->gender = $gender;
@@ -214,7 +222,7 @@ class Teamer implements TimestampableEntityInterface
return $this->dateOfBirth; return $this->dateOfBirth;
} }
public function setDateOfBirth(\DateTimeImmutable $dateOfBirth): static public function setDateOfBirth(?\DateTimeImmutable $dateOfBirth): static
{ {
$this->dateOfBirth = $dateOfBirth; $this->dateOfBirth = $dateOfBirth;
@@ -250,7 +258,7 @@ class Teamer implements TimestampableEntityInterface
return $this->nationality; return $this->nationality;
} }
public function setNationality(string $nationality): static public function setNationality(?string $nationality): static
{ {
$this->nationality = $nationality; $this->nationality = $nationality;
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Form;
use App\Entity\Embeddable\Address;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AddressType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('street', TextType::class, [
'label' => 'Straße, Hausnummer',
])
->add('postCode', TextType::class, [
'label' => 'PLZ',
])
->add('city', TextType::class, [
'label' => 'Ort',
])
->add('country', BpnCountryType::class, [
'label' => 'Land',
'property' => 'country',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Address::class,
]);
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace App\Form;
use App\Entity\Embeddable\BankAccount;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class BankAccountType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('iban', TextType::class, [
'label' => 'IBAN',
])
->add('bic', TextType::class, [
'label' => 'BIC',
])
->add('bank', TextType::class, [
'label' => 'Name der Bank',
])
->add('holder', TextType::class, [
'label' => 'Kontoinhaber',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => BankAccount::class,
]);
}
}
@@ -28,7 +28,7 @@ class BpnCountryChoiceLoader implements ChoiceLoaderInterface
foreach ($countries as $country) { foreach ($countries as $country) {
$key = 'nationality' === $this->property ? $country->getNationality() : $country->getName(); $key = 'nationality' === $this->property ? $country->getNationality() : $country->getName();
$choices[$key] = $country->getId(); $choices[$key] = $country->getToken();
} }
return new ArrayChoiceList($choices); return new ArrayChoiceList($choices);
+35
View File
@@ -0,0 +1,35 @@
<?php
namespace App\Form;
use App\Entity\Embeddable\Communication;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CommunicationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('email', EmailType::class, [
'label' => 'E-Mail',
])
->add('mobile', TextType::class, [
'label' => 'Telefon (mobil)',
])
->add('phone', TextType::class, [
'label' => 'Telefon (privat)',
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Communication::class,
]);
}
}
@@ -0,0 +1,32 @@
<?php
namespace App\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class AjaxSubmitExtension extends AbstractTypeExtension
{
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'ajax_submit' => false,
'ajax_action' => 'ajax-modal#submitForm',
]);
}
public function buildView(FormView $view, FormInterface $form, array $options): void
{
if (true === $options['ajax_submit']) {
$view->vars['attr'] = array_merge($view->vars['attr'], ['data-action' => $options['ajax_action']]);
}
}
public static function getExtendedTypes(): iterable
{
return [FormType::class];
}
}
@@ -0,0 +1,29 @@
<?php
namespace App\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
class NoValidateExtension extends AbstractTypeExtension
{
public function buildView(FormView $view, FormInterface $form, array $options): void
{
if (false === $options['clientside_validation'] && $form->isRoot()) {
$view->vars['attr'] = array_merge($view->vars['attr'], ['novalidate' => 'novalidate']);
}
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefault('clientside_validation', false);
}
public static function getExtendedTypes(): iterable
{
return [FormType::class];
}
}
@@ -6,11 +6,12 @@ use App\Entity\Teamer;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\BirthdayType; use Symfony\Component\Form\Extension\Core\Type\BirthdayType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType; 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;
class ProfileType extends AbstractType class TeamerProfileType extends AbstractType
{ {
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
@@ -23,6 +24,14 @@ class ProfileType extends AbstractType
'divers' => 'D', 'divers' => 'D',
], ],
]) ])
->add('salutation', ChoiceType::class, [
'label' => 'Anrede',
'choices' => [
'Frau' => 'Frau',
'Herr' => 'Herr',
'divers' => 'divers',
],
])
->add('firstName', TextType::class, [ ->add('firstName', TextType::class, [
'label' => 'Vorname', 'label' => 'Vorname',
]) ])
@@ -41,6 +50,30 @@ class ProfileType extends AbstractType
'label' => 'Nationalität', 'label' => 'Nationalität',
'property' => 'nationality', 'property' => 'nationality',
]) ])
->add('taxId', TextType::class, [
'label' => 'Steuer-ID',
'required' => false,
])
->add('healthInsuranceCompany', TextType::class, [
'label' => 'Krankenversicherung',
'required' => false,
])
->add('remarks', TextareaType::class, [
'label' => 'Wünsche/Anmerkungen',
'required' => false,
'attr' => [
'rows' => 3,
],
])
->add('address', AddressType::class, [
'label' => 'false',
])
->add('communication', CommunicationType::class, [
'label' => false,
])
->add('bankAccount', BankAccountType::class, [
'label' => false,
])
; ;
} }
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Menu;
use App\Entity\User;
use Knp\Menu\FactoryInterface;
use Knp\Menu\ItemInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
use Symfony\Contracts\Translation\TranslatorInterface;
class MenuBuilder
{
public function __construct(
protected FactoryInterface $factory,
protected Security $security,
protected RequestStack $requestStack,
protected TranslatorInterface $translator
) {
}
private function assertMobile(array $options): bool
{
return isset($options['mobile']) && true === (bool) $options['mobile'];
}
private function createRootElement(bool $isMobile): ItemInterface
{
return $this
->factory->createItem('root')
->setChildrenAttribute('class', 'menu'.($isMobile ? ' menu--mobile' : ''))
;
}
private function getDefaultRouteParameters(string $parameter = 'id', string $default = '0'): array
{
return [$parameter => $this->requestStack->getMainRequest()->get($parameter, $default)];
}
public function createMainMenu(array $options): ItemInterface
{
/** @var User $user */
$user = $this->security->getUser();
$userLabel = $user->getUserIdentifier();
$isMobile = $this->assertMobile($options);
$menu = $this->createRootElement($isMobile);
if ($this->security->isGranted('ROLE_TEAMER')) {
$menu
->addChild('Profil', ['route' => 'app_teamer_profile'])
->setLinkAttribute('title', 'Benutzer: '.$userLabel)
->setExtra('icon', 'user')
->setExtra('icon_only', false === $isMobile)
;
}
$token = $this->security->getToken();
if ($token instanceof SwitchUserToken) {
/** @var User $originalUser */
$originalUser = $token->getOriginalToken()->getUser();
$menu
->addChild('Logout', [
'route' => $originalUser->getDefaultRoute(),
'routeParameters' => ['_switch_user' => '_exit'],
])
->setLinkAttributes([
'title' => 'zurück',
'class' => 'icon-link',
])
->setExtra('icon', 'logout')
->setExtra('icon_only', false === $isMobile)
;
} else {
$menu
->addChild('Logout', ['route' => 'app_security_logout'])
->setLinkAttributes([
'title' => 'Logout',
'class' => 'icon-link',
])
->setExtra('icon', 'logout')
->setExtra('icon_only', false === $isMobile)
;
}
return $menu;
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
namespace App\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
class AppExtension extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('file_size', [AppRuntime::class, 'formatBytes']),
new TwigFilter('file_icon', [AppRuntime::class, 'fileIconFilter'], ['is_safe' => ['html']]),
new TwigFilter('date_diff', [AppRuntime::class, 'dateDiffForHumans']),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('icon', [AppRuntime::class, 'renderIcon'], ['needs_environment' => true, 'is_safe' => ['html']]),
new TwigFunction('is_current_route', [AppRuntime::class, 'isCurrentRoute']),
new TwigFunction('qa_attribute', [AppRuntime::class, 'renderQaAttribute'], ['is_safe' => ['html']]),
];
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace App\Twig;
use Carbon\Carbon;
use Symfony\Component\HttpFoundation\RequestStack;
use Twig\Environment;
use Twig\Extension\RuntimeExtensionInterface;
class AppRuntime implements RuntimeExtensionInterface
{
public function __construct(
private readonly RequestStack $requestStack,
private readonly string $environment
) {
}
public function fileIconFilter(Environment $environment, string $mimeType, ?string $classes = 'w-4 h-4'): string
{
$icon = match ($mimeType) {
'application/pdf' => 'pdf',
'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'excel',
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'word',
default => 'download',
};
return $this->renderIcon($environment, $icon, $classes);
}
public function formatBytes(int $bytes, ?int $precision = 2): string
{
$size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$precision}f", $bytes / (1024 ** $factor)).@$size[$factor];
}
public function renderIcon(Environment $environment, string $icon, string $classes = 'w-6 h-6'): string
{
return $environment->render('_partials/_icon.html.twig', [
'icon' => $icon,
'class' => $classes,
]);
}
public function isCurrentRoute(string $route): bool
{
$requestedRoute = $this->requestStack->getMainRequest()->attributes->get('_route');
return $requestedRoute === $route;
}
public function renderQaAttribute(string $label, string $value = null): string
{
if ('test' !== $this->environment) {
return '';
}
if (null === $value) {
return sprintf(' data-qa-%s', strtolower($label));
}
return sprintf(' data-qa-%s="%s"', strtolower($label), $value);
}
public function dateDiffForHumans(\DateTimeInterface $dateTime, \DateTimeInterface $other = null): string
{
$oldLocale = Carbon::getLocale();
$locale = $this->requestStack->getMainRequest()->getLocale();
Carbon::setLocale($locale);
$result = Carbon::instance($dateTime)->diffForHumans($other);
Carbon::setLocale($oldLocale);
return $result;
}
}
+17
View File
@@ -26,6 +26,9 @@
"migrations/.gitignore" "migrations/.gitignore"
] ]
}, },
"knplabs/knp-menu-bundle": {
"version": "v3.2.0"
},
"phpunit/phpunit": { "phpunit/phpunit": {
"version": "9.6", "version": "9.6",
"recipe": { "recipe": {
@@ -204,6 +207,20 @@
"config/packages/security.yaml" "config/packages/security.yaml"
] ]
}, },
"symfony/stimulus-bundle": {
"version": "2.11",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.9",
"ref": "05c45071c7ecacc1e48f94bc43c1f8d4405fb2b2"
},
"files": [
"assets/bootstrap.js",
"assets/controllers.json",
"assets/controllers/hello_controller.js"
]
},
"symfony/translation": { "symfony/translation": {
"version": "6.3", "version": "6.3",
"recipe": { "recipe": {
+22
View File
@@ -0,0 +1,22 @@
<div id="ajax-modal"
class="fixed inset-0 w-full h-full z-50 hidden"
{{ stimulus_controller('ajax-modal') }}
>
<div class="absolute inset-0 w-full h-full bg-black/80" {{ stimulus_action('ajax-modal', 'hide', 'click') }}></div>
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 px-4 shadow w-full max-w-xl">
<div class="bg-white p-8">
<div class="flex justify-between pb-4">
<div class="text-lg font-medium" {{ stimulus_target('ajax-modal', 'title') }}></div>
<button type="button"
class="inline-block"
{{ stimulus_action('ajax-modal', 'hide') }}>
{{ icon('close', 'w-6 h-6')}}
</button>
</div>
<div class="hidden" {{ stimulus_target('ajax-modal', 'content') }}></div>
<div {{ stimulus_target('ajax-modal', 'spinner') }}>
{% include '_partials/_spinner.html.twig' with { 'class': 'w-8 h-8 mx-auto'} %}
</div>
</div>
</div>
</div>
@@ -0,0 +1,28 @@
<div id="confirmation-modal"
class="fixed top-0 left-0 z-50 inset-0 hidden"
{{ stimulus_controller('confirmation-modal') }}
>
<div class="absolute top-0 left-0 inset-0 bg-black/80" {{ stimulus_action('confirmation-modal', 'hide', 'click') }}></div>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-8 shadow w-full max-w-3xl rounded">
<div class="flex justify-between border-b border-gray-200 pb-2 mb-4">
<div class="flex items-center space-x-2">
{{ icon('alert', 'w-8 h-8') }}
<span class="text-lg font-medium" {{ stimulus_target('confirmation-modal', 'title') }}></span>
</div>
<button class="inline-block" {{ stimulus_action('confirmation-modal', 'hide') }}>
{{ icon('close', 'w-8 h-8') }}
</button>
</div>
<form method="post" {{ stimulus_target('confirmation-modal', 'form') }}>
<div {{ stimulus_target('confirmation-modal', 'content') }}></div>
<div class="mt-6 flex justify-between">
<button type="submit" class="btn btn--warning">
Ja
</button>
<button type="button" class="btn btn--secondary" {{ stimulus_action('confirmation-modal', 'hide') }}>
Abbrechen
</button>
</div>
</form>
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
{% if app.session.flashBag.peek('error')|length > 0 %}
{% include '_partials/_toast.html.twig' with { level: 'warning', messages: app.flashes('error') } %}
{% endif %}
{% if app.session.flashBag.peek('success')|length > 0 %}
{% include '_partials/_toast.html.twig' with { level: 'success', messages: app.flashes('success') } %}
{% endif %}
{% if app.session.flashBag.peek('info')|length > 0 %}
{% include '_partials/_toast.html.twig' with { level: 'info', messages: app.flashes('info') } %}
{% endif %}
+1
View File
@@ -0,0 +1 @@
<svg class="{{ class }}"><use href="{{ asset('build/images/icons.svg') }}#icon-{{ icon }}"/></svg>

After

Width:  |  Height:  |  Size: 99 B

+14
View File
@@ -0,0 +1,14 @@
{% extends 'knp_menu.html.twig' %}
{% block label %}
{% apply spaceless %}
{% if item.extras.icon is defined %}
{{ icon(item.extras.icon, 'w-5 h-5') }}
{% if item.extras.icon_only is defined and false == item.extras.icon_only %}
<span class="flex-1">{{ item.label|raw }}</span>
{% endif %}
{% else %}
{{ item.label|raw }}
{% endif %}
{% endapply %}
{% endblock %}
@@ -0,0 +1,18 @@
<div id="mobilenav"
class="fixed z-50 top-0 left-0 inset-0 pr-16 bg-black/70 -translate-x-full"
{{ stimulus_controller('mobilenav', [], { 'closed': '-translate-x-full' }, { 'mobilenav': '#mobilenav' }) }}
>
<div class="h-screen w-full bg-white">
<div class="flex justify-between px-8 pt-4">
<img src="{{ asset('build/images/logo.svg') }}" alt="" class="block w-auto h-8">
<button type="button"
{{ stimulus_action('mobilenav', 'trigger', null, { 'mode': 'close' }) }}>
{{ icon('close', 'w-8 h-8') }}
</button>
</div>
<div class="w-full h-full p-8 pt-4 overflow-y-scroll">
{% set mobileMenu = knp_menu_get('main', [], { 'mobile': true }) %}
{{ knp_menu_render(mobileMenu, { 'firstClass': null, 'lastClass': null, 'ancestorClass': 'active', 'compressed': true }) }}
</div>
</div>
</div>
@@ -0,0 +1,19 @@
<div class="fixed top-0 left-0 inset-x-0 z-50 bg-white shadow-sm">
<div class="container px-8 py-4 flex items-center justify-between">
<a href="{{ path(app.user.defaultRoute) }}" title="Home">
<div class="flex items-center space-x-4">
<img src="{{ asset('build/images/logo.svg') }}" alt="" class="block w-auto h-8">
</div>
</a>
<nav class="hidden md:block font-medium">
{% set menu = knp_menu_get('main') %}
{{ knp_menu_render(menu, { 'firstClass': null, 'lastClass': null, 'ancestorClass': 'active', 'compressed': true }) }}
</nav>
<button type="button"
class="md:hidden"
{{ stimulus_controller('mobilenav', [], [], { 'mobilenav': '#mobilenav' }) }}
{{ stimulus_action('mobilenav', 'trigger', null, { 'mode': 'open' }) }}>
{{ icon('menu', 'w-8 h-8') }}
</button>
</div>
</div>
+37
View File
@@ -0,0 +1,37 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg class="{{ class }}" viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" stroke="currentColor">
<g fill="none" fill-rule="evenodd" stroke-width="2">
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="0s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="0s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="1">
<animate attributeName="r"
begin="-0.9s" dur="1.8s"
values="1; 20"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.165, 0.84, 0.44, 1"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="-0.9s" dur="1.8s"
values="1; 0"
calcMode="spline"
keyTimes="0; 1"
keySplines="0.3, 0.61, 0.355, 1"
repeatCount="indefinite" />
</circle>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

+10
View File
@@ -0,0 +1,10 @@
{% apply spaceless %}
{% for message in messages %}
{% if message is iterable %}
{% set message = message.id | trans(message.parameters | default({}), message.domain | default(null), message.locale | default(null)) | raw %}
{% else %}
{% set message = message | trans | raw %}
{% endif %}
<div {{ stimulus_controller('toast', { 'text': message, 'class': 'toastify--' ~ level}) }}{{ qa_attribute('toast', level)}}></div>
{% endfor %}
{% endapply %}
+2 -2
View File
@@ -1,4 +1,4 @@
{% extends 'base.html.twig' %} {% extends 'layout.html.twig' %}
{% block body %} {% block content %}
{% endblock %} {% endblock %}
+6 -2
View File
@@ -2,8 +2,11 @@
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}Welcome!{% endblock %}</title> <title>{% block title %}MyE&amp;P Team{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>"> <link rel="icon" href="{{ asset('favicon.ico') }}" sizes="any">
<link rel="icon" href="{{ asset('icon.svg') }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ asset('apple-touch-icon.png') }}">
<link rel="manifest" href="{{ asset('site.webmanifest') }}">
{% block stylesheets %} {% block stylesheets %}
{{ encore_entry_link_tags('app') }} {{ encore_entry_link_tags('app') }}
{% endblock %} {% endblock %}
@@ -13,5 +16,6 @@
</head> </head>
<body class="bg-white font-sans antialiased"> <body class="bg-white font-sans antialiased">
{% block body %}{% endblock %} {% block body %}{% endblock %}
{% include '_partials/_flashes.html.twig' %}
</body> </body>
</html> </html>
+48 -5
View File
@@ -1,11 +1,36 @@
{% use 'form_div_layout.html.twig' %} {% use 'form_div_layout.html.twig' %}
{%- block form_row -%}
{%- set widget_attr = {} -%}
{%- if help is not empty -%}
{%- set widget_attr = {attr: {'aria-describedby': id ~"_help"}} -%}
{%- endif -%}
<div{% with {attr: row_attr} %}{{ block('attributes') }}{% endwith %}>
{{- form_label(form) -}}
{{- form_errors(form) -}}
{{- form_widget(form, widget_attr) -}}
{{- form_help(form) -}}
</div>
{%- endblock form_row -%}
{%- block form_errors -%}
{%- if errors|length > 0 -%}
<ul>
{%- for error in errors -%}
<li class="text-red-500">{{ error.message }}</li>
{%- endfor -%}
</ul>
{%- endif -%}
{%- endblock form_errors -%}
{%- block form_widget_simple -%} {%- block form_widget_simple -%}
{%- set type = type|default('text') -%} {%- set type = type|default('text') -%}
{%- if type != 'hidden' -%} {%- if type != 'hidden' -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' mt-2 appearance-none text-slate-900 bg-white rounded-md block w-full px-3 h-10 shadow-sm sm:text-sm focus:outline-none ring-0 placeholder:text-slate-400 focus:ring-1 focus:ring-primary')|trim }) -%} {%- set attr = attr|merge({'class': (attr.class|default('') ~ ' mt-2 appearance-none text-slate-900 bg-white rounded-md block w-full px-3 h-10 shadow-sm sm:text-sm focus:outline-none ring-0 focus:ring-0')|trim }) -%}
{%- if errors|length -%} {%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-red-500 placeholder-red-500 focus:border-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-slate-400 focus:border-primary' }) -%}
{%- endif -%} {%- endif -%}
{%- if disabled is defined and disabled == true -%} {%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
@@ -17,10 +42,28 @@
{%- endif -%} {%- endif -%}
{%- endblock form_widget_simple -%} {%- endblock form_widget_simple -%}
{%- block choice_widget_collapsed -%} {%- block textarea_widget -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' mt-2 appearance-none text-slate-900 bg-white rounded-md block w-full px-3 h-10 shadow-sm sm:text-sm focus:outline-none ring-0 placeholder:text-slate-400 focus:ring-1 focus:ring-primary' }) -%} {%- set attr = attr|merge({'class': (attr.class|default('') ~ ' mt-2 appearance-none text-slate-900 bg-white rounded-md block w-full px-3 shadow-sm sm:text-sm focus:outline-none ring-0 focus:ring-0')|trim }) -%}
{%- if errors|length -%} {%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' ring-red-500 placeholder-red-500 focus:ring-red-500' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-red-500 placeholder-red-500 focus:border-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-slate-400 focus:ring-1 focus:ring-primary' }) -%}
{%- endif -%}
{%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
{%- endif -%}
{{ parent() }}
{%- if disabled is defined and disabled == true -%}
<input type="hidden" name="{{ form.vars.full_name }}" value="{{ form.vars.value }}">
{%- endif -%}
{%- endblock textarea_widget -%}
{%- block choice_widget_collapsed -%}
{%- set attr = attr|merge({'class': (attr.class|default('') ~ ' mt-2 appearance-none text-slate-900 bg-white rounded-md block w-full px-3 shadow-sm sm:text-sm focus:outline-none ring-0 focus:ring-0')|trim }) -%}
{%- if errors|length -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' border-red-500 placeholder-red-500 focus:border-red-500' }) -%}
{% else %}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' placeholder:text-slate-400 focus:ring-1 focus:ring-primary' }) -%}
{%- endif -%} {%- endif -%}
{%- if disabled is defined and disabled == true -%} {%- if disabled is defined and disabled == true -%}
{%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%} {%- set attr = attr|merge({'class': attr.class|default('') ~ ' cursor-not-allowed' }) -%}
+11
View File
@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block body %}
{% block page_header %}{% include '_partials/_page_header.html.twig' %}{% endblock %}
<div class="container py-24">
{% block content %}{% endblock %}
</div>
{% include '_partials/_ajax_modal.html.twig' %}
{% include '_partials/_confirmation_modal.html.twig' %}
{% include '_partials/_mobile_menu.html.twig' %}
{% endblock %}
+2 -2
View File
@@ -1,4 +1,4 @@
{% extends 'base.html.twig' %} {% extends 'layout.html.twig' %}
{% block body %} {% block content %}
{% endblock %} {% endblock %}
+4 -2
View File
@@ -1,4 +1,6 @@
{% extends 'base.html.twig' %} {% extends 'layout.html.twig' %}
{% block body %} {% block title %}Mein Dashboard{% endblock %}
{% block content %}
{% endblock %} {% endblock %}
+107 -16
View File
@@ -1,24 +1,115 @@
{% extends 'base.html.twig' %} {% extends 'layout.html.twig' %}
{% block body %} {% block title %}Mein Profil{% endblock %}
{% macro formErrors(form) %}
<ul>
{% for child in form.children %}
{% for error in child.vars.errors %}
<li>
{{ child.vars.label }}: {{error.message}}
</li>
{% endfor %}
{%endfor%}
</ul>
{% endmacro %}
{% block content %}
{{ form_start(form) }} {{ form_start(form) }}
<div class="container">
<div class="grid grid-cols-2 gap-8"> <div {{ stimulus_controller('tabs', {}, { 'buttonActive': 'btn--secondary' }) }}>
<div class="flex flex-col space-y-4">
{{ form_row(form.academicTitle) }} <div class="grid lg:grid-cols-3 gap-y-8 lg:gap-y-0 lg:gap-x-16">
{{ form_row(form.firstName) }} <div class="flex flex-col space-y-2">
{{ form_row(form.lastName) }} <button type="button" class="btn" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 0 }) }}>
{{ form_row(form.gender) }} Persönliche Daten
{{ form_row(form.dateOfBirth) }} </button>
{{ form_row(form.nationality) }} <button type="button" class="btn" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 1 }) }}>
</div> Anschrift/Kontakt
<div></div> </button>
<div> <button type="button" class="btn" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 2 }) }}>
<button type="submit" class="btn"> Bankverbindung
Aktualisieren </button>
<button type="button" class="btn" {{ stimulus_target('tabs', 'button') }} {{ stimulus_action('tabs', 'select', null, { 'tab': 3 }) }}>
Sonstiges
</button> </button>
</div> </div>
<div class="lg:col-span-2">
{% if not form.vars.valid %}
<div class="max-w-screen-md mx-auto border border-red-500 rounded-md p-4 text-red-500 mb-8">
{{ _self.formErrors(form) }}
{{ _self.formErrors(form.address) }}
{{ _self.formErrors(form.communication) }}
{{ _self.formErrors(form.bankAccount) }}
</div>
{%endif%}
<div class="pb-8 max-w-screen-md mx-auto" {{ stimulus_target('tabs', 'tab') }}>
<h3 class="text-xl font-bold pb-2">
Persönliche Daten
</h3>
<div class="flex flex-col space-y-4">
{{ form_row(form.salutation) }}
{{ form_row(form.academicTitle) }}
{{ form_row(form.firstName) }}
{{ form_row(form.lastName) }}
{{ form_row(form.gender) }}
{{ form_row(form.dateOfBirth) }}
{{ form_row(form.nationality) }}
</div>
</div>
<div class="pb-8 max-w-screen-md mx-auto" {{ stimulus_target('tabs', 'tab') }}>
<h3 class="text-xl font-bold pb-2">
Anschrift
</h3>
<div class="flex flex-col space-y-4 pb-8">
{{ form_row(form.address.street) }}
{{ form_row(form.address.postCode) }}
{{ form_row(form.address.city) }}
{{ form_row(form.address.country) }}
</div>
<h3 class="text-xl font-bold pb-2">
Kontakt
</h3>
<div class="flex flex-col space-y-4">
{{ form_row(form.communication.email) }}
{{ form_row(form.communication.phone) }}
{{ form_row(form.communication.mobile) }}
</div>
</div>
<div class="pb-8 max-w-screen-md mx-auto" {{ stimulus_target('tabs', 'tab') }}>
<h3 class="text-xl font-bold pb-2">
Bankverbindung
</h3>
<div class="flex flex-col space-y-4">
{{ form_row(form.bankAccount.iban) }}
{{ form_row(form.bankAccount.bic) }}
{{ form_row(form.bankAccount.bank) }}
{{ form_row(form.bankAccount.holder) }}
</div>
</div>
<div class="pb-8 max-w-screen-md mx-auto" {{ stimulus_target('tabs', 'tab') }}>
<h3 class="text-xl font-bold pb-2">
Sonstiges
</h3>
<div class="flex flex-col space-y-4">
{{ form_row(form.taxId) }}
{{ form_row(form.healthInsuranceCompany) }}
{{ form_row(form.remarks) }}
</div>
</div>
<div class="max-w-screen-md mx-auto">
<button type="submit" class="btn">
Aktualisieren
</button>
</div>
</div>
</div> </div>
</div> </div>
{{ form_rest(form) }} {{ form_rest(form) }}
{{ form_end(form) }} {{ form_end(form) }}
+7
View File
@@ -25,6 +25,9 @@ Encore
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization. // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
.splitEntryChunks() .splitEntryChunks()
// enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)
.enableStimulusBridge('./assets/controllers.json')
// will require an extra script tag for runtime.js // will require an extra script tag for runtime.js
// but, you probably want this, unless you're building a single-page app // but, you probably want this, unless you're building a single-page app
.enableSingleRuntimeChunk() .enableSingleRuntimeChunk()
@@ -70,6 +73,10 @@ Encore
// uncomment if you're having problems with a jQuery plugin // uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery() //.autoProvidejQuery()
.copyFiles([
{from: './assets/images', to: 'images/[path][name].[hash:8].[ext]'},
])
; ;
module.exports = Encore.getWebpackConfig(); module.exports = Encore.getWebpackConfig();