Finish
This commit is contained in:
parent
4cf1203fad
commit
acae4b4843
@ -64,10 +64,10 @@ server {
|
|||||||
try_files $uri @sfFront;
|
try_files $uri @sfFront;
|
||||||
}
|
}
|
||||||
|
|
||||||
# location /w2p/ {
|
location /w2p/ {
|
||||||
# proxy_pass http://tp:8080/w2p/;
|
proxy_pass http://tp:8080/w2p/;
|
||||||
# proxy_temp_path /tmp/proxy;
|
proxy_temp_path /tmp/proxy;
|
||||||
# }
|
}
|
||||||
|
|
||||||
location @sfFront { # Symfony
|
location @sfFront { # Symfony
|
||||||
if ($request_method = 'OPTIONS') {
|
if ($request_method = 'OPTIONS') {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 81 KiB |
@ -33,12 +33,14 @@ const Order_List_Preview = (uploadTypeObject) => {
|
|||||||
const Order_List_Detail = ({ uuid, basketField1, customerInfo, basketField2, pos, price, product, status, allNet, reOrder, reOrderOrder, reOrderPos, uploadTypeObject }, orderUuid) => `
|
const Order_List_Detail = ({ uuid, basketField1, customerInfo, basketField2, pos, price, product, status, allNet, reOrder, reOrderOrder, reOrderPos, uploadTypeObject }, orderUuid) => `
|
||||||
<div class="px-6 py-4 bg-gray-50" style="${psc.order.get_pos_bg_color(status)}">
|
<div class="px-6 py-4 bg-gray-50" style="${psc.order.get_pos_bg_color(status)}">
|
||||||
<div class="flex gap-4 items-start" id="row-${uuid}">
|
<div class="flex gap-4 items-start" id="row-${uuid}">
|
||||||
${Order_List_Preview(uploadTypeObject)}
|
|
||||||
<div class="grid grid-cols-12 gap-4 flex-1">
|
<div class="grid grid-cols-12 gap-4 flex-1">
|
||||||
<div class="col-span-1">
|
<div class="col-span-1">
|
||||||
<span class="text-xs font-semibold text-gray-700">Pos:</span>
|
<span class="text-xs font-semibold text-gray-700">Pos:</span>
|
||||||
<div class="font-medium">${pos}</div>
|
<div class="font-medium">${pos}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-span-1">
|
||||||
|
${Order_List_Preview(uploadTypeObject)}
|
||||||
|
</div>
|
||||||
<div class="col-span-4">
|
<div class="col-span-4">
|
||||||
<span class="text-xs font-semibold text-gray-700">Produkt:</span>
|
<span class="text-xs font-semibold text-gray-700">Produkt:</span>
|
||||||
<div>
|
<div>
|
||||||
@ -48,7 +50,7 @@ const Order_List_Detail = ({ uuid, basketField1, customerInfo, basketField2, pos
|
|||||||
${psc.order.get_special_product_options(product.specialProductTypeObject)}
|
${psc.order.get_special_product_options(product.specialProductTypeObject)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2">
|
<div class="col-span-1">
|
||||||
<span class="text-xs font-semibold text-gray-700">Auflage:</span>
|
<span class="text-xs font-semibold text-gray-700">Auflage:</span>
|
||||||
<div>${price.count}</div>
|
<div>${price.count}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -56,14 +58,14 @@ const Order_List_Detail = ({ uuid, basketField1, customerInfo, basketField2, pos
|
|||||||
<span class="text-xs font-semibold text-gray-700">Kunden Info:</span>
|
<span class="text-xs font-semibold text-gray-700">Kunden Info:</span>
|
||||||
<div class="text-sm">${customerInfo}${(reOrder ? `<br/><strong class="text-yellow-600">Ist eine Nachbestellung</strong>` : ``)}</div>
|
<div class="text-sm">${customerInfo}${(reOrder ? `<br/><strong class="text-yellow-600">Ist eine Nachbestellung</strong>` : ``)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2">
|
<div class="col-span-1">
|
||||||
<span class="text-xs font-semibold text-gray-700">Preis:</span>
|
<span class="text-xs font-semibold text-gray-700">Preis:</span>
|
||||||
<div class="text-right whitespace-nowrap">
|
<div class="text-right whitespace-nowrap">
|
||||||
${new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price.allNet / 100)}<br/>
|
${new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price.allNet / 100)}<br/>
|
||||||
<strong>(${new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price.allGross / 100)})</strong>
|
<strong>(${new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price.allGross / 100)})</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-1">
|
<div class="col-span-2">
|
||||||
<span class="text-xs font-semibold text-gray-700">Status:</span>
|
<span class="text-xs font-semibold text-gray-700">Status:</span>
|
||||||
<div class="relative inline-block text-left w-full">
|
<div class="relative inline-block text-left w-full">
|
||||||
<button type="button" class="inline-flex justify-between items-center w-full px-3 py-1.5 text-xs font-medium text-white bg-psc-500 hover:bg-psc-600 rounded-sm shadow-sm">
|
<button type="button" class="inline-flex justify-between items-center w-full px-3 py-1.5 text-xs font-medium text-white bg-psc-500 hover:bg-psc-600 rounded-sm shadow-sm">
|
||||||
|
|||||||
@ -476,7 +476,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* datetime?: array{
|
* datetime?: array{
|
||||||
* default_format?: scalar|Param|null, // Default: "Y-m-d\\TH:i:sP"
|
* default_format?: scalar|Param|null, // Default: "Y-m-d\\TH:i:sP"
|
||||||
* default_deserialization_formats?: list<scalar|Param|null>,
|
* default_deserialization_formats?: list<scalar|Param|null>,
|
||||||
* default_timezone?: scalar|Param|null, // Default: "UTC"
|
* default_timezone?: scalar|Param|null, // Default: "Europe/Berlin"
|
||||||
* cdata?: scalar|Param|null, // Default: true
|
* cdata?: scalar|Param|null, // Default: true
|
||||||
* },
|
* },
|
||||||
* array_collection?: array{
|
* array_collection?: array{
|
||||||
@ -576,7 +576,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* datetime?: array{
|
* datetime?: array{
|
||||||
* default_format?: scalar|Param|null, // Default: "Y-m-d\\TH:i:sP"
|
* default_format?: scalar|Param|null, // Default: "Y-m-d\\TH:i:sP"
|
||||||
* default_deserialization_formats?: list<scalar|Param|null>,
|
* default_deserialization_formats?: list<scalar|Param|null>,
|
||||||
* default_timezone?: scalar|Param|null, // Default: "UTC"
|
* default_timezone?: scalar|Param|null, // Default: "Europe/Berlin"
|
||||||
* cdata?: scalar|Param|null, // Default: true
|
* cdata?: scalar|Param|null, // Default: true
|
||||||
* },
|
* },
|
||||||
* array_collection?: array{
|
* array_collection?: array{
|
||||||
@ -2433,7 +2433,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* length?: scalar|Param|null, // Default: 5
|
* length?: scalar|Param|null, // Default: 5
|
||||||
* width?: scalar|Param|null, // Default: 130
|
* width?: scalar|Param|null, // Default: 130
|
||||||
* height?: scalar|Param|null, // Default: 50
|
* height?: scalar|Param|null, // Default: 50
|
||||||
* font?: scalar|Param|null, // Default: "/application/src/new/vendor/gregwar/captcha-bundle/DependencyInjection/../Generator/Font/captcha.ttf"
|
* font?: scalar|Param|null, // Default: "/data/www/new/vendor/gregwar/captcha-bundle/DependencyInjection/../Generator/Font/captcha.ttf"
|
||||||
* keep_value?: scalar|Param|null, // Default: false
|
* keep_value?: scalar|Param|null, // Default: false
|
||||||
* charset?: scalar|Param|null, // Default: "abcdefhjkmnprstuvwxyz23456789"
|
* charset?: scalar|Param|null, // Default: "abcdefhjkmnprstuvwxyz23456789"
|
||||||
* as_file?: scalar|Param|null, // Default: false
|
* as_file?: scalar|Param|null, // Default: false
|
||||||
|
|||||||
@ -16,7 +16,6 @@ namespace PSC\Shop\OrderBundle\Controller\Backend;
|
|||||||
use Doctrine\ORM\EntityManager;
|
use Doctrine\ORM\EntityManager;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
use Spiriit\Bundle\FormFilterBundle\Filter\FilterBuilderUpdaterInterface;
|
|
||||||
use PSC\Shop\EntityBundle\Document\Order;
|
use PSC\Shop\EntityBundle\Document\Order;
|
||||||
use PSC\Shop\EntityBundle\Entity\Motiv;
|
use PSC\Shop\EntityBundle\Entity\Motiv;
|
||||||
use PSC\Shop\EntityBundle\Entity\Shop;
|
use PSC\Shop\EntityBundle\Entity\Shop;
|
||||||
@ -27,16 +26,18 @@ use PSC\System\PluginBundle\Form\Chain\Section;
|
|||||||
use PSC\System\SettingsBundle\Document\LogEntry;
|
use PSC\System\SettingsBundle\Document\LogEntry;
|
||||||
use PSC\System\SettingsBundle\Service\Log;
|
use PSC\System\SettingsBundle\Service\Log;
|
||||||
use PSC\System\SettingsBundle\Service\Status;
|
use PSC\System\SettingsBundle\Service\Status;
|
||||||
|
use Spiriit\Bundle\FormFilterBundle\Filter\FilterBuilderUpdaterInterface;
|
||||||
|
use Symfony\Bridge\Twig\Attribute\Template;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Session\Session;
|
use Symfony\Component\HttpFoundation\Session\Session;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
use Symfony\Component\Security\Core\SecurityContext;
|
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Bridge\Twig\Attribute\Template;
|
use Symfony\Component\Security\Core\SecurityContext;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ListController fürs Backend
|
* ListController fürs Backend
|
||||||
@ -46,9 +47,11 @@ use Symfony\Component\HttpFoundation\Request;
|
|||||||
*/
|
*/
|
||||||
class ListController extends AbstractController
|
class ListController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private Log $logService)
|
public function __construct(
|
||||||
{
|
private Log $logService,
|
||||||
}
|
#[AutowireIterator('backend.order.list.button')]
|
||||||
|
private iterable $listButtons,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Seite
|
* Default Seite
|
||||||
@ -79,18 +82,18 @@ class ListController extends AbstractController
|
|||||||
Status $statusService,
|
Status $statusService,
|
||||||
Section $sectionService,
|
Section $sectionService,
|
||||||
\PSC\Shop\OrderBundle\Service\Order $orderService,
|
\PSC\Shop\OrderBundle\Service\Order $orderService,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
$customSections = $sectionService->get(\PSC\System\PluginBundle\Form\Interfaces\Section::Order);
|
$customSections = $sectionService->get(\PSC\System\PluginBundle\Form\Interfaces\Section::Order);
|
||||||
|
|
||||||
$selectedShop = $shop->getSelectedShop();
|
$selectedShop = $shop->getSelectedShop();
|
||||||
$userRepository = $em->getRepository('PSC\Shop\EntityBundle\Entity\Order');
|
$userRepository = $em->getRepository('PSC\Shop\EntityBundle\Entity\Order');
|
||||||
$qb = $userRepository->createQueryBuilder('orders')
|
$qb = $userRepository
|
||||||
|
->createQueryBuilder('orders')
|
||||||
->leftJoin('orders.contact', 'contact')
|
->leftJoin('orders.contact', 'contact')
|
||||||
->leftJoin('orders.invoiceAddress', 'invoiceAddress')
|
->leftJoin('orders.invoiceAddress', 'invoiceAddress')
|
||||||
->orderBy('orders.uid', 'desc');
|
->orderBy('orders.uid', 'desc');
|
||||||
$qbCount = $userRepository->createQueryBuilder('orders')
|
$qbCount = $userRepository
|
||||||
|
->createQueryBuilder('orders')
|
||||||
->select('count(orders.uid)')
|
->select('count(orders.uid)')
|
||||||
->leftJoin('orders.contact', 'contact')
|
->leftJoin('orders.contact', 'contact')
|
||||||
->leftJoin('orders.invoiceAddress', 'invoiceAddress')
|
->leftJoin('orders.invoiceAddress', 'invoiceAddress')
|
||||||
@ -111,24 +114,28 @@ class ListController extends AbstractController
|
|||||||
$session->set('order_sort_page', $request->get('page', 1));
|
$session->set('order_sort_page', $request->get('page', 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
$qb->andWhere('orders.shop = :shop_id')
|
$qb->andWhere('orders.shop = :shop_id')->setParameter('shop_id', $selectedShop->getUid());
|
||||||
->setParameter("shop_id", $selectedShop->getUid());
|
$qbCount->andWhere('orders.shop = :shop_id')->setParameter('shop_id', $selectedShop->getUid());
|
||||||
$qbCount->andWhere('orders.shop = :shop_id')
|
|
||||||
->setParameter("shop_id", $selectedShop->getUid());
|
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
$count = $qbCount->getQuery()->getSingleScalarResult();
|
$count = $qbCount->getQuery()->getSingleScalarResult();
|
||||||
$query->setHint('knp_paginator.count', $count);
|
$query->setHint('knp_paginator.count', $count);
|
||||||
$pagination = $paginator->paginate($query, $request->query->getInt('page', $session->get('order_sort_page', 1)), 30, [
|
$pagination = $paginator->paginate(
|
||||||
|
$query,
|
||||||
|
$request->query->getInt('page', $session->get('order_sort_page', 1)),
|
||||||
|
30,
|
||||||
|
[
|
||||||
'defaultSortFieldName' => $session->get('order_sort_field', 'orders.uid'),
|
'defaultSortFieldName' => $session->get('order_sort_field', 'orders.uid'),
|
||||||
'defaultSortDirection' => $session->get('order_sort_direction', 'desc'),
|
'defaultSortDirection' => $session->get('order_sort_direction', 'desc'),
|
||||||
'distinct' => false
|
'distinct' => false,
|
||||||
]);
|
],
|
||||||
|
);
|
||||||
return array(
|
return array(
|
||||||
'pagination' => $pagination,
|
'pagination' => $pagination,
|
||||||
'orderStatuse' => $statusService,
|
'orderStatuse' => $statusService,
|
||||||
'orderService' => $orderService,
|
'orderService' => $orderService,
|
||||||
'form' => $form->createView(),
|
'form' => $form->createView(),
|
||||||
'customSections' => $customSections
|
'customSections' => $customSections,
|
||||||
|
'customListButtons' => $this->listButtons,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +154,14 @@ class ListController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
#[Route(path: '/list/switchstatus/{order}/{status}', name: 'psc_shop_order_backend_list_switchstatus')]
|
#[Route(path: '/list/switchstatus/{order}/{status}', name: 'psc_shop_order_backend_list_switchstatus')]
|
||||||
#[Template('@PSCShopOrder/backend/list/switchstatus.html.twig')]
|
#[Template('@PSCShopOrder/backend/list/switchstatus.html.twig')]
|
||||||
public function switchStatusAction(\PSC\System\SettingsBundle\Service\Shop $shopService, SessionInterface $session, EntityManagerInterface $emService, \PSC\Shop\QueueBundle\Service\Event\Manager $eventManagerService, $order = "", $status = 10)
|
public function switchStatusAction(
|
||||||
{
|
\PSC\System\SettingsBundle\Service\Shop $shopService,
|
||||||
|
SessionInterface $session,
|
||||||
|
EntityManagerInterface $emService,
|
||||||
|
\PSC\Shop\QueueBundle\Service\Event\Manager $eventManagerService,
|
||||||
|
$order = '',
|
||||||
|
$status = 10,
|
||||||
|
) {
|
||||||
$selectedShop = $shopService->getSelectedShop();
|
$selectedShop = $shopService->getSelectedShop();
|
||||||
/**
|
/**
|
||||||
* @var \PSC\Shop\EntityBundle\Entity\Order $order
|
* @var \PSC\Shop\EntityBundle\Entity\Order $order
|
||||||
@ -159,7 +172,14 @@ class ListController extends AbstractController
|
|||||||
$order->setStatus($status);
|
$order->setStatus($status);
|
||||||
$emService->persist($order);
|
$emService->persist($order);
|
||||||
$emService->flush();
|
$emService->flush();
|
||||||
$this->logService->createLogEntry($selectedShop, $this->getUser(), LogEntry::INFO, PSCShopOrderBundle::class, $order->getUuid(), "Order Status In List Changed To: ". $order->getStatus());
|
$this->logService->createLogEntry(
|
||||||
|
$selectedShop,
|
||||||
|
$this->getUser(),
|
||||||
|
LogEntry::INFO,
|
||||||
|
PSCShopOrderBundle::class,
|
||||||
|
$order->getUuid(),
|
||||||
|
'Order Status In List Changed To: ' . $order->getStatus(),
|
||||||
|
);
|
||||||
$session->getFlashBag()->add('success', 'Status erfolgreich gesetzt');
|
$session->getFlashBag()->add('success', 'Status erfolgreich gesetzt');
|
||||||
$notify = new \PSC\Shop\QueueBundle\Event\Order\Status\Change();
|
$notify = new \PSC\Shop\QueueBundle\Event\Order\Status\Change();
|
||||||
$notify->setShop($selectedShop->getUID());
|
$notify->setShop($selectedShop->getUID());
|
||||||
@ -185,8 +205,14 @@ class ListController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
#[Route(path: '/list/switchstatuspos/{position}/{status}', name: 'psc_shop_order_backend_list_switchstatus_pos')]
|
#[Route(path: '/list/switchstatuspos/{position}/{status}', name: 'psc_shop_order_backend_list_switchstatus_pos')]
|
||||||
#[Template('@PSCShopOrder/backend/list/switchstatuspos.html.twig')]
|
#[Template('@PSCShopOrder/backend/list/switchstatuspos.html.twig')]
|
||||||
public function switchStatusPosAction(\PSC\System\SettingsBundle\Service\Shop $shopService, SessionInterface $session, EntityManagerInterface $emService, \PSC\Shop\QueueBundle\Service\Event\Manager $eventManagerService, $position = "", $status = 10)
|
public function switchStatusPosAction(
|
||||||
{
|
\PSC\System\SettingsBundle\Service\Shop $shopService,
|
||||||
|
SessionInterface $session,
|
||||||
|
EntityManagerInterface $emService,
|
||||||
|
\PSC\Shop\QueueBundle\Service\Event\Manager $eventManagerService,
|
||||||
|
$position = '',
|
||||||
|
$status = 10,
|
||||||
|
) {
|
||||||
$selectedShop = $shopService->getSelectedShop();
|
$selectedShop = $shopService->getSelectedShop();
|
||||||
/**
|
/**
|
||||||
* @var \PSC\Shop\EntityBundle\Entity\Orderpos $orderPos
|
* @var \PSC\Shop\EntityBundle\Entity\Orderpos $orderPos
|
||||||
@ -197,7 +223,14 @@ class ListController extends AbstractController
|
|||||||
$orderPos->setStatus($status);
|
$orderPos->setStatus($status);
|
||||||
$emService->persist($orderPos);
|
$emService->persist($orderPos);
|
||||||
$emService->flush();
|
$emService->flush();
|
||||||
$this->logService->createLogEntry($selectedShop, $this->getUser(), LogEntry::INFO, PSCShopOrderBundle::class, $orderPos->getUuid(), "Orderpos In List Status Changed To: " . $orderPos->getStatus());
|
$this->logService->createLogEntry(
|
||||||
|
$selectedShop,
|
||||||
|
$this->getUser(),
|
||||||
|
LogEntry::INFO,
|
||||||
|
PSCShopOrderBundle::class,
|
||||||
|
$orderPos->getUuid(),
|
||||||
|
'Orderpos In List Status Changed To: ' . $orderPos->getStatus(),
|
||||||
|
);
|
||||||
$session->getFlashBag()->add('success', 'Status erfolgreich gesetzt');
|
$session->getFlashBag()->add('success', 'Status erfolgreich gesetzt');
|
||||||
$notify = new \PSC\Shop\QueueBundle\Event\Position\Status\Change();
|
$notify = new \PSC\Shop\QueueBundle\Event\Position\Status\Change();
|
||||||
$notify->setShop($selectedShop->getUID());
|
$notify->setShop($selectedShop->getUID());
|
||||||
|
|||||||
@ -13,19 +13,26 @@
|
|||||||
|
|
||||||
namespace PSC\Shop\OrderBundle\Controller\Backend;
|
namespace PSC\Shop\OrderBundle\Controller\Backend;
|
||||||
|
|
||||||
|
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use PSC\Shop\EntityBundle\Entity\Motiv;
|
use PSC\Shop\EntityBundle\Entity\Motiv;
|
||||||
|
use PSC\Shop\EntityBundle\Entity\Orderpos;
|
||||||
|
use PSC\Shop\EntityBundle\Entity\Upload;
|
||||||
use PSC\Shop\MediaBundle\Helper\Transformer\PdfTransformer;
|
use PSC\Shop\MediaBundle\Helper\Transformer\PdfTransformer;
|
||||||
use PSC\Shop\OrderBundle\Form\Backend\Upload\DeleteType;
|
use PSC\Shop\OrderBundle\Form\Backend\Upload\DeleteType;
|
||||||
|
use PSC\Shop\ProductBundle\Model\Upload\Setting;
|
||||||
|
use Ramsey\Uuid\Uuid;
|
||||||
use Symfony\Bridge\Twig\Attribute\Template;
|
use Symfony\Bridge\Twig\Attribute\Template;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
use Symfony\Component\Security\Core\SecurityContext;
|
use Symfony\Component\Security\Core\SecurityContext;
|
||||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||||
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UploadController fürs Backend
|
* UploadController fürs Backend
|
||||||
@ -35,6 +42,29 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|||||||
*/
|
*/
|
||||||
class UploadController extends AbstractController
|
class UploadController extends AbstractController
|
||||||
{
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly DocumentManager $documentManager,
|
||||||
|
private readonly SerializerInterface $serializer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[IsGranted('ROLE_ORDER_VIEW')]
|
||||||
|
#[Route(path: '/upload/center-modal/delete/{uploadUuid}', name: 'psc_shop_order_backend_upload_center_modal_delete', methods: ['POST'])]
|
||||||
|
public function centerModalDeleteAction(string $uploadUuid, Request $request, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$upload = $entityManager->getRepository(Upload::class)->findOneBy(['uuid' => $uploadUuid]);
|
||||||
|
|
||||||
|
if (!$upload) {
|
||||||
|
return new Response('Upload nicht gefunden', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$posUuid = $upload->getOrderPos()->getUuid();
|
||||||
|
|
||||||
|
$entityManager->remove($upload);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('psc_shop_order_backend_upload_center_modal', ['pos' => $posUuid]);
|
||||||
|
}
|
||||||
|
|
||||||
#[IsGranted('ROLE_ORDER_VIEW')]
|
#[IsGranted('ROLE_ORDER_VIEW')]
|
||||||
#[Route(path: '/upload/preview', name: 'psc_shop_order_backend_upload_preview', methods: ['GET'])]
|
#[Route(path: '/upload/preview', name: 'psc_shop_order_backend_upload_preview', methods: ['GET'])]
|
||||||
public function previewAction(Request $request, PdfTransformer $pdfTransformer): BinaryFileResponse
|
public function previewAction(Request $request, PdfTransformer $pdfTransformer): BinaryFileResponse
|
||||||
@ -82,6 +112,80 @@ class UploadController extends AbstractController
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[IsGranted('ROLE_ORDER_VIEW')]
|
||||||
|
#[Route(path: '/upload/center-modal', name: 'psc_shop_order_backend_upload_center_modal', methods: ['GET', 'POST'])]
|
||||||
|
public function centerModalAction(Request $request, EntityManagerInterface $entityManager): Response
|
||||||
|
{
|
||||||
|
$posUuid = $request->query->get('pos', '');
|
||||||
|
|
||||||
|
/** @var Orderpos|null $orderpos */
|
||||||
|
$orderpos = $entityManager->getRepository(Orderpos::class)->findOneBy(['uuid' => $posUuid]);
|
||||||
|
|
||||||
|
if (!$orderpos) {
|
||||||
|
return new Response('Position nicht gefunden', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load upload areas defined on the product
|
||||||
|
$uploadAreas = [];
|
||||||
|
$product = $orderpos->getProduct();
|
||||||
|
if ($product) {
|
||||||
|
$productDoc = $this->documentManager
|
||||||
|
->getRepository(\PSC\Shop\EntityBundle\Document\Product::class)
|
||||||
|
->findOneBy(['uid' => $product->getUID()]);
|
||||||
|
|
||||||
|
if ($productDoc) {
|
||||||
|
$raw = $productDoc->getPluginSettingModule('uploads', 'config');
|
||||||
|
if ($raw) {
|
||||||
|
/** @var Setting $setting */
|
||||||
|
$setting = $this->serializer->deserialize($raw, Setting::class, 'json');
|
||||||
|
$uploadAreas = $setting->uploads;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = null;
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$file = $request->files->get('file');
|
||||||
|
$typ = trim($request->request->get('typ', ''));
|
||||||
|
|
||||||
|
if (!$file) {
|
||||||
|
$error = 'Bitte eine Datei auswählen.';
|
||||||
|
} else {
|
||||||
|
$shopUuid = $orderpos->getOrder()->getShop()->getUuid();
|
||||||
|
$uploadDir = $this->getParameter('kernel.project_dir') . '/web/uploads/' . $shopUuid . '/article/';
|
||||||
|
|
||||||
|
if (!is_dir($uploadDir)) {
|
||||||
|
mkdir($uploadDir, 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalName = $file->getClientOriginalName();
|
||||||
|
$file->move($uploadDir, $originalName);
|
||||||
|
|
||||||
|
$upload = new Upload();
|
||||||
|
$upload->setUuid(Uuid::uuid4()->toString());
|
||||||
|
$upload->setOrderPos($orderpos);
|
||||||
|
$upload->setName($originalName);
|
||||||
|
$upload->setPath($originalName);
|
||||||
|
$upload->setTyp($typ);
|
||||||
|
$upload->setExport(false);
|
||||||
|
$upload->setCreated(new \DateTime());
|
||||||
|
$upload->setUpdated(new \DateTime());
|
||||||
|
|
||||||
|
$entityManager->persist($upload);
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
return $this->redirectToRoute('psc_shop_order_backend_upload_center_modal', ['pos' => $posUuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('@PSCShopOrder/backend/upload/center_modal.html.twig', [
|
||||||
|
'orderpos' => $orderpos,
|
||||||
|
'uploadAreas' => $uploadAreas,
|
||||||
|
'error' => $error,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete Seite
|
* Delete Seite
|
||||||
*
|
*
|
||||||
|
|||||||
@ -157,6 +157,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Details
|
Details
|
||||||
</a>
|
</a>
|
||||||
|
{% for customListButton in customListButtons %}
|
||||||
|
{{ customListButton.render(orderObj)|raw }}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -0,0 +1,114 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Upload Center</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body class="bg-white p-4 text-sm font-sans">
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
<div class="mb-4 px-4 py-3 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Vorhandene Uploads #}
|
||||||
|
{% set uploads = orderpos.uploads %}
|
||||||
|
{% if uploads|length > 0 %}
|
||||||
|
<div class="mb-6">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-2">Vorhandene Dateien</h3>
|
||||||
|
<ul class="divide-y divide-gray-100 border border-gray-200 rounded-lg overflow-hidden">
|
||||||
|
{% for upload in uploads %}
|
||||||
|
<li class="flex items-center justify-between px-4 py-3 bg-white hover:bg-gray-50">
|
||||||
|
<div class="flex items-center gap-3 min-w-0">
|
||||||
|
<svg class="w-5 h-5 text-gray-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
|
</svg>
|
||||||
|
<span class="truncate text-gray-700">{{ upload.name }}</span>
|
||||||
|
{% if upload.typ %}
|
||||||
|
<span class="shrink-0 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-orange-100 text-orange-700">
|
||||||
|
{{ upload.typ }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="ml-4 flex items-center gap-3 shrink-0">
|
||||||
|
<a href="/apps/{{ upload.path }}"
|
||||||
|
target="_blank"
|
||||||
|
class="text-blue-600 hover:underline flex items-center gap-1">
|
||||||
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
||||||
|
</svg>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
<form method="POST"
|
||||||
|
action="{{ path('psc_shop_order_backend_upload_center_modal_delete', {uploadUuid: upload.uuid}) }}"
|
||||||
|
onsubmit="return confirm('Datei wirklich löschen?')">
|
||||||
|
<button type="submit"
|
||||||
|
class="text-red-500 hover:text-red-700 flex items-center gap-1">
|
||||||
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||||
|
</svg>
|
||||||
|
Löschen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p class="mb-6 text-sm text-gray-500 text-center py-4">Noch keine Dateien hochgeladen.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Upload-Formular #}
|
||||||
|
<div class="border border-gray-200 rounded-lg p-4">
|
||||||
|
<h3 class="text-sm font-semibold text-gray-700 mb-3">Neue Datei hochladen</h3>
|
||||||
|
<form method="POST" enctype="multipart/form-data" class="space-y-3">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-gray-600 mb-1">Bereich</label>
|
||||||
|
{% if uploadAreas|length > 0 %}
|
||||||
|
<select name="typ"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-transparent bg-white">
|
||||||
|
<option value="">— Bitte wählen —</option>
|
||||||
|
{% for area in uploadAreas %}
|
||||||
|
<option value="{{ area.id }}">{{ area.name }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% else %}
|
||||||
|
<input type="text"
|
||||||
|
name="typ"
|
||||||
|
placeholder="Bereich eingeben …"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-transparent">
|
||||||
|
<p class="mt-1 text-xs text-gray-400">Keine Bereiche am Produkt konfiguriert.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-gray-600 mb-1">Datei</label>
|
||||||
|
<input type="file"
|
||||||
|
name="file"
|
||||||
|
required
|
||||||
|
class="block w-full text-sm text-gray-500
|
||||||
|
file:mr-3 file:py-1.5 file:px-3
|
||||||
|
file:rounded file:border-0
|
||||||
|
file:text-sm file:font-medium
|
||||||
|
file:bg-orange-50 file:text-orange-700
|
||||||
|
hover:file:bg-orange-100 cursor-pointer">
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 bg-orange-500 text-white text-sm font-medium rounded-md hover:bg-orange-600 transition-colors">
|
||||||
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/>
|
||||||
|
</svg>
|
||||||
|
Hochladen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -29,7 +29,7 @@ class CenterUpload implements IUploadOption
|
|||||||
|
|
||||||
public function getContentUrl(): ?string
|
public function getContentUrl(): ?string
|
||||||
{
|
{
|
||||||
return null;
|
return '/apps/backend/order/upload/center-modal';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isEnabled(ProductEntity $entity, ?ProductDoc $document): bool
|
public function isEnabled(ProductEntity $entity, ?ProductDoc $document): bool
|
||||||
|
|||||||
@ -29,8 +29,12 @@ interface Section
|
|||||||
public const Shop = 9;
|
public const Shop = 9;
|
||||||
public const Order = 10;
|
public const Order = 10;
|
||||||
public const OrderPositionDetail = 11;
|
public const OrderPositionDetail = 11;
|
||||||
|
|
||||||
public function getTitle();
|
public function getTitle();
|
||||||
|
|
||||||
public function getId();
|
public function getId();
|
||||||
|
|
||||||
public function getModule();
|
public function getModule();
|
||||||
|
|
||||||
public function getController();
|
public function getController();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ class Preview
|
|||||||
array $ocrMarkers,
|
array $ocrMarkers,
|
||||||
array $configData = [],
|
array $configData = [],
|
||||||
array $currentFile = [],
|
array $currentFile = [],
|
||||||
|
array $tab = [],
|
||||||
): void {
|
): void {
|
||||||
if (empty($ocrMarkers)) {
|
if (empty($ocrMarkers)) {
|
||||||
return;
|
return;
|
||||||
@ -78,7 +79,9 @@ class Preview
|
|||||||
// Draw text with configurable font size
|
// Draw text with configurable font size
|
||||||
$pdf->SetFont('helvetica', 'B', $size); // size is font size in pt
|
$pdf->SetFont('helvetica', 'B', $size); // size is font size in pt
|
||||||
$pdf->SetTextColor(0, 0, 0);
|
$pdf->SetTextColor(0, 0, 0);
|
||||||
$pdf->Text($x, $y, $text);
|
if (isset($tab['tabNumber'])) {
|
||||||
|
$pdf->Text($x, $y, $text . ' Tab: ' . $tab['tabNumber']);
|
||||||
|
}
|
||||||
|
|
||||||
// Restore transformation
|
// Restore transformation
|
||||||
$pdf->StopTransform();
|
$pdf->StopTransform();
|
||||||
@ -167,7 +170,7 @@ class Preview
|
|||||||
});
|
});
|
||||||
$tab = array_shift($tab);
|
$tab = array_shift($tab);
|
||||||
// Render OCR markers on this page
|
// Render OCR markers on this page
|
||||||
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? [], $configData, $file);
|
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? [], $configData, $file, $tab);
|
||||||
|
|
||||||
// Draw colored background rectangle
|
// Draw colored background rectangle
|
||||||
$bgColor = $this->getBackgroundColor($file['color']);
|
$bgColor = $this->getBackgroundColor($file['color']);
|
||||||
|
|||||||
@ -48,21 +48,13 @@ class Render implements QueueInterface, ConfigurableElementInterface
|
|||||||
return 'LaufkartenLayouter Renderer';
|
return 'LaufkartenLayouter Renderer';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getForm(FormBuilderInterface $builder, $form_options, EventInterface $event): void
|
public function getForm(FormBuilderInterface $builder, $form_options, EventInterface $event): void {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function injectDocument(Form $form, EventInterface $event, Queue $objQueue): void
|
public function injectDocument(Form $form, EventInterface $event, Queue $objQueue): void {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setFormData(Form $form, EventInterface $event, Queue $queueObj): void
|
public function setFormData(Form $form, EventInterface $event, Queue $queueObj): void {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTemplate(): void
|
public function getTemplate(): void {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function execute(EventInterface $event, Queue $doc): bool
|
public function execute(EventInterface $event, Queue $doc): bool
|
||||||
{
|
{
|
||||||
@ -154,7 +146,7 @@ class Render implements QueueInterface, ConfigurableElementInterface
|
|||||||
$configData['contentAreaHeight'],
|
$configData['contentAreaHeight'],
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? []);
|
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? [], $tab);
|
||||||
$this->drawTab($pdf, $tab, $tab['x'], $tab['y'], $file);
|
$this->drawTab($pdf, $tab, $tab['x'], $tab['y'], $file);
|
||||||
|
|
||||||
// Page 2 (back, mirrored)
|
// Page 2 (back, mirrored)
|
||||||
@ -168,7 +160,7 @@ class Render implements QueueInterface, ConfigurableElementInterface
|
|||||||
$configData['contentAreaHeight'],
|
$configData['contentAreaHeight'],
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? []);
|
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? [], $tab);
|
||||||
$xPos = $configData['width'] - $tab['width'] - $tab['x'];
|
$xPos = $configData['width'] - $tab['width'] - $tab['x'];
|
||||||
$this->drawTab($pdf, $tab, $xPos - 1, $tab['y'] - 1, $file, $tab['width'] + 2, $tab['height'] + 1);
|
$this->drawTab($pdf, $tab, $xPos - 1, $tab['y'] - 1, $file, $tab['width'] + 2, $tab['height'] + 1);
|
||||||
} else {
|
} else {
|
||||||
@ -182,7 +174,7 @@ class Render implements QueueInterface, ConfigurableElementInterface
|
|||||||
$configData['contentAreaHeight'],
|
$configData['contentAreaHeight'],
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? []);
|
$this->renderOcrMarkers($pdf, $configData['ocrMarkers'] ?? [], $tab);
|
||||||
|
|
||||||
if ($frontSide) {
|
if ($frontSide) {
|
||||||
$this->drawTab($pdf, $tab, $tab['x'], $tab['y'], $file);
|
$this->drawTab($pdf, $tab, $tab['x'], $tab['y'], $file);
|
||||||
@ -245,7 +237,7 @@ class Render implements QueueInterface, ConfigurableElementInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function renderOcrMarkers(Fpdi $pdf, array $ocrMarkers): void
|
private function renderOcrMarkers(Fpdi $pdf, array $ocrMarkers, array $tab = []): void
|
||||||
{
|
{
|
||||||
if (empty($ocrMarkers)) {
|
if (empty($ocrMarkers)) {
|
||||||
return;
|
return;
|
||||||
@ -272,7 +264,7 @@ class Render implements QueueInterface, ConfigurableElementInterface
|
|||||||
$pdf->Rotate(90, $x, $y);
|
$pdf->Rotate(90, $x, $y);
|
||||||
$pdf->SetFont('helvetica', 'B', $size);
|
$pdf->SetFont('helvetica', 'B', $size);
|
||||||
$pdf->SetTextColor(0, 0, 0);
|
$pdf->SetTextColor(0, 0, 0);
|
||||||
$pdf->Text($x, $y, $marker['text'] ?? '');
|
$pdf->Text($x, $y, $marker['text'] ?? '' . ' Tab: ' . $tab['tabNumber']);
|
||||||
$pdf->StopTransform();
|
$pdf->StopTransform();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Plugin\System\PSC\Invoice\Button;
|
||||||
|
|
||||||
|
use PSC\Shop\OrderBundle\Model\Base;
|
||||||
|
use PSC\Shop\OrderBundle\Model\Order\Position;
|
||||||
|
use PSC\System\PluginBundle\Form\Section;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||||
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||||
|
|
||||||
|
#[AutoconfigureTag('backend.order.list.button')]
|
||||||
|
class OrderListButton
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private UrlGeneratorInterface $urlGenerator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function render(Base $order)
|
||||||
|
{
|
||||||
|
$url = $this->urlGenerator->generate('psc_backend_invoice_index_create') . '#/' . $order->getUuid();
|
||||||
|
|
||||||
|
return (
|
||||||
|
'<a href="'
|
||||||
|
. $url
|
||||||
|
. '#/'
|
||||||
|
. $order->getUuid()
|
||||||
|
. '" target="_blank" title="In Invoice bearbeiten"'
|
||||||
|
. ' class="inline-flex items-center justify-center gap-1 px-2.5 py-1.5 rounded-md text-xs font-medium text-white bg-orange-500 hover:bg-orange-600 transition-colors shadow-sm">'
|
||||||
|
. '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">'
|
||||||
|
. '<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" />'
|
||||||
|
. '</svg>'
|
||||||
|
. 'Bearbeiten'
|
||||||
|
. '</a>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,7 +19,7 @@ const ItemsComponent = ({positions, delPos, shop, changePos}) => {
|
|||||||
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">Netto</th>
|
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">Netto</th>
|
||||||
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">MwSt</th>
|
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">MwSt</th>
|
||||||
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">Brutto</th>
|
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">Brutto</th>
|
||||||
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">Status</th>
|
<th className="text-right py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300">Druckdaten</th>
|
||||||
<th className="py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300"></th>
|
<th className="py-4 px-4 text-xs font-bold uppercase tracking-wider text-[#EA641B] dark:text-orange-300"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import Button from '../base/Button'
|
|||||||
import EditPositionComponent from './EditPositionComponent'
|
import EditPositionComponent from './EditPositionComponent'
|
||||||
import { Shop } from '../../model/shop'
|
import { Shop } from '../../model/shop'
|
||||||
import Currency from '../base/Currency'
|
import Currency from '../base/Currency'
|
||||||
import { Button as FlowbiteButton } from "flowbite-react"
|
import { Button as FlowbiteButton, Spinner } from "flowbite-react"
|
||||||
import ProductService from '../../services/product'
|
import ProductService from '../../services/product'
|
||||||
import {UploadOption} from '../../model/uploadOption'
|
import {UploadOption} from '../../model/uploadOption'
|
||||||
import UploadOptionModal from './UploadOptionModal'
|
import UploadOptionModal from './UploadOptionModal'
|
||||||
@ -13,14 +13,20 @@ import UploadOptionModal from './UploadOptionModal'
|
|||||||
|
|
||||||
const PosComponent = ({index, pos, delPos, changePos, shop}) => {
|
const PosComponent = ({index, pos, delPos, changePos, shop}) => {
|
||||||
|
|
||||||
const [uploadOptions, setUploadOptions] = useState<UploadOption[]>([])
|
const [activeOption, setActiveOption] = useState<UploadOption | null>(null)
|
||||||
|
const [loadingOption, setLoadingOption] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pos.product.uuid) {
|
if (pos.product.uuid && pos.uploadMode) {
|
||||||
|
setLoadingOption(true)
|
||||||
const productService = new ProductService()
|
const productService = new ProductService()
|
||||||
productService.getUploadOptions(pos.product).then(setUploadOptions)
|
productService.getUploadOptions(pos.product).then(options => {
|
||||||
|
setActiveOption(options.find(o => o.type === pos.uploadMode) ?? null)
|
||||||
|
}).finally(() => setLoadingOption(false))
|
||||||
|
} else {
|
||||||
|
setActiveOption(null)
|
||||||
}
|
}
|
||||||
}, [pos.product.uuid])
|
}, [pos.product.uuid, pos.uploadMode])
|
||||||
|
|
||||||
const deletePos = (uuid: String) => {
|
const deletePos = (uuid: String) => {
|
||||||
delPos(uuid)
|
delPos(uuid)
|
||||||
@ -49,13 +55,13 @@ const PosComponent = ({index, pos, delPos, changePos, shop}) => {
|
|||||||
<Currency price={pos.price.allGross} />
|
<Currency price={pos.price.allGross} />
|
||||||
</td>
|
</td>
|
||||||
<td className="py-4 px-4 text-sm text-right">
|
<td className="py-4 px-4 text-sm text-right">
|
||||||
{uploadOptions.length > 0 && (
|
{loadingOption ? (
|
||||||
<div className="flex flex-wrap gap-1 justify-end">
|
<div className="flex justify-end">
|
||||||
{uploadOptions.map(option => (
|
<Spinner size="sm" />
|
||||||
<UploadOptionModal key={option.type} option={option} pos={pos} />
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : activeOption ? (
|
||||||
|
<UploadOptionModal option={activeOption} pos={pos} />
|
||||||
|
) : null}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-4 px-4 text-sm text-right">
|
<td className="py-4 px-4 text-sm text-right">
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
|
|||||||
@ -1,10 +1,35 @@
|
|||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {Modal, Button as FlowbiteButton} from 'flowbite-react'
|
import {createPortal} from 'react-dom'
|
||||||
|
import {Button as FlowbiteButton} from 'flowbite-react'
|
||||||
import {UploadOption} from '../../model/uploadOption'
|
import {UploadOption} from '../../model/uploadOption'
|
||||||
import {UploadFile} from '../../model/upload'
|
import {UploadFile} from '../../model/upload'
|
||||||
import {Pos} from '../../model/pos'
|
import {Pos} from '../../model/pos'
|
||||||
import {getUploadOptionRenderer} from '../../lib/uploadOptionRegistry'
|
import {getUploadOptionRenderer} from '../../lib/uploadOptionRegistry'
|
||||||
|
|
||||||
|
const BigModal = ({title, onClose, children}: {title: string, onClose: () => void, children: React.ReactNode}) =>
|
||||||
|
createPortal(
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||||
|
onClick={onClose}>
|
||||||
|
<div className="bg-white rounded-lg shadow-xl flex flex-col"
|
||||||
|
style={{width: '90vw', height: '90vh'}}
|
||||||
|
onClick={e => e.stopPropagation()}>
|
||||||
|
<div className="flex items-center justify-between px-5 py-3 border-b border-gray-200 shrink-0">
|
||||||
|
<h3 className="text-base font-semibold text-gray-800">{title}</h3>
|
||||||
|
<button onClick={onClose}
|
||||||
|
className="text-gray-400 hover:text-gray-600 p-1 rounded hover:bg-gray-100">
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 overflow-auto p-4">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)
|
||||||
|
|
||||||
const UploadOptionModal = ({option, pos}: {option: UploadOption, pos: Pos}) => {
|
const UploadOptionModal = ({option, pos}: {option: UploadOption, pos: Pos}) => {
|
||||||
const [show, setShow] = useState(false)
|
const [show, setShow] = useState(false)
|
||||||
const [files, setFiles] = useState<UploadFile[]>(pos.uploads)
|
const [files, setFiles] = useState<UploadFile[]>(pos.uploads)
|
||||||
@ -17,14 +42,12 @@ const UploadOptionModal = ({option, pos}: {option: UploadOption, pos: Pos}) => {
|
|||||||
{option.label}
|
{option.label}
|
||||||
</FlowbiteButton>
|
</FlowbiteButton>
|
||||||
|
|
||||||
<Modal show={show} onClose={() => setShow(false)}>
|
{show && (
|
||||||
<Modal.Header>{option.label} — {pos.product.title}</Modal.Header>
|
<BigModal title={`${option.label} — ${pos.product.title}`} onClose={() => setShow(false)}>
|
||||||
<Modal.Body>
|
|
||||||
{option.contentUrl ? (
|
{option.contentUrl ? (
|
||||||
<iframe
|
<iframe
|
||||||
src={option.contentUrl + '?pos=' + pos.uuid}
|
src={option.contentUrl + '?pos=' + pos.uuid}
|
||||||
className="w-full border-0 rounded"
|
className="w-full h-full border-0 rounded"
|
||||||
style={{minHeight: '400px'}}
|
|
||||||
title={option.label}
|
title={option.label}
|
||||||
/>
|
/>
|
||||||
) : Renderer ? (
|
) : Renderer ? (
|
||||||
@ -32,8 +55,8 @@ const UploadOptionModal = ({option, pos}: {option: UploadOption, pos: Pos}) => {
|
|||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-gray-500">Kein Renderer für Typ „{option.type}" registriert.</p>
|
<p className="text-sm text-gray-500">Kein Renderer für Typ „{option.type}" registriert.</p>
|
||||||
)}
|
)}
|
||||||
</Modal.Body>
|
</BigModal>
|
||||||
</Modal>
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import ProductService from "../../services/product"
|
|||||||
import {useEffect, useRef, useState} from "react"
|
import {useEffect, useRef, useState} from "react"
|
||||||
import {Price} from "../../model/price"
|
import {Price} from "../../model/price"
|
||||||
import {Pos} from "../../model/pos"
|
import {Pos} from "../../model/pos"
|
||||||
|
import {UploadOption} from "../../model/uploadOption"
|
||||||
import { useDebouncedCallback } from 'use-debounce'
|
import { useDebouncedCallback } from 'use-debounce'
|
||||||
import Button from '../base/Button'
|
import Button from '../base/Button'
|
||||||
import Currency from '../base/Currency'
|
import Currency from '../base/Currency'
|
||||||
@ -24,6 +25,8 @@ const ProductForm = ({shop, pos, handleClose, handleChange}) => {
|
|||||||
const [type, setType] = useState<number>(0)
|
const [type, setType] = useState<number>(0)
|
||||||
|
|
||||||
const [price, setPrice] = useState<Price>(new Price())
|
const [price, setPrice] = useState<Price>(new Price())
|
||||||
|
const [uploadOptions, setUploadOptions] = useState<UploadOption[]>([])
|
||||||
|
const [uploadMode, setUploadMode] = useState<string>(pos.uploadMode ?? '')
|
||||||
|
|
||||||
const loadSchema = (loadData: any) => {
|
const loadSchema = (loadData: any) => {
|
||||||
if(pos.product.uuid == "") {
|
if(pos.product.uuid == "") {
|
||||||
@ -41,6 +44,8 @@ const ProductForm = ({shop, pos, handleClose, handleChange}) => {
|
|||||||
setPrice(value['price'])
|
setPrice(value['price'])
|
||||||
setType(value['typ'])
|
setType(value['typ'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
product_api.getUploadOptions(pos.product).then(setUploadOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeCalc = (formData) => {
|
const changeCalc = (formData) => {
|
||||||
@ -72,6 +77,7 @@ const ProductForm = ({shop, pos, handleClose, handleChange}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pos.price = price
|
pos.price = price
|
||||||
|
pos.uploadMode = uploadMode
|
||||||
handleChange(pos)
|
handleChange(pos)
|
||||||
handleClose()
|
handleClose()
|
||||||
}
|
}
|
||||||
@ -96,6 +102,38 @@ const ProductForm = ({shop, pos, handleClose, handleChange}) => {
|
|||||||
formData={formData}
|
formData={formData}
|
||||||
onChange={(e) => changeCalc(e.formData)}
|
onChange={(e) => changeCalc(e.formData)}
|
||||||
validator={validator}/>
|
validator={validator}/>
|
||||||
|
|
||||||
|
{uploadOptions.length > 0 && (
|
||||||
|
<div className='mt-4 pt-4 border-t border-gray-200'>
|
||||||
|
<label className='block text-sm font-medium text-gray-700 mb-2'>Druckdaten</label>
|
||||||
|
<div className='flex flex-wrap gap-2'>
|
||||||
|
<label className='flex items-center gap-2 cursor-pointer'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
name='uploadMode'
|
||||||
|
value=''
|
||||||
|
checked={uploadMode === ''}
|
||||||
|
onChange={() => setUploadMode('')}
|
||||||
|
className='text-orange-500'
|
||||||
|
/>
|
||||||
|
<span className='text-sm text-gray-600'>Keine</span>
|
||||||
|
</label>
|
||||||
|
{uploadOptions.map(opt => (
|
||||||
|
<label key={opt.type} className='flex items-center gap-2 cursor-pointer'>
|
||||||
|
<input
|
||||||
|
type='radio'
|
||||||
|
name='uploadMode'
|
||||||
|
value={opt.type}
|
||||||
|
checked={uploadMode === opt.type}
|
||||||
|
onChange={() => setUploadMode(opt.type)}
|
||||||
|
className='text-orange-500'
|
||||||
|
/>
|
||||||
|
<span className='text-sm text-gray-600'>{opt.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='w-48 shrink-0 border-l border-gray-200 pl-6 pt-2'>
|
<div className='w-48 shrink-0 border-l border-gray-200 pl-6 pt-2'>
|
||||||
<h5 className='text-sm text-gray-600 mb-1'>Netto: <Currency price={ price.allNet} /></h5>
|
<h5 className='text-sm text-gray-600 mb-1'>Netto: <Currency price={ price.allNet} /></h5>
|
||||||
@ -104,7 +142,6 @@ const ProductForm = ({shop, pos, handleClose, handleChange}) => {
|
|||||||
<Button onClick={addProduct} type={3} variant={"success"} />
|
<Button onClick={addProduct} type={3} variant={"success"} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1342,6 +1342,10 @@ html {
|
|||||||
width: 100vw;
|
width: 100vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.min-w-0{
|
||||||
|
min-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.min-w-\[120px\]{
|
.min-w-\[120px\]{
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
@ -1991,6 +1995,16 @@ html {
|
|||||||
background-color: rgb(101 163 13 / var(--tw-bg-opacity));
|
background-color: rgb(101 163 13 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-orange-100{
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 237 213 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-orange-500{
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(249 115 22 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-psc-50{
|
.bg-psc-50{
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(238 180 175 / var(--tw-bg-opacity));
|
background-color: rgb(238 180 175 / var(--tw-bg-opacity));
|
||||||
@ -2246,6 +2260,10 @@ html {
|
|||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.font-sans{
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
}
|
||||||
|
|
||||||
.text-2xl{
|
.text-2xl{
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
@ -2383,6 +2401,11 @@ html {
|
|||||||
color: rgb(22 101 52 / var(--tw-text-opacity));
|
color: rgb(22 101 52 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-orange-700{
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(194 65 12 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-psc{
|
.text-psc{
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(234 100 27 / var(--tw-text-opacity));
|
color: rgb(234 100 27 / var(--tw-text-opacity));
|
||||||
@ -2408,6 +2431,11 @@ html {
|
|||||||
color: rgb(147 51 234 / var(--tw-text-opacity));
|
color: rgb(147 51 234 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-red-500{
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(239 68 68 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-red-600{
|
.text-red-600{
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(220 38 38 / var(--tw-text-opacity));
|
color: rgb(220 38 38 / var(--tw-text-opacity));
|
||||||
@ -2826,6 +2854,52 @@ html {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file\:mr-3::file-selector-button{
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:rounded::file-selector-button{
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:border-0::file-selector-button{
|
||||||
|
border-width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:bg-orange-50::file-selector-button{
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 247 237 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:px-3::file-selector-button{
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:py-1::file-selector-button{
|
||||||
|
padding-top: 0.25rem;
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:py-1\.5::file-selector-button{
|
||||||
|
padding-top: 0.375rem;
|
||||||
|
padding-bottom: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:text-sm::file-selector-button{
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:font-medium::file-selector-button{
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file\:text-orange-700::file-selector-button{
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(194 65 12 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.after\:absolute::after{
|
.after\:absolute::after{
|
||||||
content: var(--tw-content);
|
content: var(--tw-content);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -2962,6 +3036,11 @@ html {
|
|||||||
background-color: rgb(79 70 229 / var(--tw-bg-opacity));
|
background-color: rgb(79 70 229 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:bg-orange-600:hover{
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(234 88 12 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.hover\:bg-psc-50:hover{
|
.hover\:bg-psc-50:hover{
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(238 180 175 / var(--tw-bg-opacity));
|
background-color: rgb(238 180 175 / var(--tw-bg-opacity));
|
||||||
@ -3105,6 +3184,11 @@ html {
|
|||||||
--tw-ring-offset-width: 2px;
|
--tw-ring-offset-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover\:file\:bg-orange-100::file-selector-button:hover{
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(255 237 213 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.focus\:z-10:focus{
|
.focus\:z-10:focus{
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
@ -3196,6 +3280,11 @@ html {
|
|||||||
--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focus\:ring-orange-400:focus{
|
||||||
|
--tw-ring-opacity: 1;
|
||||||
|
--tw-ring-color: rgb(251 146 60 / var(--tw-ring-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.focus\:ring-psc-500:focus{
|
.focus\:ring-psc-500:focus{
|
||||||
--tw-ring-opacity: 1;
|
--tw-ring-opacity: 1;
|
||||||
--tw-ring-color: rgb(234 100 27 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(234 100 27 / var(--tw-ring-opacity));
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
info:
|
info:
|
||||||
datum: 25.02.2026
|
datum: 10.03.2026
|
||||||
release: 2.3.3
|
release: 2.3.3
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
- version: 2.3.3
|
- version: 2.3.3
|
||||||
datum: 25.02.2026
|
datum: 10.03.2026
|
||||||
changes:
|
changes:
|
||||||
|
- "Auftrag bearbeiten jetzt mit Dateiupload"
|
||||||
- "Auftragsdetails in Tailwind"
|
- "Auftragsdetails in Tailwind"
|
||||||
- "Bereich Aufträge hat jetzt eigene Benutzergruppe"
|
- "Bereich Aufträge hat jetzt eigene Benutzergruppe"
|
||||||
- "In EMails können Bilder eingebettet werden: {{ email.image('@images/logo.png') }}"
|
- "In EMails können Bilder eingebettet werden: {{ email.image('@images/logo.png') }}"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user