This commit is contained in:
Thomas Peterson 2026-03-09 13:01:38 +01:00
parent 285e0e2d33
commit 4cf1203fad
26 changed files with 480 additions and 122 deletions

View File

@ -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: "Europe/Berlin" * default_timezone?: scalar|Param|null, // Default: "UTC"
* 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: "Europe/Berlin" * default_timezone?: scalar|Param|null, // Default: "UTC"
* 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: "/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 * 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

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -26,19 +26,19 @@ class Position extends Base
protected \PSC\Shop\ProductBundle\Service\Product $productService; protected \PSC\Shop\ProductBundle\Service\Product $productService;
private \PSC\Shop\ProductBundle\Hydrate\Product $productHydrate; private \PSC\Shop\ProductBundle\Hydrate\Product $productHydrate;
private ProductType $productTypeRegistry; private ProductType $productTypeRegistry;
private iterable $uploadObjectTypes; private iterable $uploadOptionTypes;
#[\Symfony\Contracts\Service\Attribute\Required] #[\Symfony\Contracts\Service\Attribute\Required]
public function setProductService( public function setProductService(
\PSC\Shop\ProductBundle\Service\Product $productService, \PSC\Shop\ProductBundle\Service\Product $productService,
ProductType $productTypeRegistry, ProductType $productTypeRegistry,
\PSC\Shop\ProductBundle\Hydrate\Product $productHydrate, \PSC\Shop\ProductBundle\Hydrate\Product $productHydrate,
#[AutowireIterator('order.position.uploadObjectType')] iterable $uploadObjectTypes, #[AutowireIterator('product.upload.option')] iterable $uploadOptionTypes,
) { ) {
$this->productService = $productService; $this->productService = $productService;
$this->productTypeRegistry = $productTypeRegistry; $this->productTypeRegistry = $productTypeRegistry;
$this->productHydrate = $productHydrate; $this->productHydrate = $productHydrate;
$this->uploadObjectTypes = $uploadObjectTypes; $this->uploadOptionTypes = $uploadOptionTypes;
} }
public function toDb( public function toDb(
@ -100,7 +100,9 @@ class Position extends Base
/** /**
* Plugin Special Savings * Plugin Special Savings
*/ */
if ($this->productTypeRegistry->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())) { if ($this->productTypeRegistry->getProductType(
$position->getProduct()->getSpecialProductTypeObject()->getTyp(),
)) {
$specialProductTransformer = $this->productTypeRegistry $specialProductTransformer = $this->productTypeRegistry
->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp()) ->getProductType($position->getProduct()->getSpecialProductTypeObject()->getTyp())
->getPositionProductTransformer(); ->getPositionProductTransformer();
@ -188,8 +190,8 @@ class Position extends Base
$position->setCustomerInfo((string) $positionDoc->getCustomerInfo()); $position->setCustomerInfo((string) $positionDoc->getCustomerInfo());
if ($positionDoc->getUploadMode() != '') { if ($positionDoc->getUploadMode() != '') {
foreach ($this->uploadObjectTypes as $object) { foreach ($this->uploadOptionTypes as $object) {
if ($positionDoc->getUploadMode() == $object->getCode()) { if ($positionDoc->getUploadMode() == $object->getType()) {
$object->getTransformer()->fromDb($position, $pos, $positionDoc); $object->getTransformer()->fromDb($position, $pos, $positionDoc);
} }
} }

View File

@ -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]);
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -1,15 +1,15 @@
<?php <?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\Document\Position as PosDoc;
use PSC\Shop\EntityBundle\Entity\Orderpos; use PSC\Shop\EntityBundle\Entity\Orderpos;
use PSC\Shop\OrderBundle\Model\Order\Position; use PSC\Shop\OrderBundle\Model\Order\Position;
use PSC\Shop\OrderBundle\Model\Order\Position\Upload; use PSC\Shop\OrderBundle\Model\Order\Position\Upload;
use PSC\Shop\OrderBundle\Model\Order\Position\Upload\Center as PSCCenter; 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 public function fromDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void
{ {

View File

@ -1,21 +1,19 @@
<?php <?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\Document\Position as PosDoc;
use PSC\Shop\EntityBundle\Entity\Orderpos; use PSC\Shop\EntityBundle\Entity\Orderpos;
use PSC\Shop\OrderBundle\Model\Order\Position; use PSC\Shop\OrderBundle\Model\Order\Position;
use PSC\Shop\OrderBundle\Model\Order\Position\Upload\Mail as PSCMail; 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 public function fromDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void
{ {
$position->setUploadTypeObject(new PSCMail()); $position->setUploadTypeObject(new PSCMail());
} }
public function toDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void public function toDb(Position $position, Orderpos $posEntity, PosDoc $posDoc): void {}
{
}
} }

View File

@ -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)
}

