Fixes
This commit is contained in:
parent
285e0e2d33
commit
4cf1203fad
@ -476,7 +476,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||
* datetime?: array{
|
||||
* default_format?: scalar|Param|null, // Default: "Y-m-d\\TH:i:sP"
|
||||
* default_deserialization_formats?: list<scalar|Param|null>,
|
||||
* default_timezone?: scalar|Param|null, // Default: "Europe/Berlin"
|
||||
* default_timezone?: scalar|Param|null, // Default: "UTC"
|
||||
* cdata?: scalar|Param|null, // Default: true
|
||||
* },
|
||||
* array_collection?: array{
|
||||
@ -576,7 +576,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||
* datetime?: array{
|
||||
* default_format?: scalar|Param|null, // Default: "Y-m-d\\TH:i:sP"
|
||||
* default_deserialization_formats?: list<scalar|Param|null>,
|
||||
* default_timezone?: scalar|Param|null, // Default: "Europe/Berlin"
|
||||
* default_timezone?: scalar|Param|null, // Default: "UTC"
|
||||
* cdata?: scalar|Param|null, // Default: true
|
||||
* },
|
||||
* array_collection?: array{
|
||||
@ -2433,7 +2433,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||
* length?: scalar|Param|null, // Default: 5
|
||||
* width?: scalar|Param|null, // Default: 130
|
||||
* height?: scalar|Param|null, // Default: 50
|
||||
* font?: scalar|Param|null, // Default: "/data/www/new/vendor/gregwar/captcha-bundle/DependencyInjection/../Generator/Font/captcha.ttf"
|
||||
* font?: scalar|Param|null, // Default: "/application/src/new/vendor/gregwar/captcha-bundle/DependencyInjection/../Generator/Font/captcha.ttf"
|
||||
* keep_value?: scalar|Param|null, // Default: false
|
||||
* charset?: scalar|Param|null, // Default: "abcdefhjkmnprstuvwxyz23456789"
|
||||
* as_file?: scalar|Param|null, // Default: false
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\OrderBundle\Interface;
|
||||
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\IUploadModeTransformer;
|
||||
|
||||
interface IUploadMode
|
||||
{
|
||||
public function getCode(): string;
|
||||
|
||||
public function getTransformer(): IUploadModeTransformer;
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\OrderBundle\Service\UploadMode;
|
||||
|
||||
use PSC\Shop\OrderBundle\Interface\IUploadMode;
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\IUploadModeTransformer;
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\Upload\Center as PSCCenter;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
|
||||
#[AutoconfigureTag('order.position.uploadObjectType')]
|
||||
class Center implements IUploadMode
|
||||
{
|
||||
public function getCode(): string
|
||||
{
|
||||
return 'center';
|
||||
}
|
||||
|
||||
public function getTransformer(): IUploadModeTransformer
|
||||
{
|
||||
return new PSCCenter();
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\OrderBundle\Service\UploadMode;
|
||||
|
||||
use PSC\Shop\OrderBundle\Interface\IUploadMode;
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\IUploadModeTransformer;
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\Upload\Mail as PSCMail;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
|
||||
#[AutoconfigureTag('order.position.uploadObjectType')]
|
||||
class Mail implements IUploadMode
|
||||
{
|
||||
public function getCode(): string
|
||||
{
|
||||
return 'mail';
|
||||
}
|
||||
|
||||
public function getTransformer(): IUploadModeTransformer
|
||||
{
|
||||
return new PSCMail();
|
||||
}
|
||||
}
|
||||
@ -26,19 +26,19 @@ class Position extends Base
|
||||
protected \PSC\Shop\ProductBundle\Service\Product $productService;
|
||||
private \PSC\Shop\ProductBundle\Hydrate\Product $productHydrate;
|
||||
private ProductType $productTypeRegistry;
|
||||
private iterable $uploadObjectTypes;
|
||||
private iterable $uploadOptionTypes;
|
||||
|
||||
#[\Symfony\Contracts\Service\Attribute\Required]
|
||||
public function setProductService(
|
||||
\PSC\Shop\ProductBundle\Service\Product $productService,
|
||||
ProductType $productTypeRegistry,
|
||||
\PSC\Shop\ProductBundle\Hydrate\Product $productHydrate,
|
||||
#[AutowireIterator('order.position.uploadObjectType')] iterable $uploadObjectTypes,
|
||||
#[AutowireIterator('product.upload.option')] iterable $uploadOptionTypes,
|
||||
) {
|
||||
$this->productService = $productService;
|
||||
$this->productTypeRegistry = $productTypeRegistry;
|
||||
$this->productHydrate = $productHydrate;
|
||||
$this->uploadObjectTypes = $uploadObjectTypes;
|
||||
$this->uploadOptionTypes = $uploadOptionTypes;
|
||||
}
|
||||
|
||||
public function toDb(
|
||||
@ -100,7 +100,9 @@ class Position extends Base
|
||||
/**
|
||||
* Plugin Special Savings
|
||||
*/
|
||||
if ($this->productTypeRegistry->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())) {
|
||||
if ($this->productTypeRegistry->getProductType(
|
||||
$position->getProduct()->getSpecialProductTypeObject()->getTyp(),
|
||||
)) {
|
||||
$specialProductTransformer = $this->productTypeRegistry
|
||||
->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
|
||||
->getPositionProductTransformer();
|
||||
@ -188,8 +190,8 @@ class Position extends Base
|
||||
$position->setCustomerInfo((string) $positionDoc->getCustomerInfo());
|
||||
|
||||
if ($positionDoc->getUploadMode() != '') {
|
||||
foreach ($this->uploadObjectTypes as $object) {
|
||||
if ($positionDoc->getUploadMode() == $object->getCode()) {
|
||||
foreach ($this->uploadOptionTypes as $object) {
|
||||
if ($positionDoc->getUploadMode() == $object->getType()) {
|
||||
$object->getTransformer()->fromDb($position, $pos, $positionDoc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\ProductBundle\Api\Product;
|
||||
|
||||
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PSC\Component\ApiBundle\Dto\Error\NotFound;
|
||||
use PSC\Shop\EntityBundle\Entity\Product;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutowireIterator;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class GetUploadOptions extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly DocumentManager $documentManager,
|
||||
#[AutowireIterator('product.upload.option')] private readonly iterable $uploadOptions,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/product/{uuid}/upload-options', methods: ['GET'])]
|
||||
public function getOptions(string $uuid): JsonResponse
|
||||
{
|
||||
/** @var Product|null $product */
|
||||
$product = $this->entityManager->getRepository(Product::class)->findOneBy(['uuid' => $uuid]);
|
||||
|
||||
if (!$product) {
|
||||
return $this->json(new NotFound('Product not found'), 404);
|
||||
}
|
||||
|
||||
$productDoc = $this->documentManager
|
||||
->getRepository(\PSC\Shop\EntityBundle\Document\Product::class)
|
||||
->findOneBy(['uid' => $product->getUID()]);
|
||||
|
||||
$result = [];
|
||||
foreach ($this->uploadOptions as $option) {
|
||||
if ($option->isEnabled($product, $productDoc)) {
|
||||
$result[] = [
|
||||
'type' => $option->getType(),
|
||||
'label' => $option->getLabel(),
|
||||
'contentUrl' => $option->getContentUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->json(['data' => $result]);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\ProductBundle\Interfaces;
|
||||
|
||||
use PSC\Shop\EntityBundle\Document\Product as ProductDoc;
|
||||
use PSC\Shop\EntityBundle\Entity\Product as ProductEntity;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\IUploadOptionTransformer;
|
||||
|
||||
interface IUploadOption
|
||||
{
|
||||
public function getTransformer(): IUploadOptionTransformer;
|
||||
|
||||
public function getType(): string;
|
||||
|
||||
public function getLabel(): string;
|
||||
|
||||
public function isEnabled(ProductEntity $entity, ?ProductDoc $document): bool;
|
||||
|
||||
/**
|
||||
* Optional: return a backend URL that will be rendered as an iframe in the modal.
|
||||
* Use this from plugins to provide custom upload UIs without recompiling the frontend.
|
||||
* The frontend will append ?pos={positionUuid} as query parameter.
|
||||
* Return null to use the built-in React renderer (registered via uploadOptionRegistry).
|
||||
*/
|
||||
public function getContentUrl(): ?string;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\ProductBundle\Service\UploadOption;
|
||||
|
||||
use PSC\Shop\EntityBundle\Document\Product as ProductDoc;
|
||||
use PSC\Shop\EntityBundle\Entity\Product as ProductEntity;
|
||||
use PSC\Shop\ProductBundle\Interfaces\IUploadOption;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\IUploadOptionTransformer;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\Upload\Center;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
|
||||
#[AutoconfigureTag('product.upload.option')]
|
||||
class CenterUpload implements IUploadOption
|
||||
{
|
||||
public function getType(): string
|
||||
{
|
||||
return 'center';
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'Upload im Auftrag nachreichen';
|
||||
}
|
||||
|
||||
public function getTransformer(): IUploadOptionTransformer
|
||||
{
|
||||
return new Center();
|
||||
}
|
||||
|
||||
public function getContentUrl(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isEnabled(ProductEntity $entity, ?ProductDoc $document): bool
|
||||
{
|
||||
return (bool) $entity->isUploadCenter();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\ProductBundle\Service\UploadOption;
|
||||
|
||||
use PSC\Shop\EntityBundle\Document\Product as ProductDoc;
|
||||
use PSC\Shop\EntityBundle\Entity\Product as ProductEntity;
|
||||
use PSC\Shop\ProductBundle\Interfaces\IUploadOption;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\IUploadOptionTransformer;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\Upload\Mail;
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
|
||||
#[AutoconfigureTag('product.upload.option')]
|
||||
class EmailUpload implements IUploadOption
|
||||
{
|
||||
public function getType(): string
|
||||
{
|
||||
return 'mail';
|
||||
}
|
||||
|
||||
public function getLabel(): string
|
||||
{
|
||||
return 'Per E-Mail einsenden';
|
||||
}
|
||||
|
||||
public function getTransformer(): IUploadOptionTransformer
|
||||
{
|
||||
return new Mail();
|
||||
}
|
||||
|
||||
public function getContentUrl(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isEnabled(ProductEntity $entity, ?ProductDoc $document): bool
|
||||
{
|
||||
return (bool) $entity->isUploadEmail();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\ProductBundle\Transformer\Order\Position;
|
||||
|
||||
use PSC\Shop\EntityBundle\Entity\Orderpos;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position;
|
||||
|
||||
interface IUploadOptionTransformer
|
||||
{
|
||||
public function fromDb(Position $position, Orderpos $posEntity, \PSC\Shop\EntityBundle\Document\Position $posDoc);
|
||||
|
||||
public function toDb(Position $position, Orderpos $posEntity, \PSC\Shop\EntityBundle\Document\Position $posDoc);
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\OrderBundle\Transformer\Order\Position\Upload;
|
||||
namespace PSC\Shop\ProductBundle\Transformer\Order\Position\Upload;
|
||||
|
||||
use PSC\Shop\EntityBundle\Document\Position as PosDoc;
|
||||
use PSC\Shop\EntityBundle\Entity\Orderpos;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position\Upload;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position\Upload\Center as PSCCenter;
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\IUploadModeTransformer;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\IUploadOptionTransformer;
|
||||
|
||||
class Center implements IUploadModeTransformer
|
||||
class Center implements IUploadOptionTransformer
|
||||
{
|
||||
public function fromDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void
|
||||
{
|
||||
@ -1,21 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace PSC\Shop\OrderBundle\Transformer\Order\Position\Upload;
|
||||
namespace PSC\Shop\ProductBundle\Transformer\Order\Position\Upload;
|
||||
|
||||
use PSC\Shop\EntityBundle\Document\Position as PosDoc;
|
||||
use PSC\Shop\EntityBundle\Entity\Orderpos;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position;
|
||||
use PSC\Shop\OrderBundle\Model\Order\Position\Upload\Mail as PSCMail;
|
||||
use PSC\Shop\OrderBundle\Transformer\Order\Position\IUploadModeTransformer;
|
||||
use PSC\Shop\ProductBundle\Transformer\Order\Position\IUploadOptionTransformer;
|
||||
|
||||
class Mail implements IUploadModeTransformer
|
||||
class Mail implements IUploadOptionTransformer
|
||||
{
|
||||
public function fromDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void
|
||||
{
|
||||
$position->setUploadTypeObject(new PSCMail());
|
||||
}
|
||||
|
||||
public function toDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void
|
||||
{
|
||||
}
|
||||
public function toDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void {}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import {Pos} from '../model/pos'
|
||||
import {UploadFile} from '../model/upload'
|
||||
|
||||
export interface UploadOptionRendererProps {
|
||||
pos: Pos
|
||||
files: UploadFile[]
|
||||
onUploaded: (f: UploadFile) => void
|
||||
}
|
||||
|
||||
export type UploadOptionRenderer = (props: UploadOptionRendererProps) => React.ReactElement | null
|
||||
|
||||
const registry = new Map<string, UploadOptionRenderer>()
|
||||
|
||||
export const registerUploadOptionRenderer = (type: string, renderer: UploadOptionRenderer) => {
|
||||
registry.set(type, renderer)
|
||||
}
|
||||
|
||||
export const getUploadOptionRenderer = (type: string): UploadOptionRenderer | undefined => {
|
||||
return registry.get(type)
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import "./assets/index.css"
|
||||
import "reflect-metadata";
|
||||
import "./modules/uploadOptions";
|
||||
import * as $ from "jquery";
|
||||
import { App } from "./app/app";
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import {Product} from "./product"
|
||||
import {Price} from "./price"
|
||||
import {UploadFile} from "./upload"
|
||||
|
||||
export class Pos {
|
||||
uuid: String = ""
|
||||
@ -8,6 +9,8 @@ export class Pos {
|
||||
status: Number = 10
|
||||
count: Number = 1
|
||||
price: Price = new Price()
|
||||
uploads: UploadFile[] = []
|
||||
uploadMode: string = ''
|
||||
|
||||
constructor() {
|
||||
this.uuid = uuidv4()
|
||||
@ -17,7 +20,16 @@ export class Pos {
|
||||
this.count = item.price.count
|
||||
this.status = item.status
|
||||
this.uuid = item.uuid
|
||||
this.uploadMode = item.uploadMode ?? ''
|
||||
this.price.parseFromJson(item.price)
|
||||
this.product.parseFromJson(item.product)
|
||||
this.uploads = (item.uploads ?? []).map((u: any) => {
|
||||
const f = new UploadFile()
|
||||
f.uuid = u.uuid
|
||||
f.name = u.fileName
|
||||
f.path = u.path
|
||||
f.typ = u.typ
|
||||
return f
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
export class UploadFile {
|
||||
uuid: string = ''
|
||||
name: string = ''
|
||||
path: string = ''
|
||||
typ: string = ''
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
export class UploadOption {
|
||||
type: string = ''
|
||||
label: string = ''
|
||||
contentUrl: string | null = null
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import * as PropTypes from "prop-types"
|
||||
import {Pos} from "../../model/pos"
|
||||
import Button from '../base/Button'
|
||||
@ -6,10 +6,22 @@ import EditPositionComponent from './EditPositionComponent'
|
||||
import { Shop } from '../../model/shop'
|
||||
import Currency from '../base/Currency'
|
||||
import { Button as FlowbiteButton } from "flowbite-react"
|
||||
import ProductService from '../../services/product'
|
||||
import {UploadOption} from '../../model/uploadOption'
|
||||
import UploadOptionModal from './UploadOptionModal'
|
||||
|
||||
|
||||
const PosComponent = ({index, pos, delPos, changePos, shop}) => {
|
||||
|
||||
const [uploadOptions, setUploadOptions] = useState<UploadOption[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (pos.product.uuid) {
|
||||
const productService = new ProductService()
|
||||
productService.getUploadOptions(pos.product).then(setUploadOptions)
|
||||
}
|
||||
}, [pos.product.uuid])
|
||||
|
||||
const deletePos = (uuid: String) => {
|
||||
delPos(uuid)
|
||||
}
|
||||
@ -36,7 +48,15 @@ const PosComponent = ({index, pos, delPos, changePos, shop}) => {
|
||||
<td className="py-4 px-4 text-sm text-right font-semibold text-[#EA641B] dark:text-orange-300">
|
||||
<Currency price={pos.price.allGross} />
|
||||
</td>
|
||||
<td className="py-4 px-4 text-sm text-right text-gray-700 dark:text-gray-300"></td>
|
||||
<td className="py-4 px-4 text-sm text-right">
|
||||
{uploadOptions.length > 0 && (
|
||||
<div className="flex flex-wrap gap-1 justify-end">
|
||||
{uploadOptions.map(option => (
|
||||
<UploadOptionModal key={option.type} option={option} pos={pos} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-4 px-4 text-sm text-right">
|
||||
<div className="flex justify-end">
|
||||
<FlowbiteButton.Group>
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
import React, {useState} from 'react'
|
||||
import {Modal, Button as FlowbiteButton} from 'flowbite-react'
|
||||
import {UploadOption} from '../../model/uploadOption'
|
||||
import {UploadFile} from '../../model/upload'
|
||||
import {Pos} from '../../model/pos'
|
||||
import {getUploadOptionRenderer} from '../../lib/uploadOptionRegistry'
|
||||
|
||||
const UploadOptionModal = ({option, pos}: {option: UploadOption, pos: Pos}) => {
|
||||
const [show, setShow] = useState(false)
|
||||
const [files, setFiles] = useState<UploadFile[]>(pos.uploads)
|
||||
|
||||
const Renderer = getUploadOptionRenderer(option.type)
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlowbiteButton size="xs" color="light" onClick={() => setShow(true)} title={option.label}>
|
||||
{option.label}
|
||||
</FlowbiteButton>
|
||||
|
||||
<Modal show={show} onClose={() => setShow(false)}>
|
||||
<Modal.Header>{option.label} — {pos.product.title}</Modal.Header>
|
||||
<Modal.Body>
|
||||
{option.contentUrl ? (
|
||||
<iframe
|
||||
src={option.contentUrl + '?pos=' + pos.uuid}
|
||||
className="w-full border-0 rounded"
|
||||
style={{minHeight: '400px'}}
|
||||
title={option.label}
|
||||
/>
|
||||
) : Renderer ? (
|
||||
<Renderer pos={pos} files={files} onUploaded={f => setFiles(prev => [...prev, f])} />
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">Kein Renderer für Typ „{option.type}" registriert.</p>
|
||||
)}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UploadOptionModal
|
||||
@ -0,0 +1,76 @@
|
||||
import React, {useRef, useState} from 'react'
|
||||
import {Button as FlowbiteButton, Spinner} from 'flowbite-react'
|
||||
import {UploadOptionRendererProps, registerUploadOptionRenderer} from '../../lib/uploadOptionRegistry'
|
||||
import UploadService from '../../services/upload'
|
||||
|
||||
const FileUploadRenderer = ({pos, files, onUploaded}: UploadOptionRendererProps) => {
|
||||
const uploadService = new UploadService()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
setUploading(true)
|
||||
try {
|
||||
const uploaded = await uploadService.uploadFile(String(pos.uuid), file, 'upload')
|
||||
onUploaded(uploaded)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
if (fileInputRef.current) fileInputRef.current.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{files.length > 0 ? (
|
||||
<ul className="divide-y divide-gray-100 border border-gray-200 rounded-lg overflow-hidden">
|
||||
{files.map(f => (
|
||||
<li key={f.uuid} className="flex items-center justify-between px-4 py-3 bg-white hover:bg-gray-50">
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<svg className="w-5 h-5 text-gray-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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 className="text-sm text-gray-700 truncate">{f.name}</span>
|
||||
</div>
|
||||
<a
|
||||
href={'/apps/' + f.path}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="ml-4 shrink-0 text-sm text-blue-600 hover:underline flex items-center gap-1"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
</svg>
|
||||
Download
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500 text-center py-4">Noch keine Dateien vorhanden</p>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<input ref={fileInputRef} type="file" className="hidden" onChange={handleFileChange} />
|
||||
<FlowbiteButton size="sm" color="light" onClick={() => fileInputRef.current?.click()} disabled={uploading}>
|
||||
{uploading ? (
|
||||
<><Spinner size="sm" className="mr-2" />Wird hochgeladen…</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={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>
|
||||
Datei hochladen
|
||||
</>
|
||||
)}
|
||||
</FlowbiteButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
registerUploadOptionRenderer('upload', FileUploadRenderer)
|
||||
registerUploadOptionRenderer('center', FileUploadRenderer)
|
||||
|
||||
export default FileUploadRenderer
|
||||
@ -0,0 +1,12 @@
|
||||
import React from 'react'
|
||||
import {registerUploadOptionRenderer} from '../../lib/uploadOptionRegistry'
|
||||
|
||||
const InfoText = ({text}: {text: string}) => (
|
||||
<p className="text-sm text-gray-600">{text}</p>
|
||||
)
|
||||
|
||||
registerUploadOptionRenderer('post', () => <InfoText text="Der Kunde schickt die Druckdaten per Post." />)
|
||||
registerUploadOptionRenderer('email', () => <InfoText text="Der Kunde schickt die Druckdaten per E-Mail." />)
|
||||
registerUploadOptionRenderer('steplayouter', () => <InfoText text="Der Kunde gestaltet die Druckdaten im StepLayouter." />)
|
||||
registerUploadOptionRenderer('provided', () => <InfoText text="Die Druckdatei wird von der Druckerei bereitgestellt." />)
|
||||
registerUploadOptionRenderer('from_latest_order', () => <InfoText text="Die Druckdaten werden aus dem letzten Auftrag übernommen." />)
|
||||
@ -0,0 +1,4 @@
|
||||
// Import all built-in upload option renderers to trigger their self-registration.
|
||||
// To add a new renderer, create a file in this directory and import it here.
|
||||
import './FileUploadRenderer'
|
||||
import './InfoRenderers'
|
||||
@ -5,6 +5,7 @@ import { Shop } from "../model/shop";
|
||||
import {ProductGroup} from "../model/productGroup";
|
||||
import {Product} from "../model/product";
|
||||
import {JSONSchema7} from "json-schema";
|
||||
import {UploadOption} from "../model/uploadOption";
|
||||
|
||||
@singleton()
|
||||
@autoInjectable()
|
||||
@ -72,6 +73,22 @@ class ProductService {
|
||||
return response.data;
|
||||
})
|
||||
}
|
||||
|
||||
async getUploadOptions(product: Product): Promise<UploadOption[]> {
|
||||
return await axios.get('/apps/api/product/' + product.uuid + '/upload-options', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + this.token?.currentToken
|
||||
}
|
||||
}).then((response) => {
|
||||
return response.data.data.map((item: any) => {
|
||||
const opt = new UploadOption()
|
||||
opt.type = item.type
|
||||
opt.label = item.label
|
||||
opt.contentUrl = item.contentUrl ?? null
|
||||
return opt
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductService
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import {inject, autoInjectable, singleton} from "tsyringe-neo";
|
||||
import Token from "./token";
|
||||
import axios from 'axios';
|
||||
import {UploadFile} from "../model/upload";
|
||||
|
||||
@singleton()
|
||||
@autoInjectable()
|
||||
class UploadService {
|
||||
constructor(@inject(Token) token?: Token) {
|
||||
this.token = token
|
||||
}
|
||||
|
||||
async uploadFile(posUuid: string, file: File, typ: string): Promise<UploadFile> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('position', posUuid)
|
||||
formData.append('typ', typ)
|
||||
|
||||
return await axios.post('/apps/api/upload/create', formData, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + this.token?.currentToken,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
}).then((response) => {
|
||||
const f = new UploadFile()
|
||||
f.uuid = response.data.uuid
|
||||
f.name = response.data.name
|
||||
return f
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default UploadService
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user