From 10e6fb48f323cb0813e6ea0e2033a86d539de814 Mon Sep 17 00:00:00 2001 From: Marcel Diegelmann Date: Thu, 3 Jul 2025 13:47:20 +0200 Subject: [PATCH] =?UTF-8?q?Assembly=20Listen=C3=BCbersicht=20umsetzen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/css/app/images.css | 5 + config/parameters.yaml | 6 - docs/configuration.md | 3 +- src/Controller/AssemblyController.php | 74 ++++++ src/DataTables/AssemblyDataTable.php | 249 ++++++++++++++++++ src/DataTables/Filters/AssemblyFilter.php | 68 +++++ .../Filters/AssemblySearchFilter.php | 183 +++++++++++++ .../Helpers/AssemblyDataTableHelper.php | 77 ++++++ .../Helpers/ProjectDataTableHelper.php | 2 +- src/Form/Filters/AssemblyFilterType.php | 114 ++++++++ .../assemblies/lists/_action_bar.html.twig | 6 + templates/assemblies/lists/_filter.html.twig | 62 +++++ templates/assemblies/lists/all_list.html.twig | 30 +++ templates/assemblies/lists/data.html.twig | 3 + translations/messages.cs.xlf | 126 +++++++++ translations/messages.da.xlf | 126 +++++++++ translations/messages.de.xlf | 126 +++++++++ translations/messages.el.xlf | 126 +++++++++ translations/messages.en.xlf | 126 +++++++++ translations/messages.es.xlf | 126 +++++++++ translations/messages.fr.xlf | 126 +++++++++ translations/messages.it.xlf | 126 +++++++++ translations/messages.ja.xlf | 126 +++++++++ translations/messages.nl.xlf | 126 +++++++++ translations/messages.pl.xlf | 126 +++++++++ translations/messages.ru.xlf | 126 +++++++++ translations/messages.zh.xlf | 126 +++++++++ 27 files changed, 2511 insertions(+), 9 deletions(-) create mode 100644 src/DataTables/AssemblyDataTable.php create mode 100644 src/DataTables/Filters/AssemblyFilter.php create mode 100644 src/DataTables/Filters/AssemblySearchFilter.php create mode 100644 src/DataTables/Helpers/AssemblyDataTableHelper.php create mode 100644 src/Form/Filters/AssemblyFilterType.php create mode 100644 templates/assemblies/lists/_action_bar.html.twig create mode 100644 templates/assemblies/lists/_filter.html.twig create mode 100644 templates/assemblies/lists/all_list.html.twig create mode 100644 templates/assemblies/lists/data.html.twig diff --git a/assets/css/app/images.css b/assets/css/app/images.css index 0212a85b..132cab99 100644 --- a/assets/css/app/images.css +++ b/assets/css/app/images.css @@ -61,3 +61,8 @@ .object-fit-cover { object-fit: cover; } + +.assembly-table-image { + max-height: 40px; + object-fit: contain; +} diff --git a/config/parameters.yaml b/config/parameters.yaml index 345b8feb..e86072fe 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -48,14 +48,8 @@ parameters: ###################################################################################################################### # Table settings ###################################################################################################################### -<<<<<<< HEAD partdb.table.assemblies.default_columns: '%env(trim:string:TABLE_ASSEMBLIES_DEFAULT_COLUMNS)%' # The default columns in assembly tables and their order -======= - partdb.table.default_page_size: '%env(int:TABLE_DEFAULT_PAGE_SIZE)%' # The default number of entries shown per page in tables - partdb.table.parts.default_columns: '%env(trim:string:TABLE_PARTS_DEFAULT_COLUMNS)%' # The default columns in part tables and their order - partdb.table.assemblies.default_columns: '%env(trim:string:TABLE_ASSEMBLIES_DEFAULT_COLUMNS)%' # The default columns in assembly tables and their order partdb.table.assemblies_bom.default_columns: '%env(trim:string:TABLE_ASSEMBLIES_BOM_DEFAULT_COLUMNS)%' # The default columns in assembly bom tables and their order ->>>>>>> 2779c55a (Baugruppen Stückliste um referenzierte Baugruppe erweitern) ###################################################################################################################### # Miscellaneous diff --git a/docs/configuration.md b/docs/configuration.md index 498308b0..242164bf 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -137,8 +137,7 @@ bundled with Part-DB. Set `DATABASE_MYSQL_SSL_VERIFY_CERT` if you want to accept time). Also specify the default order of the columns. This is a comma separated list of column names. Available columns are: `name`, `id`, `ipn`, `description`, `category`, `footprint`, `manufacturer`, `storage_location`, `amount`, `minamount`, `partUnit`, `addedDate`, `lastModified`, `needs_review`, `favorite`, `manufacturing_status`, `manufacturer_product_number`, `mass`, `tags`, `attachments`, `edit`. -* `TABLE_ASSEMBLIES_DEFAULT_COLUMNS`: The columns in assemblies tables, which are visible by default (when loading table for first - time). +* `TABLE_ASSEMBLIES_DEFAULT_COLUMNS`: The columns in assemblies tables, which are visible by default (when loading table for first time). Also specify the default order of the columns. This is a comma separated list of column names. Available columns are: `name`, `id`, `ipn`, `description`, `referencedAssemblies`, `edit`, `addedDate`, `lastModified`. * `TABLE_ASSEMBLIES_BOM_DEFAULT_COLUMNS`: The columns in assemblies bom tables, which are visible by default (when loading table for first time). diff --git a/src/Controller/AssemblyController.php b/src/Controller/AssemblyController.php index 9106f677..a1ba7fa6 100644 --- a/src/Controller/AssemblyController.php +++ b/src/Controller/AssemblyController.php @@ -23,15 +23,22 @@ declare(strict_types=1); namespace App\Controller; use App\DataTables\AssemblyBomEntriesDataTable; +use App\DataTables\AssemblyDataTable; +use App\DataTables\ErrorDataTable; +use App\DataTables\Filters\AssemblyFilter; use App\Entity\AssemblySystem\Assembly; use App\Entity\AssemblySystem\AssemblyBOMEntry; use App\Entity\Parts\Part; +use App\Exceptions\InvalidRegexException; use App\Form\AssemblySystem\AssemblyAddPartsType; use App\Form\AssemblySystem\AssemblyBuildType; +use App\Form\Filters\AssemblyFilterType; use App\Helpers\Assemblies\AssemblyBuildRequest; use App\Services\ImportExportSystem\BOMImporter; use App\Services\AssemblySystem\AssemblyBuildHelper; +use App\Services\Trees\NodesListBuilder; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\EntityManagerInterface; use League\Csv\SyntaxError; use Omines\DataTablesBundle\DataTableFactory; @@ -54,9 +61,76 @@ class AssemblyController extends AbstractController public function __construct( private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator, + private readonly NodesListBuilder $nodesListBuilder ) { } + #[Route(path: '/list', name: 'assemblies_list')] + public function showAll(Request $request): Response + { + return $this->showListWithFilter($request,'assemblies/lists/all_list.html.twig'); + } + + /** + * Common implementation for the part list pages. + * @param Request $request The request to parse + * @param string $template The template that should be rendered + * @param callable|null $filter_changer A function that is called with the filter object as parameter. This function can be used to customize the filter + * @param callable|null $form_changer A function that is called with the form object as parameter. This function can be used to customize the form + * @param array $additonal_template_vars Any additional template variables that should be passed to the template + * @param array $additional_table_vars Any additional variables that should be passed to the table creation + */ + protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = [], array $additional_table_vars = []): Response + { + $this->denyAccessUnlessGranted('@assemblies.read'); + + $formRequest = clone $request; + $formRequest->setMethod('GET'); + $filter = new AssemblyFilter($this->nodesListBuilder); + if($filter_changer !== null){ + $filter_changer($filter); + } + + $filterForm = $this->createForm(AssemblyFilterType::class, $filter, ['method' => 'GET']); + if($form_changer !== null) { + $form_changer($filterForm); + } + + $filterForm->handleRequest($formRequest); + + $table = $this->dataTableFactory->createFromType( + AssemblyDataTable::class, + array_merge(['filter' => $filter], $additional_table_vars), + ['lengthMenu' => AssemblyDataTable::LENGTH_MENU] + ) + ->handleRequest($request); + + if ($table->isCallback()) { + try { + try { + return $table->getResponse(); + } catch (DriverException $driverException) { + if ($driverException->getCode() === 1139) { + //Convert the driver exception to InvalidRegexException so it has the same handler as for SQLite + throw InvalidRegexException::fromDriverException($driverException); + } else { + throw $driverException; + } + } + } catch (InvalidRegexException $exception) { + $errors = $this->translator->trans('assembly.table.invalid_regex').': '.$exception->getReason(); + $request->request->set('order', []); + + return ErrorDataTable::errorTable($this->dataTableFactory, $request, $errors); + } + } + + return $this->render($template, array_merge([ + 'datatable' => $table, + 'filterForm' => $filterForm->createView(), + ], $additonal_template_vars)); + } + #[Route(path: '/{id}/info', name: 'assembly_info', requirements: ['id' => '\d+'])] public function info(Assembly $assembly, Request $request, AssemblyBuildHelper $buildHelper): Response { diff --git a/src/DataTables/AssemblyDataTable.php b/src/DataTables/AssemblyDataTable.php new file mode 100644 index 00000000..f3854ebc --- /dev/null +++ b/src/DataTables/AssemblyDataTable.php @@ -0,0 +1,249 @@ +. + */ + +declare(strict_types=1); + +namespace App\DataTables; + +use App\DataTables\Adapters\TwoStepORMAdapter; +use App\DataTables\Column\IconLinkColumn; +use App\DataTables\Column\LocaleDateTimeColumn; +use App\DataTables\Column\MarkdownColumn; +use App\DataTables\Column\SelectColumn; +use App\DataTables\Filters\AssemblyFilter; +use App\DataTables\Filters\AssemblySearchFilter; +use App\DataTables\Helpers\AssemblyDataTableHelper; +use App\DataTables\Helpers\ColumnSortHelper; +use App\Doctrine\Helpers\FieldHelper; +use App\Entity\AssemblySystem\Assembly; +use App\Services\EntityURLGenerator; +use Doctrine\ORM\AbstractQuery; +use Doctrine\ORM\QueryBuilder; +use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider; +use Omines\DataTablesBundle\Column\TextColumn; +use Omines\DataTablesBundle\DataTable; +use Omines\DataTablesBundle\DataTableTypeInterface; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Translation\TranslatorInterface; + +final class AssemblyDataTable implements DataTableTypeInterface +{ + const LENGTH_MENU = [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]]; + + public function __construct( + private readonly EntityURLGenerator $urlGenerator, + private readonly TranslatorInterface $translator, + private readonly AssemblyDataTableHelper $assemblyDataTableHelper, + private readonly Security $security, + private readonly string $visible_columns, + private readonly ColumnSortHelper $csh, + ) { + } + + public function configureOptions(OptionsResolver $optionsResolver): void + { + $optionsResolver->setDefaults([ + 'filter' => null, + 'search' => null + ]); + + $optionsResolver->setAllowedTypes('filter', [AssemblyFilter::class, 'null']); + $optionsResolver->setAllowedTypes('search', [AssemblySearchFilter::class, 'null']); + } + + public function configure(DataTable $dataTable, array $options): void + { + $resolver = new OptionsResolver(); + $this->configureOptions($resolver); + $options = $resolver->resolve($options); + + $this->csh + ->add('select', SelectColumn::class, visibility_configurable: false) + ->add('picture', TextColumn::class, [ + 'label' => '', + 'className' => 'no-colvis', + 'render' => fn($value, Assembly $context) => $this->assemblyDataTableHelper->renderPicture($context), + ], visibility_configurable: false) + ->add('name', TextColumn::class, [ + 'label' => $this->translator->trans('assembly.table.name'), + 'render' => fn($value, Assembly $context) => $this->assemblyDataTableHelper->renderName($context), + 'orderField' => 'NATSORT(assembly.name)' + ]) + ->add('id', TextColumn::class, [ + 'label' => $this->translator->trans('assembly.table.id'), + ]) + ->add('ipn', TextColumn::class, [ + 'label' => $this->translator->trans('assembly.table.ipn'), + 'orderField' => 'NATSORT(assembly.ipn)' + ]) + ->add('description', MarkdownColumn::class, [ + 'label' => $this->translator->trans('assembly.table.description'), + ]) + ->add('addedDate', LocaleDateTimeColumn::class, [ + 'label' => $this->translator->trans('assembly.table.addedDate'), + ]) + ->add('lastModified', LocaleDateTimeColumn::class, [ + 'label' => $this->translator->trans('assembly.table.lastModified'), + ]); + + //Add a assembly column to list where the assembly is used as referenced assembly as bom-entry, when the user has the permission to see the assemblies + if ($this->security->isGranted('read', Assembly::class)) { + $this->csh->add('referencedAssemblies', TextColumn::class, [ + 'label' => $this->translator->trans('assembly.referencedAssembly.labelp'), + 'render' => function ($value, Assembly $context): string { + $assemblies = $context->getReferencedAssemblies(); + + $max = 5; + $tmp = ""; + + for ($i = 0; $i < min($max, count($assemblies)); $i++) { + $url = $this->urlGenerator->infoURL($assemblies[$i]); + $tmp .= sprintf('%s', $url, htmlspecialchars($assemblies[$i]->getName())); + if ($i < count($assemblies) - 1) { + $tmp .= ", "; + } + } + + if (count($assemblies) > $max) { + $tmp .= ", + ".(count($assemblies) - $max); + } + + return $tmp; + } + ]); + } + + $this->csh + ->add('edit', IconLinkColumn::class, [ + 'label' => $this->translator->trans('assembly.table.edit'), + 'href' => fn($value, Assembly $context) => $this->urlGenerator->editURL($context), + 'disabled' => fn($value, Assembly $context) => !$this->security->isGranted('edit', $context), + 'title' => $this->translator->trans('assembly.table.edit.title'), + ]); + + //Apply the user configured order and visibility and add the columns to the table + $this->csh->applyVisibilityAndConfigureColumns($dataTable, $this->visible_columns, "TABLE_ASSEMBLIES_DEFAULT_COLUMNS"); + + $dataTable->addOrderBy('name') + ->createAdapter(TwoStepORMAdapter::class, [ + 'filter_query' => $this->getFilterQuery(...), + 'detail_query' => $this->getDetailQuery(...), + 'entity' => Assembly::class, + 'hydrate' => AbstractQuery::HYDRATE_OBJECT, + //Use the simple total query, as we just want to get the total number of assemblies without any conditions + //For this the normal query would be pretty slow + 'simple_total_query' => true, + 'criteria' => [ + function (QueryBuilder $builder) use ($options): void { + $this->buildCriteria($builder, $options); + }, + new SearchCriteriaProvider(), + ], + 'query_modifier' => $this->addJoins(...), + ]); + } + + + private function getFilterQuery(QueryBuilder $builder): void + { + /* In the filter query we only select the IDs. The fetching of the full entities is done in the detail query. + * We only need to join the entities here, so we can filter by them. + * The filter conditions are added to this QB in the buildCriteria method. + * + * The amountSum field and the joins are dynamically added by the addJoins method, if the fields are used in the query. + * This improves the performance, as we do not need to join all tables, if we do not need them. + */ + $builder + ->select('assembly.id') + ->from(Assembly::class, 'assembly') + + //The other group by fields, are dynamically added by the addJoins method + ->addGroupBy('assembly'); + } + + private function getDetailQuery(QueryBuilder $builder, array $filter_results): void + { + $ids = array_map(static fn($row) => $row['id'], $filter_results); + + /* + * In this query we take the IDs which were filtered, paginated and sorted in the filter query, and fetch the + * full entities. + * We can do complex fetch joins, as we do not need to filter or sort here (which would kill the performance). + * The only condition should be for the IDs. + * It is important that elements are ordered the same way, as the IDs are passed, or ordering will be wrong. + * + * We do not require the subqueries like amountSum here, as it is not used to render the table (and only for sorting) + */ + $builder + ->select('assembly') + ->addSelect('master_picture_attachment') + ->addSelect('attachments') + ->from(Assembly::class, 'assembly') + ->leftJoin('assembly.master_picture_attachment', 'master_picture_attachment') + ->leftJoin('assembly.attachments', 'attachments') + ->where('assembly.id IN (:ids)') + ->setParameter('ids', $ids) + ->addGroupBy('assembly') + ->addGroupBy('master_picture_attachment') + ->addGroupBy('attachments'); + + //Get the results in the same order as the IDs were passed + FieldHelper::addOrderByFieldParam($builder, 'assembly.id', 'ids'); + } + + /** + * This function is called right before the filter query is executed. + * We use it to dynamically add joins to the query, if the fields are used in the query. + * @param QueryBuilder $builder + * @return QueryBuilder + */ + private function addJoins(QueryBuilder $builder): QueryBuilder + { + //Check if the query contains certain conditions, for which we need to add additional joins + //The join fields get prefixed with an underscore, so we can check if they are used in the query easy without confusing them for a assembly subfield + $dql = $builder->getDQL(); + + if (str_contains($dql, '_master_picture_attachment')) { + $builder->leftJoin('assembly.master_picture_attachment', '_master_picture_attachment'); + $builder->addGroupBy('_master_picture_attachment'); + } + if (str_contains($dql, '_attachments')) { + $builder->leftJoin('assembly.attachments', '_attachments'); + } + + return $builder; + } + + private function buildCriteria(QueryBuilder $builder, array $options): void + { + //Apply the search criterias first + if ($options['search'] instanceof AssemblySearchFilter) { + $search = $options['search']; + $search->apply($builder); + } + + //We do the most stuff here in the filter class + if ($options['filter'] instanceof AssemblyFilter) { + $filter = $options['filter']; + $filter->apply($builder); + } + } +} diff --git a/src/DataTables/Filters/AssemblyFilter.php b/src/DataTables/Filters/AssemblyFilter.php new file mode 100644 index 00000000..d8d07a1e --- /dev/null +++ b/src/DataTables/Filters/AssemblyFilter.php @@ -0,0 +1,68 @@ +. + */ +namespace App\DataTables\Filters; + +use App\DataTables\Filters\Constraints\DateTimeConstraint; +use App\DataTables\Filters\Constraints\EntityConstraint; +use App\DataTables\Filters\Constraints\IntConstraint; +use App\DataTables\Filters\Constraints\TextConstraint; +use App\Entity\Attachments\AttachmentType; +use App\Services\Trees\NodesListBuilder; +use Doctrine\ORM\QueryBuilder; + +class AssemblyFilter implements FilterInterface +{ + + use CompoundFilterTrait; + + public readonly IntConstraint $dbId; + public readonly TextConstraint $ipn; + public readonly TextConstraint $name; + public readonly TextConstraint $description; + public readonly TextConstraint $comment; + public readonly DateTimeConstraint $lastModified; + public readonly DateTimeConstraint $addedDate; + + public readonly IntConstraint $attachmentsCount; + public readonly EntityConstraint $attachmentType; + public readonly TextConstraint $attachmentName; + + public function __construct(NodesListBuilder $nodesListBuilder) + { + $this->name = new TextConstraint('assembly.name'); + $this->description = new TextConstraint('assembly.description'); + $this->comment = new TextConstraint('assembly.comment'); + $this->dbId = new IntConstraint('assembly.id'); + $this->ipn = new TextConstraint('assembly.ipn'); + $this->addedDate = new DateTimeConstraint('assembly.addedDate'); + $this->lastModified = new DateTimeConstraint('assembly.lastModified'); + $this->attachmentsCount = new IntConstraint('COUNT(_attachments)'); + $this->attachmentType = new EntityConstraint($nodesListBuilder, AttachmentType::class, '_attachments.attachment_type'); + $this->attachmentName = new TextConstraint('_attachments.name'); + } + + public function apply(QueryBuilder $queryBuilder): void + { + $this->applyAllChildFilters($queryBuilder); + } +} diff --git a/src/DataTables/Filters/AssemblySearchFilter.php b/src/DataTables/Filters/AssemblySearchFilter.php new file mode 100644 index 00000000..1627cc61 --- /dev/null +++ b/src/DataTables/Filters/AssemblySearchFilter.php @@ -0,0 +1,183 @@ +. + */ +namespace App\DataTables\Filters; +use Doctrine\ORM\QueryBuilder; + +class AssemblySearchFilter implements FilterInterface +{ + + /** @var boolean Whether to use regex for searching */ + protected bool $regex = false; + + /** @var bool Use name field for searching */ + protected bool $name = true; + + /** @var bool Use description for searching */ + protected bool $description = true; + + /** @var bool Use comment field for searching */ + protected bool $comment = true; + + /** @var bool Use ordernr for searching */ + protected bool $ordernr = true; + + /** @var bool Use Internal part number for searching */ + protected bool $ipn = true; + + public function __construct( + /** @var string The string to query for */ + protected string $keyword + ) + { + } + + protected function getFieldsToSearch(): array + { + $fields_to_search = []; + + if($this->name) { + $fields_to_search[] = 'assembly.name'; + } + if($this->description) { + $fields_to_search[] = 'assembly.description'; + } + if ($this->comment) { + $fields_to_search[] = 'assembly.comment'; + } + if ($this->ipn) { + $fields_to_search[] = 'assembly.ipn'; + } + + return $fields_to_search; + } + + public function apply(QueryBuilder $queryBuilder): void + { + $fields_to_search = $this->getFieldsToSearch(); + + //If we have nothing to search for, do nothing + if ($fields_to_search === [] || $this->keyword === '') { + return; + } + + //Convert the fields to search to a list of expressions + $expressions = array_map(function (string $field): string { + if ($this->regex) { + return sprintf("REGEXP(%s, :search_query) = TRUE", $field); + } + + return sprintf("ILIKE(%s, :search_query) = TRUE", $field); + }, $fields_to_search); + + //Add Or concatenation of the expressions to our query + $queryBuilder->andWhere( + $queryBuilder->expr()->orX(...$expressions) + ); + + //For regex, we pass the query as is, for like we add % to the start and end as wildcards + if ($this->regex) { + $queryBuilder->setParameter('search_query', $this->keyword); + } else { + $queryBuilder->setParameter('search_query', '%' . $this->keyword . '%'); + } + } + + public function getKeyword(): string + { + return $this->keyword; + } + + public function setKeyword(string $keyword): AssemblySearchFilter + { + $this->keyword = $keyword; + return $this; + } + + public function isRegex(): bool + { + return $this->regex; + } + + public function setRegex(bool $regex): AssemblySearchFilter + { + $this->regex = $regex; + return $this; + } + + public function isName(): bool + { + return $this->name; + } + + public function setName(bool $name): AssemblySearchFilter + { + $this->name = $name; + return $this; + } + + public function isCategory(): bool + { + return $this->category; + } + + public function setCategory(bool $category): AssemblySearchFilter + { + $this->category = $category; + return $this; + } + + public function isDescription(): bool + { + return $this->description; + } + + public function setDescription(bool $description): AssemblySearchFilter + { + $this->description = $description; + return $this; + } + + public function isIPN(): bool + { + return $this->ipn; + } + + public function setIPN(bool $ipn): AssemblySearchFilter + { + $this->ipn = $ipn; + return $this; + } + + public function isComment(): bool + { + return $this->comment; + } + + public function setComment(bool $comment): AssemblySearchFilter + { + $this->comment = $comment; + return $this; + } + + +} diff --git a/src/DataTables/Helpers/AssemblyDataTableHelper.php b/src/DataTables/Helpers/AssemblyDataTableHelper.php new file mode 100644 index 00000000..dda563ea --- /dev/null +++ b/src/DataTables/Helpers/AssemblyDataTableHelper.php @@ -0,0 +1,77 @@ +. + */ + +namespace App\DataTables\Helpers; + +use App\Entity\AssemblySystem\Assembly; +use App\Entity\Attachments\Attachment; +use App\Services\Attachments\AssemblyPreviewGenerator; +use App\Services\Attachments\AttachmentURLGenerator; +use App\Services\EntityURLGenerator; + +/** + * A helper service which contains common code to render columns for assembly related tables + */ +class AssemblyDataTableHelper +{ + public function __construct( + private readonly EntityURLGenerator $entityURLGenerator, + private readonly AssemblyPreviewGenerator $previewGenerator, + private readonly AttachmentURLGenerator $attachmentURLGenerator + ) { + } + + public function renderName(Assembly $context): string + { + $icon = ''; + + return sprintf( + '%s%s', + $this->entityURLGenerator->infoURL($context), + $icon, + htmlspecialchars($context->getName()) + ); + } + + public function renderPicture(Assembly $context): string + { + $preview_attachment = $this->previewGenerator->getTablePreviewAttachment($context); + if (!$preview_attachment instanceof Attachment) { + return ''; + } + + $title = htmlspecialchars($preview_attachment->getName()); + if ($preview_attachment->getFilename()) { + $title .= ' ('.htmlspecialchars($preview_attachment->getFilename()).')'; + } + + return sprintf( + '%s', + 'Assembly image', + $this->attachmentURLGenerator->getThumbnailURL($preview_attachment), + $this->attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_md'), + 'hoverpic assembly-table-image', + $title + ); + } +} diff --git a/src/DataTables/Helpers/ProjectDataTableHelper.php b/src/DataTables/Helpers/ProjectDataTableHelper.php index 0118d5d5..baa0e24e 100644 --- a/src/DataTables/Helpers/ProjectDataTableHelper.php +++ b/src/DataTables/Helpers/ProjectDataTableHelper.php @@ -27,7 +27,7 @@ use App\Entity\ProjectSystem\Project; use App\Services\EntityURLGenerator; /** - * A helper service which contains common code to render columns for assembly related tables + * A helper service which contains common code to render columns for project related tables */ class ProjectDataTableHelper { diff --git a/src/Form/Filters/AssemblyFilterType.php b/src/Form/Filters/AssemblyFilterType.php new file mode 100644 index 00000000..acfbb1a8 --- /dev/null +++ b/src/Form/Filters/AssemblyFilterType.php @@ -0,0 +1,114 @@ +. + */ +namespace App\Form\Filters; + +use App\DataTables\Filters\AssemblyFilter; +use App\Entity\Attachments\AttachmentType; +use App\Form\Filters\Constraints\DateTimeConstraintType; +use App\Form\Filters\Constraints\NumberConstraintType; +use App\Form\Filters\Constraints\StructuralEntityConstraintType; +use App\Form\Filters\Constraints\TextConstraintType; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ResetType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class AssemblyFilterType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'compound' => true, + 'data_class' => AssemblyFilter::class, + 'csrf_protection' => false, + ]); + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + /* + * Common tab + */ + + $builder->add('name', TextConstraintType::class, [ + 'label' => 'assembly.filter.name', + ]); + + $builder->add('description', TextConstraintType::class, [ + 'label' => 'assembly.filter.description', + ]); + + $builder->add('comment', TextConstraintType::class, [ + 'label' => 'assembly.filter.comment' + ]); + + /* + * Advanced tab + */ + + $builder->add('dbId', NumberConstraintType::class, [ + 'label' => 'assembly.filter.dbId', + 'min' => 1, + 'step' => 1, + ]); + + $builder->add('ipn', TextConstraintType::class, [ + 'label' => 'assembly.filter.ipn', + ]); + + $builder->add('lastModified', DateTimeConstraintType::class, [ + 'label' => 'lastModified' + ]); + + $builder->add('addedDate', DateTimeConstraintType::class, [ + 'label' => 'createdAt' + ]); + + /** + * Attachments count + */ + $builder->add('attachmentsCount', NumberConstraintType::class, [ + 'label' => 'assembly.filter.attachments_count', + 'step' => 1, + 'min' => 0, + ]); + + $builder->add('attachmentType', StructuralEntityConstraintType::class, [ + 'label' => 'attachment.attachment_type', + 'entity_class' => AttachmentType::class + ]); + + $builder->add('attachmentName', TextConstraintType::class, [ + 'label' => 'assembly.filter.attachmentName', + ]); + + $builder->add('submit', SubmitType::class, [ + 'label' => 'filter.submit', + ]); + + $builder->add('discard', ResetType::class, [ + 'label' => 'filter.discard', + ]); + } +} diff --git a/templates/assemblies/lists/_action_bar.html.twig b/templates/assemblies/lists/_action_bar.html.twig new file mode 100644 index 00000000..37289812 --- /dev/null +++ b/templates/assemblies/lists/_action_bar.html.twig @@ -0,0 +1,6 @@ +
+ +
\ No newline at end of file diff --git a/templates/assemblies/lists/_filter.html.twig b/templates/assemblies/lists/_filter.html.twig new file mode 100644 index 00000000..11be7bc2 --- /dev/null +++ b/templates/assemblies/lists/_filter.html.twig @@ -0,0 +1,62 @@ +
+
+ +
+
+
+ + + {{ form_start(filterForm, {"attr": {"data-controller": "helpers--form-cleanup", "data-action": "helpers--form-cleanup#submit"}}) }} + +
+
+ {{ form_row(filterForm.name) }} + {{ form_row(filterForm.description) }} + {{ form_row(filterForm.comment) }} +
+ +
+ {{ form_row(filterForm.dbId) }} + {{ form_row(filterForm.ipn) }} + {{ form_row(filterForm.lastModified) }} + {{ form_row(filterForm.addedDate) }} +
+ +
+ {{ form_row(filterForm.attachmentsCount) }} + {{ form_row(filterForm.attachmentType) }} + {{ form_row(filterForm.attachmentName) }} +
+
+ + {{ form_row(filterForm.submit) }} + {{ form_row(filterForm.discard) }} + +
+
+ +
+
+ + {# Retain the query parameters of the search form if it is existing #} + {% if searchFilter is defined %} + {% for property, value in searchFilter|to_array %} + + {% endfor %} + + {% endif %} + + {{ form_end(filterForm) }} +
+
+
\ No newline at end of file diff --git a/templates/assemblies/lists/all_list.html.twig b/templates/assemblies/lists/all_list.html.twig new file mode 100644 index 00000000..70d75ad4 --- /dev/null +++ b/templates/assemblies/lists/all_list.html.twig @@ -0,0 +1,30 @@ +{% extends "base.html.twig" %} + +{% block title %} + {% trans %}assembly_list.all.title{% endtrans %} +{% endblock %} + +{% block content %} + +
+
+
+ +
+
+
+ +
+
+
+ + {% include "assemblies/lists/_filter.html.twig" %} +
+ + {% include "assemblies/lists/_action_bar.html.twig" with {'url_options': {}} %} + {% include "assemblies/lists/data.html.twig" %} + +{% endblock %} diff --git a/templates/assemblies/lists/data.html.twig b/templates/assemblies/lists/data.html.twig new file mode 100644 index 00000000..69e13e4f --- /dev/null +++ b/templates/assemblies/lists/data.html.twig @@ -0,0 +1,3 @@ +{% import "components/datatables.macro.html.twig" as datatables %} + +{{ datatables.partsDatatableWithForm(datatable) }} diff --git a/translations/messages.cs.xlf b/translations/messages.cs.xlf index 4191cf30..34e21fa3 100644 --- a/translations/messages.cs.xlf +++ b/translations/messages.cs.xlf @@ -14619,5 +14619,131 @@ Vezměte prosím na vědomí, že se nemůžete vydávat za uživatele se zakáz + + + assembly_list.all.title + Všechny sestavy + + + + + assembly.edit.tab.common + Obecné + + + + + assembly.edit.tab.advanced + Pokročilé možnosti + + + + + assembly.edit.tab.attachments + Přílohy + + + + + assembly.filter.dbId + ID databáze + + + + + assembly.filter.ipn + Interní číslo dílu (IPN) + + + + + assembly.filter.name + Název + + + + + assembly.filter.description + Popis + + + + + assembly.filter.comment + Poznámky + + + + + assembly.filter.attachments_count + Počet příloh + + + + + assembly.filter.attachmentName + Název přílohy + + + + + assemblies.create.btn + Vytvořit novou sestavu + + + + + assembly.table.id + ID + + + + + assembly.table.name + Název + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Popis + + + + + assembly.table.addedDate + Přidáno + + + + + assembly.table.lastModified + Naposledy upraveno + + + + + assembly.table.edit + Upravit + + + + + assembly.table.edit.title + Upravit sestavu + + + + + assembly.table.invalid_regex + Neplatný regulární výraz (regex) + + diff --git a/translations/messages.da.xlf b/translations/messages.da.xlf index c68acdd4..5901e9f5 100644 --- a/translations/messages.da.xlf +++ b/translations/messages.da.xlf @@ -13336,5 +13336,131 @@ Bemærk venligst, at du ikke kan kopiere fra deaktiveret bruger. Hvis du prøver + + + assembly_list.all.title + Alle samlinger + + + + + assembly.edit.tab.common + Generelt + + + + + assembly.edit.tab.advanced + Avancerede indstillinger + + + + + assembly.edit.tab.attachments + Vedhæftede filer + + + + + assembly.filter.dbId + Database-ID + + + + + assembly.filter.ipn + Internt delnummer (IPN) + + + + + assembly.filter.name + Navn + + + + + assembly.filter.description + Beskrivelse + + + + + assembly.filter.comment + Kommentarer + + + + + assembly.filter.attachments_count + Antal vedhæftninger + + + + + assembly.filter.attachmentName + Vedhæftningens navn + + + + + assemblies.create.btn + Opret ny samling + + + + + assembly.table.id + ID + + + + + assembly.table.name + Navn + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Beskrivelse + + + + + assembly.table.addedDate + Tilføjet + + + + + assembly.table.lastModified + Sidst ændret + + + + + assembly.table.edit + Rediger + + + + + assembly.table.edit.title + Rediger samling + + + + + assembly.table.invalid_regex + Ugyldigt regulært udtryk (regex) + + diff --git a/translations/messages.de.xlf b/translations/messages.de.xlf index ff775b49..10ca553d 100644 --- a/translations/messages.de.xlf +++ b/translations/messages.de.xlf @@ -15329,5 +15329,131 @@ Bitte beachten Sie, dass Sie sich nicht als deaktivierter Benutzer ausgeben kön Maximale Anzahl von Zuordnungen erreicht + + + assembly_list.all.title + Alle Baugruppen + + + + + assembly.edit.tab.common + Allgemein + + + + + assembly.edit.tab.advanced + Erweiterte Optionen + + + + + assembly.edit.tab.attachments + Dateianhänge + + + + + assembly.filter.dbId + Datenbank ID + + + + + assembly.filter.ipn + Internal Part Number (IPN) + + + + + assembly.filter.name + Name + + + + + assembly.filter.description + Beschreibung + + + + + assembly.filter.comment + Notizen + + + + + assembly.filter.attachments_count + Anzahl der Anhänge + + + + + assembly.filter.attachmentName + Name des Anhangs + + + + + assemblies.create.btn + Neue Baugruppe anlegen + + + + + assembly.table.id + ID + + + + + assembly.table.name + Name + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Beschreibung + + + + + assembly.table.addedDate + Hinzugefügt + + + + + assembly.table.lastModified + Zuletzt bearbeitet + + + + + assembly.table.edit + Ändern + + + + + assembly.table.edit.title + Baugruppe ändern + + + + + assembly.table.invalid_regex + Ungültiger regulärer Ausdruck (regex) + + diff --git a/translations/messages.el.xlf b/translations/messages.el.xlf index 97b27f53..fd511453 100644 --- a/translations/messages.el.xlf +++ b/translations/messages.el.xlf @@ -2344,5 +2344,131 @@ + + + assembly_list.all.title + Όλες οι συναρμολογήσεις + + + + + assembly.edit.tab.common + Γενικά + + + + + assembly.edit.tab.advanced + Προηγμένες επιλογές + + + + + assembly.edit.tab.attachments + Συνημμένα + + + + + assembly.filter.dbId + Αναγνωριστικό βάσης δεδομένων + + + + + assembly.filter.ipn + Εσωτερικός αριθμός εξαρτήματος (IPN) + + + + + assembly.filter.name + Όνομα + + + + + assembly.filter.description + Περιγραφή + + + + + assembly.filter.comment + Σχόλια + + + + + assembly.filter.attachments_count + Αριθμός συνημμένων + + + + + assembly.filter.attachmentName + Όνομα συνημμένου + + + + + assemblies.create.btn + Δημιουργία νέας συναρμολόγησης + + + + + assembly.table.id + Αναγνωριστικό + + + + + assembly.table.name + Όνομα + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Περιγραφή + + + + + assembly.table.addedDate + Προστέθηκε + + + + + assembly.table.lastModified + Τελευταία επεξεργασία + + + + + assembly.table.edit + Επεξεργασία + + + + + assembly.table.edit.title + Επεξεργασία συναρμολόγησης + + + + + assembly.table.invalid_regex + Μη έγκυρη κανονική έκφραση (regex) + + diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 0138aabb..117c263a 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -15330,5 +15330,131 @@ Please note, that you can not impersonate a disabled user. If you try you will g Maximum number of mappings reached + + + assembly_list.all.title + All assemblies + + + + + assembly.edit.tab.common + General + + + + + assembly.edit.tab.advanced + Advanced options + + + + + assembly.edit.tab.attachments + Attachments + + + + + assembly.filter.dbId + Database ID + + + + + assembly.filter.ipn + Internal Part Number (IPN) + + + + + assembly.filter.name + Name + + + + + assembly.filter.description + Description + + + + + assembly.filter.comment + Comments + + + + + assembly.filter.attachments_count + Number of attachments + + + + + assembly.filter.attachmentName + Attachment name + + + + + assemblies.create.btn + Create new assembly + + + + + assembly.table.id + ID + + + + + assembly.table.name + Name + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Description + + + + + assembly.table.addedDate + Added + + + + + assembly.table.lastModified + Last modified + + + + + assembly.table.edit + Edit + + + + + assembly.table.edit.title + Edit assembly + + + + + assembly.table.invalid_regex + Invalid regular expression (regex) + + diff --git a/translations/messages.es.xlf b/translations/messages.es.xlf index d4ece66e..5854628c 100644 --- a/translations/messages.es.xlf +++ b/translations/messages.es.xlf @@ -13508,5 +13508,131 @@ Por favor ten en cuenta que no puedes personificar a un usuario deshabilitado. S + + + assembly_list.all.title + Todas las ensamblajes + + + + + assembly.edit.tab.common + General + + + + + assembly.edit.tab.advanced + Opciones avanzadas + + + + + assembly.edit.tab.attachments + Archivos adjuntos + + + + + assembly.filter.dbId + ID de la base de datos + + + + + assembly.filter.ipn + Número interno de pieza (IPN) + + + + + assembly.filter.name + Nombre + + + + + assembly.filter.description + Descripción + + + + + assembly.filter.comment + Comentarios + + + + + assembly.filter.attachments_count + Cantidad de adjuntos + + + + + assembly.filter.attachmentName + Nombre del adjunto + + + + + assemblies.create.btn + Crear una nueva ensamblaje + + + + + assembly.table.id + ID + + + + + assembly.table.name + Nombre + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Descripción + + + + + assembly.table.addedDate + Añadido + + + + + assembly.table.lastModified + Última modificación + + + + + assembly.table.edit + Editar + + + + + assembly.table.edit.title + Editar ensamblaje + + + + + assembly.table.invalid_regex + Expresión regular no válida (regex) + + diff --git a/translations/messages.fr.xlf b/translations/messages.fr.xlf index a75db3a3..2b4341ca 100644 --- a/translations/messages.fr.xlf +++ b/translations/messages.fr.xlf @@ -9918,5 +9918,131 @@ exemple de ville + + + assembly_list.all.title + Toutes les assemblages + + + + + assembly.edit.tab.common + Général + + + + + assembly.edit.tab.advanced + Options avancées + + + + + assembly.edit.tab.attachments + Pièces jointes + + + + + assembly.filter.dbId + ID de la base de données + + + + + assembly.filter.ipn + Numéro de pièce interne (IPN) + + + + + assembly.filter.name + Nom + + + + + assembly.filter.description + Description + + + + + assembly.filter.comment + Commentaires + + + + + assembly.filter.attachments_count + Nombre de pièces jointes + + + + + assembly.filter.attachmentName + Nom de la pièce jointe + + + + + assemblies.create.btn + Créer un nouvel assemblage + + + + + assembly.table.id + ID + + + + + assembly.table.name + Nom + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Description + + + + + assembly.table.addedDate + Ajouté + + + + + assembly.table.lastModified + Dernière modification + + + + + assembly.table.edit + Modifier + + + + + assembly.table.edit.title + Modifier l'assemblage + + + + + assembly.table.invalid_regex + Expression régulière invalide (regex) + + diff --git a/translations/messages.it.xlf b/translations/messages.it.xlf index 12e20ca2..c727eef8 100644 --- a/translations/messages.it.xlf +++ b/translations/messages.it.xlf @@ -13510,5 +13510,131 @@ Notare che non è possibile impersonare un utente disattivato. Quando si prova a Questo componente contiene più di uno stock. Cambia manualmente la posizione per selezionare quale stock scegliere. + + + assembly_list.all.title + Tutti gli assiemi + + + + + assembly.edit.tab.common + Generale + + + + + assembly.edit.tab.advanced + Opzioni avanzate + + + + + assembly.edit.tab.attachments + Allegati + + + + + assembly.filter.dbId + ID del database + + + + + assembly.filter.ipn + Numero interno di parte (IPN) + + + + + assembly.filter.name + Nome + + + + + assembly.filter.description + Descrizione + + + + + assembly.filter.comment + Commenti + + + + + assembly.filter.attachments_count + Numero di allegati + + + + + assembly.filter.attachmentName + Nome dell'allegato + + + + + assemblies.create.btn + Crea un nuovo assieme + + + + + assembly.table.id + ID + + + + + assembly.table.name + Nome + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Descrizione + + + + + assembly.table.addedDate + Aggiunto + + + + + assembly.table.lastModified + Ultima modifica + + + + + assembly.table.edit + Modifica + + + + + assembly.table.edit.title + Modifica l'assieme + + + + + assembly.table.invalid_regex + Espressione regolare non valida (regex) + + diff --git a/translations/messages.ja.xlf b/translations/messages.ja.xlf index b7be7490..157b1cf2 100644 --- a/translations/messages.ja.xlf +++ b/translations/messages.ja.xlf @@ -9631,5 +9631,131 @@ Exampletown + + + assembly_list.all.title + すべてのアセンブリ + + + + + assembly.edit.tab.common + 一般 + + + + + assembly.edit.tab.advanced + 詳細オプション + + + + + assembly.edit.tab.attachments + 添付ファイル + + + + + assembly.filter.dbId + データベースID + + + + + assembly.filter.ipn + 内部部品番号(IPN) + + + + + assembly.filter.name + 名前 + + + + + assembly.filter.description + 説明 + + + + + assembly.filter.comment + コメント + + + + + assembly.filter.attachments_count + 添付ファイルの数 + + + + + assembly.filter.attachmentName + 添付ファイル名 + + + + + assemblies.create.btn + 新しいアセンブリを作成 + + + + + assembly.table.id + ID + + + + + assembly.table.name + 名前 + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + 説明 + + + + + assembly.table.addedDate + 追加日 + + + + + assembly.table.lastModified + 最終変更 + + + + + assembly.table.edit + 編集 + + + + + assembly.table.edit.title + アセンブリを編集 + + + + + assembly.table.invalid_regex + 無効な正規表現(regex) + + diff --git a/translations/messages.nl.xlf b/translations/messages.nl.xlf index fe17c3ac..44a48dcb 100644 --- a/translations/messages.nl.xlf +++ b/translations/messages.nl.xlf @@ -1569,5 +1569,131 @@ + + + assembly_list.all.title + Alle assemblages + + + + + assembly.edit.tab.common + Algemeen + + + + + assembly.edit.tab.advanced + Geavanceerde opties + + + + + assembly.edit.tab.attachments + Bijlagen + + + + + assembly.filter.dbId + Database-ID + + + + + assembly.filter.ipn + Intern partnummer (IPN) + + + + + assembly.filter.name + Naam + + + + + assembly.filter.description + Beschrijving + + + + + assembly.filter.comment + Opmerkingen + + + + + assembly.filter.attachments_count + Aantal bijlagen + + + + + assembly.filter.attachmentName + Naam van de bijlage + + + + + assemblies.create.btn + Nieuwe assemblage aanmaken + + + + + assembly.table.id + ID + + + + + assembly.table.name + Naam + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Beschrijving + + + + + assembly.table.addedDate + Toegevoegd + + + + + assembly.table.lastModified + Laatst gewijzigd + + + + + assembly.table.edit + Bewerken + + + + + assembly.table.edit.title + Assemblage bewerken + + + + + assembly.table.invalid_regex + Ongeldige reguliere expressie (regex) + + diff --git a/translations/messages.pl.xlf b/translations/messages.pl.xlf index 73b6e4fc..171c585d 100644 --- a/translations/messages.pl.xlf +++ b/translations/messages.pl.xlf @@ -13363,5 +13363,131 @@ Należy pamiętać, że nie możesz udawać nieaktywnych użytkowników. Jeśli + + + assembly_list.all.title + Wszystkie zespoły + + + + + assembly.edit.tab.common + Ogólne + + + + + assembly.edit.tab.advanced + Zaawansowane + + + + + assembly.edit.tab.attachments + Załączniki + + + + + assembly.filter.dbId + ID bazy danych + + + + + assembly.filter.ipn + Wewnętrzny numer części (IPN) + + + + + assembly.filter.name + Nazwa + + + + + assembly.filter.description + Opis + + + + + assembly.filter.comment + Komentarze + + + + + assembly.filter.attachments_count + Liczba załączników + + + + + assembly.filter.attachmentName + Nazwa załącznika + + + + + assemblies.create.btn + Utwórz nowy zespół + + + + + assembly.table.id + ID + + + + + assembly.table.name + Nazwa + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Opis + + + + + assembly.table.addedDate + Dodano + + + + + assembly.table.lastModified + Ostatnia modyfikacja + + + + + assembly.table.edit + Edytuj + + + + + assembly.table.edit.title + Edytuj zespół + + + + + assembly.table.invalid_regex + Nieprawidłowe wyrażenie regularne (regex) + + diff --git a/translations/messages.ru.xlf b/translations/messages.ru.xlf index 751ff35f..7bd0f3bc 100644 --- a/translations/messages.ru.xlf +++ b/translations/messages.ru.xlf @@ -13463,5 +13463,131 @@ + + + assembly_list.all.title + Все сборки + + + + + assembly.edit.tab.common + Общие + + + + + assembly.edit.tab.advanced + Дополнительные параметры + + + + + assembly.edit.tab.attachments + Вложения + + + + + assembly.filter.dbId + ID базы данных + + + + + assembly.filter.ipn + Внутренний номер детали (IPN) + + + + + assembly.filter.name + Название + + + + + assembly.filter.description + Описание + + + + + assembly.filter.comment + Комментарии + + + + + assembly.filter.attachments_count + Количество вложений + + + + + assembly.filter.attachmentName + Имя вложения + + + + + assemblies.create.btn + Создать новую сборку + + + + + assembly.table.id + ID + + + + + assembly.table.name + Название + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + Описание + + + + + assembly.table.addedDate + Добавлено + + + + + assembly.table.lastModified + Последнее изменение + + + + + assembly.table.edit + Редактировать + + + + + assembly.table.edit.title + Редактировать сборку + + + + + assembly.table.invalid_regex + Неверное регулярное выражение (regex) + + diff --git a/translations/messages.zh.xlf b/translations/messages.zh.xlf index 396feae3..9e8d8aa1 100644 --- a/translations/messages.zh.xlf +++ b/translations/messages.zh.xlf @@ -13348,5 +13348,131 @@ Element 3 + + + assembly_list.all.title + 所有组件 + + + + + assembly.edit.tab.common + 通用 + + + + + assembly.edit.tab.advanced + 高级选项 + + + + + assembly.edit.tab.attachments + 附件 + + + + + assembly.filter.dbId + 数据库ID + + + + + assembly.filter.ipn + 内部零件编号(IPN) + + + + + assembly.filter.name + 名称 + + + + + assembly.filter.description + 描述 + + + + + assembly.filter.comment + 评论 + + + + + assembly.filter.attachments_count + 附件数量 + + + + + assembly.filter.attachmentName + 附件名称 + + + + + assemblies.create.btn + 创建新组件 + + + + + assembly.table.id + ID + + + + + assembly.table.name + 名称 + + + + + assembly.table.ipn + IPN + + + + + assembly.table.description + 描述 + + + + + assembly.table.addedDate + 添加日期 + + + + + assembly.table.lastModified + 最后修改 + + + + + assembly.table.edit + 编辑 + + + + + assembly.table.edit.title + 编辑组件 + + + + + assembly.table.invalid_regex + 无效的正则表达式(regex) + +