View File

@ -1,5 +1,6 @@
import "./assets/index.css" import "./assets/index.css"
import "reflect-metadata"; import "reflect-metadata";
import "./modules/uploadOptions";
import * as $ from "jquery"; import * as $ from "jquery";
import { App } from "./app/app"; import { App } from "./app/app";

View File

@ -1,6 +1,7 @@
import {v4 as uuidv4} from 'uuid' import {v4 as uuidv4} from 'uuid'
import {Product} from "./product" import {Product} from "./product"
import {Price} from "./price" import {Price} from "./price"
import {UploadFile} from "./upload"
export class Pos { export class Pos {
uuid: String = "" uuid: String = ""
@ -8,6 +9,8 @@ export class Pos {
status: Number = 10 status: Number = 10
count: Number = 1 count: Number = 1
price: Price = new Price() price: Price = new Price()
uploads: UploadFile[] = []
uploadMode: string = ''
constructor() { constructor() {
this.uuid = uuidv4() this.uuid = uuidv4()
@ -17,7 +20,16 @@ export class Pos {
this.count = item.price.count this.count = item.price.count
this.status = item.status this.status = item.status
this.uuid = item.uuid this.uuid = item.uuid
this.uploadMode = item.uploadMode ?? ''
this.price.parseFromJson(item.price) this.price.parseFromJson(item.price)
this.product.parseFromJson(item.product) 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
})
} }
} }

View File

@ -0,0 +1,6 @@
export class UploadFile {
uuid: string = ''
name: string = ''
path: string = ''
typ: string = ''
}

View File

@ -0,0 +1,5 @@
export class UploadOption {
type: string = ''
label: string = ''
contentUrl: string | null = null
}

View File

@ -1,4 +1,4 @@
import React from 'react' import React, {useEffect, useState} from 'react'
import * as PropTypes from "prop-types" import * as PropTypes from "prop-types"
import {Pos} from "../../model/pos" import {Pos} from "../../model/pos"
import Button from '../base/Button' import Button from '../base/Button'
@ -6,10 +6,22 @@ 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 } 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 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) => { const deletePos = (uuid: String) => {
delPos(uuid) 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"> <td className="py-4 px-4 text-sm text-right font-semibold text-[#EA641B] dark:text-orange-300">
<Currency price={pos.price.allGross} /> <Currency price={pos.price.allGross} />
</td> </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"> <td className="py-4 px-4 text-sm text-right">
<div className="flex justify-end"> <div className="flex justify-end">
<FlowbiteButton.Group> <FlowbiteButton.Group>

View File

@ -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

View File

@ -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

View File

@ -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." />)

View File

@ -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'

View File

@ -5,6 +5,7 @@ import { Shop } from "../model/shop";
import {ProductGroup} from "../model/productGroup"; import {ProductGroup} from "../model/productGroup";
import {Product} from "../model/product"; import {Product} from "../model/product";
import {JSONSchema7} from "json-schema"; import {JSONSchema7} from "json-schema";
import {UploadOption} from "../model/uploadOption";
@singleton() @singleton()
@autoInjectable() @autoInjectable()
@ -72,6 +73,22 @@ class ProductService {
return response.data; 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 export default ProductService

View File

@ -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