mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Added simple info page for projects
This commit is contained in:
parent
855b3070bb
commit
d5b1c6be0a
12 changed files with 647 additions and 76 deletions
61
src/Controller/ProjectController.php
Normal file
61
src/Controller/ProjectController.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\DataTables\ProjectBomEntriesDataTable;
|
||||||
|
use App\Entity\ProjectSystem\Project;
|
||||||
|
use Omines\DataTablesBundle\DataTableFactory;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/project")
|
||||||
|
*/
|
||||||
|
class ProjectController extends AbstractController
|
||||||
|
{
|
||||||
|
private DataTableFactory $dataTableFactory;
|
||||||
|
|
||||||
|
public function __construct(DataTableFactory $dataTableFactory)
|
||||||
|
{
|
||||||
|
$this->dataTableFactory = $dataTableFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/{id}", name="project_info")
|
||||||
|
*/
|
||||||
|
public function info(Project $project, Request $request)
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('read', $project);
|
||||||
|
|
||||||
|
$table = $this->dataTableFactory->createFromType(ProjectBomEntriesDataTable::class, ['project' => $project])
|
||||||
|
->handleRequest($request);
|
||||||
|
|
||||||
|
if ($table->isCallback()) {
|
||||||
|
return $table->getResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('Projects/info.html.twig', [
|
||||||
|
'datatable' => $table,
|
||||||
|
'project' => $project,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,8 +65,8 @@ class EntityColumn extends AbstractColumn
|
||||||
});
|
});
|
||||||
|
|
||||||
$resolver->setDefault('render', function (Options $options) {
|
$resolver->setDefault('render', function (Options $options) {
|
||||||
return function ($value, Part $context) use ($options) {
|
return function ($value, $context) use ($options) {
|
||||||
/** @var AbstractDBElement|null $entity */
|
/** @var AbstractNamedDBElement|null $entity */
|
||||||
$entity = $this->accessor->getValue($context, $options['property']);
|
$entity = $this->accessor->getValue($context, $options['property']);
|
||||||
|
|
||||||
if (null !== $entity) {
|
if (null !== $entity) {
|
||||||
|
@ -74,7 +74,7 @@ class EntityColumn extends AbstractColumn
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<a href="%s">%s</a>',
|
'<a href="%s">%s</a>',
|
||||||
$this->urlGenerator->listPartsURL($entity),
|
$this->urlGenerator->listPartsURL($entity),
|
||||||
$value
|
$entity->getName()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
91
src/DataTables/Helpers/PartDataTableHelper.php
Normal file
91
src/DataTables/Helpers/PartDataTableHelper.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataTables\Helpers;
|
||||||
|
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Services\Attachments\AttachmentURLGenerator;
|
||||||
|
use App\Services\Attachments\PartPreviewGenerator;
|
||||||
|
use App\Services\EntityURLGenerator;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper service which contains common code to render columns for part related tables
|
||||||
|
*/
|
||||||
|
class PartDataTableHelper
|
||||||
|
{
|
||||||
|
private PartPreviewGenerator $previewGenerator;
|
||||||
|
private AttachmentURLGenerator $attachmentURLGenerator;
|
||||||
|
|
||||||
|
private TranslatorInterface $translator;
|
||||||
|
private EntityURLGenerator $entityURLGenerator;
|
||||||
|
|
||||||
|
public function __construct(PartPreviewGenerator $previewGenerator, AttachmentURLGenerator $attachmentURLGenerator,
|
||||||
|
EntityURLGenerator $entityURLGenerator, TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->previewGenerator = $previewGenerator;
|
||||||
|
$this->attachmentURLGenerator = $attachmentURLGenerator;
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->entityURLGenerator = $entityURLGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderName(Part $context): string
|
||||||
|
{
|
||||||
|
$icon = '';
|
||||||
|
|
||||||
|
//Depending on the part status we show a different icon (the later conditions have higher priority)
|
||||||
|
if ($context->isFavorite()) {
|
||||||
|
$icon = sprintf('<i class="fa-solid fa-star fa-fw me-1" title="%s"></i>', $this->translator->trans('part.favorite.badge'));
|
||||||
|
}
|
||||||
|
if ($context->isNeedsReview()) {
|
||||||
|
$icon = sprintf('<i class="fa-solid fa-ambulance fa-fw me-1" title="%s"></i>', $this->translator->trans('part.needs_review.badge'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s">%s%s</a>',
|
||||||
|
$this->entityURLGenerator->infoURL($context),
|
||||||
|
$icon,
|
||||||
|
htmlentities($context->getName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderPicture(Part $context): string
|
||||||
|
{
|
||||||
|
$preview_attachment = $this->previewGenerator->getTablePreviewAttachment($context);
|
||||||
|
if (null === $preview_attachment) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$title = htmlspecialchars($preview_attachment->getName());
|
||||||
|
if ($preview_attachment->getFilename()) {
|
||||||
|
$title .= ' ('.htmlspecialchars($preview_attachment->getFilename()).')';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<img alt="%s" src="%s" data-thumbnail="%s" class="%s" data-title="%s" data-controller="elements--hoverpic">',
|
||||||
|
'Part image',
|
||||||
|
$this->attachmentURLGenerator->getThumbnailURL($preview_attachment),
|
||||||
|
$this->attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_md'),
|
||||||
|
'img-fluid hoverpic',
|
||||||
|
$title
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ use App\DataTables\Column\SIUnitNumberColumn;
|
||||||
use App\DataTables\Column\TagsColumn;
|
use App\DataTables\Column\TagsColumn;
|
||||||
use App\DataTables\Filters\PartFilter;
|
use App\DataTables\Filters\PartFilter;
|
||||||
use App\DataTables\Filters\PartSearchFilter;
|
use App\DataTables\Filters\PartSearchFilter;
|
||||||
|
use App\DataTables\Helpers\PartDataTableHelper;
|
||||||
use App\Entity\Parts\Category;
|
use App\Entity\Parts\Category;
|
||||||
use App\Entity\Parts\Footprint;
|
use App\Entity\Parts\Footprint;
|
||||||
use App\Entity\Parts\Manufacturer;
|
use App\Entity\Parts\Manufacturer;
|
||||||
|
@ -63,26 +64,27 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||||
private TranslatorInterface $translator;
|
private TranslatorInterface $translator;
|
||||||
private NodesListBuilder $treeBuilder;
|
private NodesListBuilder $treeBuilder;
|
||||||
private AmountFormatter $amountFormatter;
|
private AmountFormatter $amountFormatter;
|
||||||
private PartPreviewGenerator $previewGenerator;
|
|
||||||
private AttachmentURLGenerator $attachmentURLGenerator;
|
private AttachmentURLGenerator $attachmentURLGenerator;
|
||||||
private Security $security;
|
private Security $security;
|
||||||
|
|
||||||
|
private PartDataTableHelper $partDataTableHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var EntityURLGenerator
|
* @var EntityURLGenerator
|
||||||
*/
|
*/
|
||||||
private $urlGenerator;
|
private $urlGenerator;
|
||||||
|
|
||||||
public function __construct(EntityURLGenerator $urlGenerator, TranslatorInterface $translator,
|
public function __construct(EntityURLGenerator $urlGenerator, TranslatorInterface $translator,
|
||||||
NodesListBuilder $treeBuilder, AmountFormatter $amountFormatter,
|
NodesListBuilder $treeBuilder, AmountFormatter $amountFormatter,PartDataTableHelper $partDataTableHelper,
|
||||||
PartPreviewGenerator $previewGenerator, AttachmentURLGenerator $attachmentURLGenerator, Security $security)
|
AttachmentURLGenerator $attachmentURLGenerator, Security $security)
|
||||||
{
|
{
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->translator = $translator;
|
$this->translator = $translator;
|
||||||
$this->treeBuilder = $treeBuilder;
|
$this->treeBuilder = $treeBuilder;
|
||||||
$this->amountFormatter = $amountFormatter;
|
$this->amountFormatter = $amountFormatter;
|
||||||
$this->previewGenerator = $previewGenerator;
|
|
||||||
$this->attachmentURLGenerator = $attachmentURLGenerator;
|
$this->attachmentURLGenerator = $attachmentURLGenerator;
|
||||||
$this->security = $security;
|
$this->security = $security;
|
||||||
|
$this->partDataTableHelper = $partDataTableHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureOptions(OptionsResolver $optionsResolver): void
|
public function configureOptions(OptionsResolver $optionsResolver): void
|
||||||
|
@ -122,46 +124,13 @@ final class PartsDataTable implements DataTableTypeInterface
|
||||||
'label' => '',
|
'label' => '',
|
||||||
'className' => 'no-colvis',
|
'className' => 'no-colvis',
|
||||||
'render' => function ($value, Part $context) {
|
'render' => function ($value, Part $context) {
|
||||||
$preview_attachment = $this->previewGenerator->getTablePreviewAttachment($context);
|
return $this->partDataTableHelper->renderPicture($context);
|
||||||
if (null === $preview_attachment) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$title = htmlspecialchars($preview_attachment->getName());
|
|
||||||
if ($preview_attachment->getFilename()) {
|
|
||||||
$title .= ' ('.htmlspecialchars($preview_attachment->getFilename()).')';
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf(
|
|
||||||
'<img alt="%s" src="%s" data-thumbnail="%s" class="%s" data-title="%s" data-controller="elements--hoverpic">',
|
|
||||||
'Part image',
|
|
||||||
$this->attachmentURLGenerator->getThumbnailURL($preview_attachment),
|
|
||||||
$this->attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_md'),
|
|
||||||
'img-fluid hoverpic',
|
|
||||||
$title
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
->add('name', TextColumn::class, [
|
->add('name', TextColumn::class, [
|
||||||
'label' => $this->translator->trans('part.table.name'),
|
'label' => $this->translator->trans('part.table.name'),
|
||||||
'render' => function ($value, Part $context) {
|
'render' => function ($value, Part $context) {
|
||||||
$icon = '';
|
return $this->partDataTableHelper->renderName($context);
|
||||||
|
|
||||||
//Depending on the part status we show a different icon (the later conditions have higher priority)
|
|
||||||
if ($context->isFavorite()) {
|
|
||||||
$icon = sprintf('<i class="fa-solid fa-star fa-fw me-1" title="%s"></i>', $this->translator->trans('part.favorite.badge'));
|
|
||||||
}
|
|
||||||
if ($context->isNeedsReview()) {
|
|
||||||
$icon = sprintf('<i class="fa-solid fa-ambulance fa-fw me-1" title="%s"></i>', $this->translator->trans('part.needs_review.badge'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return sprintf(
|
|
||||||
'<a href="%s">%s%s</a>',
|
|
||||||
$this->urlGenerator->infoURL($context),
|
|
||||||
$icon,
|
|
||||||
htmlentities($context->getName())
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
->add('id', TextColumn::class, [
|
->add('id', TextColumn::class, [
|
||||||
|
|
178
src/DataTables/ProjectBomEntriesDataTable.php
Normal file
178
src/DataTables/ProjectBomEntriesDataTable.php
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataTables;
|
||||||
|
|
||||||
|
use App\DataTables\Column\EntityColumn;
|
||||||
|
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||||
|
use App\DataTables\Column\MarkdownColumn;
|
||||||
|
use App\DataTables\Column\SelectColumn;
|
||||||
|
use App\DataTables\Helpers\PartDataTableHelper;
|
||||||
|
use App\Entity\Attachments\Attachment;
|
||||||
|
use App\Entity\Parts\Part;
|
||||||
|
use App\Entity\ProjectSystem\ProjectBOMEntry;
|
||||||
|
use App\Services\EntityURLGenerator;
|
||||||
|
use App\Services\Formatters\AmountFormatter;
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
|
||||||
|
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
|
||||||
|
use Omines\DataTablesBundle\Column\TextColumn;
|
||||||
|
use Omines\DataTablesBundle\DataTable;
|
||||||
|
use Omines\DataTablesBundle\DataTableTypeInterface;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
class ProjectBomEntriesDataTable implements DataTableTypeInterface
|
||||||
|
{
|
||||||
|
protected TranslatorInterface $translator;
|
||||||
|
protected PartDataTableHelper $partDataTableHelper;
|
||||||
|
protected EntityURLGenerator $entityURLGenerator;
|
||||||
|
protected AmountFormatter $amountFormatter;
|
||||||
|
|
||||||
|
public function __construct(TranslatorInterface $translator, PartDataTableHelper $partDataTableHelper,
|
||||||
|
EntityURLGenerator $entityURLGenerator, AmountFormatter $amountFormatter)
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
$this->partDataTableHelper = $partDataTableHelper;
|
||||||
|
$this->entityURLGenerator = $entityURLGenerator;
|
||||||
|
$this->amountFormatter = $amountFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function configure(DataTable $dataTable, array $options)
|
||||||
|
{
|
||||||
|
$dataTable
|
||||||
|
//->add('select', SelectColumn::class)
|
||||||
|
->add('picture', TextColumn::class, [
|
||||||
|
'label' => '',
|
||||||
|
'className' => 'no-colvis',
|
||||||
|
'render' => function ($value, ProjectBOMEntry $context) {
|
||||||
|
if($context->getPart() === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return $this->partDataTableHelper->renderPicture($context->getPart());
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('id', TextColumn::class, [
|
||||||
|
'label' => $this->translator->trans('part.table.id'),
|
||||||
|
'visible' => false,
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('quantity', TextColumn::class, [
|
||||||
|
'label' => $this->translator->trans('project.bom.quantity'),
|
||||||
|
'className' => 'text-center',
|
||||||
|
'render' => function ($value, ProjectBOMEntry $context) {
|
||||||
|
//If we have a non-part entry, only show the rounded quantity
|
||||||
|
if ($context->getPart() === null) {
|
||||||
|
return round($context->getQuantity());
|
||||||
|
}
|
||||||
|
//Otherwise use the unit of the part to format the quantity
|
||||||
|
return $this->amountFormatter->format($context->getQuantity(), $context->getPart()->getPartUnit());
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('name', TextColumn::class, [
|
||||||
|
'label' => $this->translator->trans('part.table.name'),
|
||||||
|
'orderable' => false,
|
||||||
|
'render' => function ($value, ProjectBOMEntry $context) {
|
||||||
|
if($context->getPart() === null) {
|
||||||
|
return $context->getName();
|
||||||
|
}
|
||||||
|
if($context->getPart() !== null) {
|
||||||
|
$tmp = $this->partDataTableHelper->renderName($context->getPart());
|
||||||
|
if(!empty($context->getName())) {
|
||||||
|
$tmp .= '<br><b>'.htmlspecialchars($context->getName()).'</b>';
|
||||||
|
}
|
||||||
|
return $tmp;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('description', MarkdownColumn::class, [
|
||||||
|
'label' => $this->translator->trans('part.table.description'),
|
||||||
|
'data' => function (ProjectBOMEntry $context) {
|
||||||
|
if($context->getPart() !== null) {
|
||||||
|
return $context->getPart()->getDescription();
|
||||||
|
}
|
||||||
|
//For non-part BOM entries show the comment field
|
||||||
|
return $context->getComment();
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
->add('category', EntityColumn::class, [
|
||||||
|
'label' => $this->translator->trans('part.table.category'),
|
||||||
|
'property' => 'part.category',
|
||||||
|
])
|
||||||
|
->add('footprint', EntityColumn::class, [
|
||||||
|
'property' => 'part.footprint',
|
||||||
|
'label' => $this->translator->trans('part.table.footprint'),
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('manufacturer', EntityColumn::class, [
|
||||||
|
'property' => 'part.manufacturer',
|
||||||
|
'label' => $this->translator->trans('part.table.manufacturer'),
|
||||||
|
])
|
||||||
|
|
||||||
|
->add('mountnames', TextColumn::class, [
|
||||||
|
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
->add('addedDate', LocaleDateTimeColumn::class, [
|
||||||
|
'label' => $this->translator->trans('part.table.addedDate'),
|
||||||
|
'visible' => false,
|
||||||
|
])
|
||||||
|
->add('lastModified', LocaleDateTimeColumn::class, [
|
||||||
|
'label' => $this->translator->trans('part.table.lastModified'),
|
||||||
|
'visible' => false,
|
||||||
|
])
|
||||||
|
;
|
||||||
|
|
||||||
|
$dataTable->createAdapter(ORMAdapter::class, [
|
||||||
|
'entity' => Attachment::class,
|
||||||
|
'query' => function (QueryBuilder $builder) use ($options): void {
|
||||||
|
$this->getQuery($builder, $options);
|
||||||
|
},
|
||||||
|
'criteria' => [
|
||||||
|
function (QueryBuilder $builder) use ($options): void {
|
||||||
|
$this->buildCriteria($builder, $options);
|
||||||
|
},
|
||||||
|
new SearchCriteriaProvider(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getQuery(QueryBuilder $builder, array $options): void
|
||||||
|
{
|
||||||
|
$builder->select('bom_entry')
|
||||||
|
->addSelect('part')
|
||||||
|
->from(ProjectBOMEntry::class, 'bom_entry')
|
||||||
|
->leftJoin('bom_entry.part', 'part')
|
||||||
|
->where('bom_entry.project = :project')
|
||||||
|
->setParameter('project', $options['project']);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildCriteria(QueryBuilder $builder, array $options): void
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ class Project extends AbstractStructuralDBElement
|
||||||
protected $parent;
|
protected $parent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="device")
|
* @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="project")
|
||||||
*/
|
*/
|
||||||
protected $bom_entries;
|
protected $bom_entries;
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
* @ORM\ManyToOne(targetEntity="Project", inversedBy="parts")
|
* @ORM\ManyToOne(targetEntity="Project", inversedBy="parts")
|
||||||
* @ORM\JoinColumn(name="id_device", referencedColumnName="id")
|
* @ORM\JoinColumn(name="id_device", referencedColumnName="id")
|
||||||
*/
|
*/
|
||||||
protected ?Project $device = null;
|
protected ?Project $project = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Part|null The part associated with this
|
* @var Part|null The part associated with this
|
||||||
|
@ -76,4 +76,116 @@ class ProjectBOMEntry extends AbstractDBElement
|
||||||
* @ORM\JoinColumn(name="id_part", referencedColumnName="id", nullable=true)
|
* @ORM\JoinColumn(name="id_part", referencedColumnName="id", nullable=true)
|
||||||
*/
|
*/
|
||||||
protected ?Part $part = null;
|
protected ?Part $part = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return float
|
||||||
|
*/
|
||||||
|
public function getQuantity(): float
|
||||||
|
{
|
||||||
|
return $this->quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param float $quantity
|
||||||
|
* @return ProjectBOMEntry
|
||||||
|
*/
|
||||||
|
public function setQuantity(float $quantity): ProjectBOMEntry
|
||||||
|
{
|
||||||
|
$this->quantity = $quantity;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMountnames(): string
|
||||||
|
{
|
||||||
|
return $this->mountnames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $mountnames
|
||||||
|
* @return ProjectBOMEntry
|
||||||
|
*/
|
||||||
|
public function setMountnames(string $mountnames): ProjectBOMEntry
|
||||||
|
{
|
||||||
|
$this->mountnames = $mountnames;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @return ProjectBOMEntry
|
||||||
|
*/
|
||||||
|
public function setName(string $name): ProjectBOMEntry
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getComment(): string
|
||||||
|
{
|
||||||
|
return $this->comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $comment
|
||||||
|
* @return ProjectBOMEntry
|
||||||
|
*/
|
||||||
|
public function setComment(string $comment): ProjectBOMEntry
|
||||||
|
{
|
||||||
|
$this->comment = $comment;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Project|null
|
||||||
|
*/
|
||||||
|
public function getProject(): ?Project
|
||||||
|
{
|
||||||
|
return $this->project;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Project|null $project
|
||||||
|
* @return ProjectBOMEntry
|
||||||
|
*/
|
||||||
|
public function setProject(?Project $project): ProjectBOMEntry
|
||||||
|
{
|
||||||
|
$this->project = $project;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Part|null
|
||||||
|
*/
|
||||||
|
public function getPart(): ?Part
|
||||||
|
{
|
||||||
|
return $this->part;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Part|null $part
|
||||||
|
* @return ProjectBOMEntry
|
||||||
|
*/
|
||||||
|
public function setPart(?Part $part): ProjectBOMEntry
|
||||||
|
{
|
||||||
|
$this->part = $part;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,6 +322,8 @@ class EntityURLGenerator
|
||||||
public function listPartsURL(AbstractDBElement $entity): string
|
public function listPartsURL(AbstractDBElement $entity): string
|
||||||
{
|
{
|
||||||
$map = [
|
$map = [
|
||||||
|
Project::class => 'project_info',
|
||||||
|
|
||||||
Category::class => 'part_list_category',
|
Category::class => 'part_list_category',
|
||||||
Footprint::class => 'part_list_footprint',
|
Footprint::class => 'part_list_footprint',
|
||||||
Manufacturer::class => 'part_list_manufacturer',
|
Manufacturer::class => 'part_list_manufacturer',
|
||||||
|
|
|
@ -110,7 +110,7 @@ class TreeViewGenerator
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($mode === 'devices') {
|
if ($mode === 'devices') {
|
||||||
$href_type = '';
|
$href_type = 'list_parts';
|
||||||
}
|
}
|
||||||
|
|
||||||
$generic = $this->getGenericTree($class, $parent);
|
$generic = $this->getGenericTree($class, $parent);
|
||||||
|
|
133
templates/Projects/_info_card.html.twig
Normal file
133
templates/Projects/_info_card.html.twig
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
{% import "helper.twig" as helper %}
|
||||||
|
{% import "LabelSystem/dropdown_macro.html.twig" as dropdown %}
|
||||||
|
|
||||||
|
{{ helper.breadcrumb_entity_link(project) }}
|
||||||
|
|
||||||
|
<div class="accordion mb-4" id="listAccordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<div class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed py-2" data-bs-toggle="collapse" data-bs-target="#entityInfo" aria-expanded="true">
|
||||||
|
{% if project.masterPictureAttachment is not null and attachment_manager.isFileExisting(project.masterPictureAttachment) %}
|
||||||
|
<img class="hoverpic ms-0 me-1 d-inline" {{ stimulus_controller('elements/hoverpic') }} data-thumbnail="{{ entity_url(project.masterPictureAttachment, 'file_view') }}" src="{{ attachment_thumbnail(project.masterPictureAttachment, 'thumbnail_sm') }}">
|
||||||
|
{% else %}
|
||||||
|
{{ helper.entity_icon(project, "me-1") }}
|
||||||
|
{% endif %}
|
||||||
|
{% trans %}device.label{% endtrans %}: <b>{{ project.name }}</b>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="entityInfo" class="accordion-collapse collapse show" data-bs-parent="#listAccordion">
|
||||||
|
<div class="accordion-body">
|
||||||
|
{% if project.description is not empty %}
|
||||||
|
{{ project.description|format_markdown }}
|
||||||
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
|
||||||
|
<a class="nav-link active" id="v-pills-home-tab" data-bs-toggle="pill" href="#v-pills-home" role="tab" aria-controls="v-pills-home" aria-selected="true">
|
||||||
|
<i class="fas fa-info-circle fa-fw"></i>
|
||||||
|
{% trans %}entity.info.common.tab{% endtrans %}
|
||||||
|
</a>
|
||||||
|
<a class="nav-link" id="v-pills-statistics-tab" data-bs-toggle="pill" href="#v-pills-statistics" role="tab" aria-controls="v-pills-profile" aria-selected="false">
|
||||||
|
<i class="fas fa-chart-pie fa-fw"></i>
|
||||||
|
{% trans %}entity.info.statistics.tab{% endtrans %}
|
||||||
|
</a>
|
||||||
|
{% if project.attachments is not empty %}
|
||||||
|
<a class="nav-link" id="v-pills-attachments-tab" data-bs-toggle="pill" href="#v-pills-attachments" role="tab" aria-controls="v-pills-attachments" aria-selected="false">
|
||||||
|
<i class="fas fa-paperclip fa-fw"></i>
|
||||||
|
{% trans %}entity.info.attachments.tab{% endtrans %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if project.parameters is not empty %}
|
||||||
|
<a class="nav-link" id="v-pills-parameters-tab" data-bs-toggle="pill" href="#v-pills-parameters" role="tab" aria-controls="v-pills-parameters" aria-selected="false">
|
||||||
|
<i class="fas fa-atlas fa-fw"></i>
|
||||||
|
{% trans %}entity.info.parameters.tab{% endtrans %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if project.comment is not empty %}
|
||||||
|
<a class="nav-link" id="v-pills-comment-tab" data-bs-toggle="pill" href="#v-pills-comment" role="tab">
|
||||||
|
<i class="fas fa-comment-alt fa-fw"></i>
|
||||||
|
{% trans %}comment.label{% endtrans %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="tab-content" id="v-pills-tabContent">
|
||||||
|
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel" aria-labelledby="v-pills-home-tab">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-9 form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-4">{% trans %}entity.info.name{% endtrans %}:</label>
|
||||||
|
<span class="col-sm form-control-static">{{ project.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-4">{% trans %}entity.info.parent{% endtrans %}:</label>
|
||||||
|
<span class="col-sm form-control-static">
|
||||||
|
{% if project.parent %}
|
||||||
|
{{ project.parent.fullPath }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
{% block quick_links %}{% endblock %}
|
||||||
|
|
||||||
|
<a class="btn btn-secondary w-100 mb-2" href="{{ entity_url(project, 'edit') }}">
|
||||||
|
<i class="fas fa-edit"></i> {% trans %}entity.edit.btn{% endtrans %}
|
||||||
|
</a>
|
||||||
|
<div class="">
|
||||||
|
<span class="text-muted" title="{% trans %}lastModified{% endtrans %}">
|
||||||
|
<i class="fas fa-history fa-fw"></i> {{ project.lastModified | format_datetime("short") }}
|
||||||
|
</span>
|
||||||
|
<br>
|
||||||
|
<span class="text-muted mt-1" title="{% trans %}createdAt{% endtrans %}">
|
||||||
|
<i class="fas fa-calendar-plus fa-fw"></i> {{ project.addedDate | format_datetime("short") }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="v-pills-statistics" role="tabpanel" aria-labelledby="v-pills-statistics-tab">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-4">{% trans %}entity.info.children_count{% endtrans %}:</label>
|
||||||
|
<span class="col-sm form-control-static">{{ project.children | length }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-4">{% trans %}entity.info.parts_count{% endtrans %}:</label>
|
||||||
|
<span class="col-sm form-control-static">{{ project.bomEntries | length }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if project.attachments is not empty %}
|
||||||
|
<div class="tab-pane fade" id="v-pills-attachments" role="tabpanel" aria-labelledby="v-pills-attachments-tab">
|
||||||
|
{% include "Parts/info/_attachments_info.html.twig" with {"part": project} %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if project.parameters is not empty %}
|
||||||
|
<div class="tab-pane fade" id="v-pills-parameters" role="tabpanel" aria-labelledby="v-pills-parameters-tab">
|
||||||
|
{% for name, parameters in project.groupedParameters %}
|
||||||
|
{% if name is not empty %}<h5 class="mt-1">{{ name }}</h5>{% endif %}
|
||||||
|
{{ helper.parameters_table(project) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if project.comment is not empty %}
|
||||||
|
<div class="tab-pane fade" id="v-pills-comment" role="tabpanel" aria-labelledby="home-tab">
|
||||||
|
<div class="container-fluid mt-2 latex" data-controller="common--latex">
|
||||||
|
{{ project.comment|format_markdown }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
19
templates/Projects/info.html.twig
Normal file
19
templates/Projects/info.html.twig
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends "base.html.twig" %}
|
||||||
|
|
||||||
|
{% import "components/datatables.macro.html.twig" as datatables %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% trans %}parts_list.category.title{% endtrans %} {{ project.name }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% include "Projects/_info_card.html.twig" %}
|
||||||
|
|
||||||
|
{{ datatables.datatable(datatable, 'elements/datatables/datatables', 'projects') }}
|
||||||
|
|
||||||
|
{# {% include "Parts/lists/_action_bar.html.twig" with {'url_options': {'category': entity.iD}} %}
|
||||||
|
|
||||||
|
{% include "Parts/lists/_parts_list.html.twig" %} #}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -8693,7 +8693,7 @@ Element 3</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="RAX6xpX" name="tfa.provider.webauthn_two_factor_provider">
|
<unit id="RAX6xpX" name="tfa.provider.webauthn_two_factor_provider">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>tfa.provider.webauthn_two_factor_provider</source>
|
<source>tfa.provider.webauthn_two_factor_provider</source>
|
||||||
<target>Security key</target>
|
<target>Security key</target>
|
||||||
</segment>
|
</segment>
|
||||||
|
@ -9768,184 +9768,190 @@ Element 3</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="dU7EyhM" name="entity.info.parts_count_recursive">
|
<unit id="dU7EyhM" name="entity.info.parts_count_recursive">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>entity.info.parts_count_recursive</source>
|
<source>entity.info.parts_count_recursive</source>
|
||||||
<target>Number of parts with this element or its subelements</target>
|
<target>Number of parts with this element or its subelements</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="_hKlKv." name="tools.server_infos.title">
|
<unit id="_hKlKv." name="tools.server_infos.title">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>tools.server_infos.title</source>
|
<source>tools.server_infos.title</source>
|
||||||
<target>Server Infos</target>
|
<target>Server Infos</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="NvclBUL" name="permission.preset.read_only">
|
<unit id="NvclBUL" name="permission.preset.read_only">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.read_only</source>
|
<source>permission.preset.read_only</source>
|
||||||
<target>Read-Only</target>
|
<target>Read-Only</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="HD3j3BW" name="permission.preset.read_only.desc">
|
<unit id="HD3j3BW" name="permission.preset.read_only.desc">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.read_only.desc</source>
|
<source>permission.preset.read_only.desc</source>
|
||||||
<target>Only allow read operations on data</target>
|
<target>Only allow read operations on data</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="Ge20aJg" name="permission.preset.all_inherit">
|
<unit id="Ge20aJg" name="permission.preset.all_inherit">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.all_inherit</source>
|
<source>permission.preset.all_inherit</source>
|
||||||
<target>Inherit all</target>
|
<target>Inherit all</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="DJpsLcr" name="permission.preset.all_inherit.desc">
|
<unit id="DJpsLcr" name="permission.preset.all_inherit.desc">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.all_inherit.desc</source>
|
<source>permission.preset.all_inherit.desc</source>
|
||||||
<target>Set all permissions to Inherit</target>
|
<target>Set all permissions to Inherit</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="lzjvvzm" name="permission.preset.all_forbid">
|
<unit id="lzjvvzm" name="permission.preset.all_forbid">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.all_forbid</source>
|
<source>permission.preset.all_forbid</source>
|
||||||
<target>Forbid all</target>
|
<target>Forbid all</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="QqQDTyH" name="permission.preset.all_forbid.desc">
|
<unit id="QqQDTyH" name="permission.preset.all_forbid.desc">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.all_forbid.desc</source>
|
<source>permission.preset.all_forbid.desc</source>
|
||||||
<target>Set all permissions to Forbid</target>
|
<target>Set all permissions to Forbid</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="DV2fh6l" name="permission.preset.all_allow">
|
<unit id="DV2fh6l" name="permission.preset.all_allow">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.all_allow</source>
|
<source>permission.preset.all_allow</source>
|
||||||
<target>Allow all</target>
|
<target>Allow all</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="_m.Pbza" name="permission.preset.all_allow.desc">
|
<unit id="_m.Pbza" name="permission.preset.all_allow.desc">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.all_allow.desc</source>
|
<source>permission.preset.all_allow.desc</source>
|
||||||
<target>Set all permissions to allow</target>
|
<target>Set all permissions to allow</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="VIDdo5K" name="perm.server_infos">
|
<unit id="VIDdo5K" name="perm.server_infos">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>perm.server_infos</source>
|
<source>perm.server_infos</source>
|
||||||
<target>Server infos</target>
|
<target>Server infos</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="d6SOlzR" name="permission.preset.editor">
|
<unit id="d6SOlzR" name="permission.preset.editor">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.editor</source>
|
<source>permission.preset.editor</source>
|
||||||
<target>Editor</target>
|
<target>Editor</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="8KYl_wh" name="permission.preset.editor.desc">
|
<unit id="8KYl_wh" name="permission.preset.editor.desc">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.editor.desc</source>
|
<source>permission.preset.editor.desc</source>
|
||||||
<target>Allow to change parts and data structures</target>
|
<target>Allow to change parts and data structures</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="dYudjp." name="permission.preset.admin">
|
<unit id="dYudjp." name="permission.preset.admin">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.admin</source>
|
<source>permission.preset.admin</source>
|
||||||
<target>Admin</target>
|
<target>Admin</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="0o2M0uj" name="permission.preset.admin.desc">
|
<unit id="0o2M0uj" name="permission.preset.admin.desc">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.admin.desc</source>
|
<source>permission.preset.admin.desc</source>
|
||||||
<target>Allow administrative actions</target>
|
<target>Allow administrative actions</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="SnAIVQf" name="permission.preset.button">
|
<unit id="SnAIVQf" name="permission.preset.button">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>permission.preset.button</source>
|
<source>permission.preset.button</source>
|
||||||
<target>Apply preset</target>
|
<target>Apply preset</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="6q4uHDx" name="perm.attachments.show_private">
|
<unit id="6q4uHDx" name="perm.attachments.show_private">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>perm.attachments.show_private</source>
|
<source>perm.attachments.show_private</source>
|
||||||
<target>Show private attachments</target>
|
<target>Show private attachments</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="NL9t5hy" name="perm.attachments.list_attachments">
|
<unit id="NL9t5hy" name="perm.attachments.list_attachments">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>perm.attachments.list_attachments</source>
|
<source>perm.attachments.list_attachments</source>
|
||||||
<target>Show list of all attachments</target>
|
<target>Show list of all attachments</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="PYh9dNP" name="user.edit.permission_success">
|
<unit id="PYh9dNP" name="user.edit.permission_success">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>user.edit.permission_success</source>
|
<source>user.edit.permission_success</source>
|
||||||
<target>Permission preset applied successfully. Check if the new permissions fit your needs.</target>
|
<target>Permission preset applied successfully. Check if the new permissions fit your needs.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="cP8VNKS" name="perm.group.data">
|
<unit id="cP8VNKS" name="perm.group.data">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>perm.group.data</source>
|
<source>perm.group.data</source>
|
||||||
<target>Data</target>
|
<target>Data</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="AAoGo_X" name="part_list.action.action.group.needs_review">
|
<unit id="AAoGo_X" name="part_list.action.action.group.needs_review">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part_list.action.action.group.needs_review</source>
|
<source>part_list.action.action.group.needs_review</source>
|
||||||
<target>Needs Review</target>
|
<target>Needs Review</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="hcvOTrH" name="part_list.action.action.set_needs_review">
|
<unit id="hcvOTrH" name="part_list.action.action.set_needs_review">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part_list.action.action.set_needs_review</source>
|
<source>part_list.action.action.set_needs_review</source>
|
||||||
<target>Set Needs Review Status</target>
|
<target>Set Needs Review Status</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="E1AQubV" name="part_list.action.action.unset_needs_review">
|
<unit id="E1AQubV" name="part_list.action.action.unset_needs_review">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part_list.action.action.unset_needs_review</source>
|
<source>part_list.action.action.unset_needs_review</source>
|
||||||
<target>Unset Needs Review Status</target>
|
<target>Unset Needs Review Status</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="DNEEkTy" name="part.edit.ipn">
|
<unit id="DNEEkTy" name="part.edit.ipn">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.edit.ipn</source>
|
<source>part.edit.ipn</source>
|
||||||
<target>Internal Part Number (IPN)</target>
|
<target>Internal Part Number (IPN)</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="bT6yxOA" name="part.ipn.not_defined">
|
<unit id="bT6yxOA" name="part.ipn.not_defined">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.ipn.not_defined</source>
|
<source>part.ipn.not_defined</source>
|
||||||
<target>Not defined</target>
|
<target>Not defined</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="SHo2Ejq" name="part.table.ipn">
|
<unit id="SHo2Ejq" name="part.table.ipn">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>part.table.ipn</source>
|
<source>part.table.ipn</source>
|
||||||
<target>IPN</target>
|
<target>IPN</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="1HcqCmo" name="currency.edit.update_rate">
|
<unit id="1HcqCmo" name="currency.edit.update_rate">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>currency.edit.update_rate</source>
|
<source>currency.edit.update_rate</source>
|
||||||
<target>Retrieve exchange rate</target>
|
<target>Retrieve exchange rate</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="jSf6Wmz" name="currency.edit.exchange_rate_update.unsupported_currency">
|
<unit id="jSf6Wmz" name="currency.edit.exchange_rate_update.unsupported_currency">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>currency.edit.exchange_rate_update.unsupported_currency</source>
|
<source>currency.edit.exchange_rate_update.unsupported_currency</source>
|
||||||
<target>The currency is unsupported by the exchange rate provider. Check your exchange rate provider configuration.</target>
|
<target>The currency is unsupported by the exchange rate provider. Check your exchange rate provider configuration.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="D481NZD" name="currency.edit.exchange_rate_update.generic_error">
|
<unit id="D481NZD" name="currency.edit.exchange_rate_update.generic_error">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>currency.edit.exchange_rate_update.generic_error</source>
|
<source>currency.edit.exchange_rate_update.generic_error</source>
|
||||||
<target>Unable to retrieve the exchange rate. Check your exchange rate provider configuration.</target>
|
<target>Unable to retrieve the exchange rate. Check your exchange rate provider configuration.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="E_M7mZ5" name="currency.edit.exchange_rate_updated.success">
|
<unit id="E_M7mZ5" name="currency.edit.exchange_rate_updated.success">
|
||||||
<segment state="translated">
|
<segment>
|
||||||
<source>currency.edit.exchange_rate_updated.success</source>
|
<source>currency.edit.exchange_rate_updated.success</source>
|
||||||
<target>Retrieved the exchange rate successfully.</target>
|
<target>Retrieved the exchange rate successfully.</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
|
<unit id="HbPND5j" name="project.bom.quantity">
|
||||||
|
<segment>
|
||||||
|
<source>project.bom.quantity</source>
|
||||||
|
<target>BOM Qty.</target>
|
||||||
|
</segment>
|
||||||
|
</unit>
|
||||||
</file>
|
</file>
|
||||||
</xliff>
|
</xliff>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue