Merge commit '4895fbc9fed2c7df28729e3b38929fc26f767ae9' into consumable-part

This commit is contained in:
Oliver Walters 2026-07-03 10:37:05 +00:00
commit 86d42c5d30
59 changed files with 1633 additions and 630 deletions

View File

@ -8,7 +8,7 @@ name: Frontend
on:
push:
branches-ignore: ["l10*", "dependabot/*", "backport/*"]
branches-ignore: ["l10*", "dependabot/**", "backport/**"]
pull_request:
branches-ignore: ["l10*"]

View File

@ -7,7 +7,7 @@ name: Import / Export
on:
push:
branches-ignore: ["l10*", "dependabot/*", "backport/*"]
branches-ignore: ["l10*", "dependabot/**", "backport/**"]
pull_request:
branches-ignore: ["l10*"]

View File

@ -4,7 +4,7 @@ name: QC
on:
push:
branches-ignore: ["l10*", "dependabot/*", "backport/*"]
branches-ignore: ["l10*", "dependabot/**", "backport/**"]
pull_request:
branches-ignore: ["l10*"]

View File

@ -53,20 +53,20 @@ To read more about the capabilities of the report templating engine, and how to
## Creating Templates
Report and label templates can be created (and edited) via the [admin interface](../settings/admin.md), under the *Report* section.
Report and label templates are managed from the [Admin Center](../settings/admin.md#admin-center), which provides dedicated panels (under the *Reporting* group) for each template type:
Select the type of template you are wanting to create (a *Report Template* or *Label Template*) and press the *Add* button in the top right corner:
- **Label Templates** - Create and edit [label templates](./labels.md)
- **Report Templates** - Create and edit [report templates](./report.md)
- **Report Snippets** - Manage reusable [snippet](#report-snippets) files
- **Report Assets** - Manage uploaded [asset](#report-assets) files
{{ image("report/report_template_admin.png", "Report template admin") }}
Label and report templates are created and edited using the built-in [template editor](./template_editor.md), which allows templates to be written directly within the browser, with a live preview of the rendered output.
!!! tip "Staff Access Only"
Only users with staff access can upload or edit report template files.
Only users with staff access can create, upload or edit templates, snippets and assets.
!!! info "Editing Reports"
Existing reports can be edited from the admin interface, in the same location as described above. To change the contents of the template, re-upload a template file, to override the existing template data.
!!! tip "Template Editor"
InvenTree also provides a powerful [template editor](./template_editor.md) which allows for the creation and editing of report templates directly within the browser.
!!! info "Backend Admin Interface"
Templates can also be managed at a lower level via the [backend admin interface](../settings/admin.md#backend-admin-interface), under the *Report* section. This is recommended for advanced users only.
### Name and Description
@ -147,7 +147,9 @@ Setting the *Debug Mode* option renders the template as raw HTML instead of PDF,
## Report Assets
User can upload asset files (e.g. images) which can be used when generating reports. For example, you may wish to generate a report with your company logo in the header. Asset files are uploaded via the admin interface.
Users can upload asset files (e.g. images) which can be used when generating reports. For example, you may wish to generate a report with your company logo in the header.
Asset files are managed from the [Admin Center](../settings/admin.md#admin-center), via the *Report Assets* panel. Staff users can upload new asset files, and remove assets which are no longer required.
Asset files can be rendered directly into the template as follows
@ -181,7 +183,7 @@ Asset files can be rendered directly into the template as follows
If the requested asset name does not match the name of an uploaded asset, the template will continue without loading the image.
!!! info "Assets location"
Upload new assets via the [admin interface](../settings/admin.md) to ensure they are uploaded to the correct location on the server.
Upload new assets via the *Report Assets* panel in the [Admin Center](../settings/admin.md#admin-center) to ensure they are uploaded to the correct location on the server.
## Report Snippets
@ -190,7 +192,9 @@ A powerful feature provided by the django / WeasyPrint templating framework is t
To support this, InvenTree provides report "snippets" - short (or not so short) template files which cannot be rendered by themselves, but can be called from other templates.
Similar to assets files, snippet template files are uploaded via the admin interface.
Snippet files are managed from the [Admin Center](../settings/admin.md#admin-center), via the *Report Snippets* panel. Staff users can upload new snippet files, and edit or remove existing snippets.
Additionally, the content of an existing snippet can be modified directly within the browser - simply select a snippet from the table to open it in the built-in code editor.
Snippets are included in a template as follows:
@ -243,7 +247,7 @@ When WeasyPrint renders a template to PDF it can make outbound requests to load
|---|---|
| `data:` URIs | Always permitted — self-contained, no network access |
| `file://` | Always blocked — assets and images must be inlined as `data:` URIs before reaching WeasyPrint |
| `http` / `https` | Disabled by default, but can be blocked - see *Remote URL Fetching* below |
| `http` / `https` | Disabled by default, but can be enabled - see *Remote URL Fetching* below |
| Any other scheme | Always blocked |
HTTP redirects are also disabled: a URL that passes validation cannot redirect to an internal address.

View File

@ -47,7 +47,7 @@ For example, rendering the name of a part (which is available in the particular
## Merging Reports
When rendering reports for multiple items, the default behaviour is that each item is rendered as a separate report. The chosen templeate is rendered multiple times, once for each item selected, and expects a single item in the context variable.
When rendering reports for multiple items, the default behaviour is that each item is rendered as a separate report. The chosen template is rendered multiple times, once for each item selected, and expects a single item in the context variable.
However, it is possible to merge multiple items into a single report document. This is achieved by enabling the `merge` attribute of the report template:

View File

@ -4,11 +4,11 @@ title: Template editor
## Template editor
The Template Editor is integrated into the [Admin Center](../settings//admin.md#admin-center) of the Web UI. It allows users to create and edit label and report templates directly with a side by side preview for a more productive workflow.
The Template Editor is integrated into the [Admin Center](../settings/admin.md#admin-center) of the Web UI. It allows users to create and edit label and report templates directly with a side by side preview for a more productive workflow.
![Template Table](../assets/images/report/template-table.png)
On the left side (1) are all possible possible template types for labels and reports listed. With the "+" button (2), above the template table (3), new templates for the selected type can be created. To switch to the template editor click on a template.
On the left side (1) are all possible template types for labels and reports listed. With the "+" button (2), above the template table (3), new templates for the selected type can be created. To switch to the template editor click on a template.
### Editing Templates
@ -31,3 +31,10 @@ If you don't want to override the template, but just render a preview for a temp
#### Edit template metadata
Editing metadata such as name, description, filters and even width/height for labels and orientation/page size for reports can be done from the edit modal accessible when clicking on the three dots (4) and select "Edit" in the dropdown menu.
### Editing Snippets
[Report snippets](./index.md#report-snippets) can also be edited directly within the browser, from the *Report Snippets* panel in the Admin Center. Selecting a snippet from the table opens it in the same code editor as used for report and label templates.
!!! info "No Preview"
As snippets cannot be rendered by themselves (they must be included in a report or label template), no preview area is available when editing a snippet.

View File

@ -1,14 +1,19 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 514
INVENTREE_API_VERSION = 515
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v514 -> 2026-07-03 : https://github.com/inventree/InvenTree/pull/12295
v515 -> 2026-07-03 : https://github.com/inventree/InvenTree/pull/12295
- Adds "consumable" field to the Part model
v514 -> 2026-07-02 : https://github.com/inventree/InvenTree/pull/12294
- Adds "duplicate" field to the BuildOrder, Company, ManufacturerPart, SupplierPart and SalesOrderShipment API endpoints
- Order duplication options: renames "order_id" field to "original", which now performs primary-key validation
- Part duplication options: renames "part" field to "original"
v513 -> 2026-06-25 : https://github.com/inventree/InvenTree/pull/12250
- Adds "active" field to the ProjectCode model and API endpoints

View File

@ -600,7 +600,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
Default implementation returns an empty list
"""
return []
return getattr(self, 'SKIP_CREATE_FIELDS', [])
def save(self, **kwargs):
"""Catch any django ValidationError thrown at the moment `save` is called, and re-throw as a DRF ValidationError."""
@ -921,3 +921,104 @@ class ContentTypeField(serializers.ChoiceField):
)
return content_type
class DuplicateOptionsSerializer(serializers.Serializer):
"""Generic serializer for specifying copy options when duplicating a model instance.
Builds its fields dynamically at instantiation time so the same class can be
reused for any model without subclassing.
"""
# Special 'shortcut' fields which are used for multiple models
DEFAULT_FIELDS = [
(
'copy_parameters',
_('Copy Parameters'),
_('Copy parameters from the original item'),
False,
),
(
'copy_lines',
_('Copy Lines'),
_('Copy line items from the original order'),
False,
),
(
'copy_extra_lines',
_('Copy Extra Lines'),
_('Copy extra line items from the original order'),
False,
),
]
def __init__(
self,
queryset: QuerySet,
*args,
copy_fields: Optional[list[dict]] = None,
**kwargs,
):
"""Initialise the serializer and dynamically attach fields.
Arguments:
queryset: Queryset used for the `original` PrimaryKeyRelatedField.
copy_fields: Optional list of dicts, each describing one boolean copy toggle.
Keys:
name: (str, required)
label: (str, optional)
help_text: (str, optional)
default: (bool, optional, default True)
"""
# Enforce certain properties onto this serializer
kwargs['label'] = kwargs.get('label', _('Duplication Options'))
kwargs['help_text'] = kwargs.get(
'help_text', _('Specify options for duplicating this item')
)
kwargs['required'] = False
kwargs['write_only'] = True
copy_fields = copy_fields or []
copy_field_names = [spec['name'] for spec in copy_fields]
# Apply "default" fields
for name, label, help_text, default_value in self.DEFAULT_FIELDS:
popped_value = kwargs.pop(name, default_value)
if name in copy_field_names:
# Manually supplied field, continue
continue
if popped_value:
copy_fields.append({
'name': name,
'label': label,
'help_text': help_text,
'default': True,
})
super().__init__(*args, **kwargs)
# Re-class the instance with a model-specific subclass,
# so that each model generates a unique schema component name
if self.__class__ is DuplicateOptionsSerializer:
self.__class__ = type(
f'{queryset.model.__name__}DuplicateOptionsSerializer',
(DuplicateOptionsSerializer,),
{},
)
self.fields['original'] = serializers.PrimaryKeyRelatedField(
queryset=queryset,
required=True,
label=_('Original'),
help_text=_('Select instance to duplicate'),
)
for spec in copy_fields or []:
self.fields[spec['name']] = serializers.BooleanField(
required=False,
default=spec.get('default', True),
label=spec.get('label', spec['name']),
help_text=spec.get('help_text', ''),
)

View File

@ -34,6 +34,7 @@ from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
DuplicateOptionsSerializer,
FilterableSerializerMixin,
InvenTreeDecimalField,
InvenTreeModelSerializer,
@ -67,6 +68,8 @@ class BuildSerializer(
):
"""Serializes a Build object."""
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Serializer metaclass."""
@ -80,6 +83,7 @@ class BuildSerializer(
'completed',
'completion_date',
'destination',
'duplicate',
'external',
'parent',
'part',
@ -190,12 +194,29 @@ class BuildSerializer(
return queryset
duplicate = DuplicateOptionsSerializer(Build.objects.all(), copy_parameters=True)
def __init__(self, *args, **kwargs):
"""Determine if extra serializer fields are required."""
kwargs.pop('create', False)
super().__init__(*args, **kwargs)
@transaction.atomic
def create(self, validated_data):
"""Create a new Build instance, optionally copying data from an existing build."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
def validate_reference(self, reference):
"""Custom validation for the Build reference field."""
# Ensure the reference matches the required pattern

View File

@ -1,5 +1,6 @@
"""JSON serializers for Company app."""
from django.db import transaction
from django.db.models import Prefetch
from django.utils.translation import gettext_lazy as _
@ -14,6 +15,7 @@ from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.ready import isGeneratingSchema
from InvenTree.serializers import (
DuplicateOptionsSerializer,
FilterableSerializerMixin,
InvenTreeCurrencySerializer,
InvenTreeDecimalField,
@ -118,6 +120,8 @@ class CompanySerializer(
import_exclude_fields = ['image']
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Metaclass options."""
@ -132,6 +136,7 @@ class CompanySerializer(
'email',
'currency',
'contact',
'duplicate',
'link',
'image',
'active',
@ -191,6 +196,23 @@ class CompanySerializer(
parameters = common.filters.enable_parameters_filter()
duplicate = DuplicateOptionsSerializer(Company.objects.all(), copy_parameters=True)
@transaction.atomic
def create(self, validated_data):
"""Create a new Company instance, optionally copying data from an existing company."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
@register_importer()
class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
@ -217,6 +239,8 @@ class ManufacturerPartSerializer(
):
"""Serializer for ManufacturerPart object."""
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Metaclass options."""
@ -229,6 +253,7 @@ class ManufacturerPartSerializer(
'manufacturer',
'manufacturer_detail',
'description',
'duplicate',
'MPN',
'link',
'barcode_hash',
@ -241,6 +266,25 @@ class ManufacturerPartSerializer(
parameters = common.filters.enable_parameters_filter()
duplicate = DuplicateOptionsSerializer(
ManufacturerPart.objects.all(), copy_parameters=True
)
@transaction.atomic
def create(self, validated_data):
"""Create a new ManufacturerPart instance, optionally copying data from an existing instance."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
part_detail = OptionalField(
serializer_class=part_serializers.PartBriefSerializer,
serializer_kwargs={
@ -323,6 +367,8 @@ class SupplierPartSerializer(
export_exclude_fields = ['tags']
SKIP_CREATE_FIELDS = ['duplicate']
export_child_fields = [
'part_detail.name',
'part_detail.description',
@ -339,6 +385,7 @@ class SupplierPartSerializer(
'available',
'availability_updated',
'description',
'duplicate',
'in_stock',
'on_order',
'link',
@ -494,6 +541,10 @@ class SupplierPartSerializer(
# Date fields
updated = serializers.DateTimeField(allow_null=True, read_only=True)
duplicate = DuplicateOptionsSerializer(
SupplierPart.objects.all(), copy_parameters=True
)
@staticmethod
def annotate_queryset(queryset):
"""Annotate the SupplierPart queryset with extra fields.
@ -522,8 +573,11 @@ class SupplierPartSerializer(
return response
@transaction.atomic
def create(self, validated_data):
"""Extract manufacturer data and process ManufacturerPart."""
duplicate = validated_data.pop('duplicate', None)
# Extract 'available' quantity from the serializer
available = validated_data.pop('available', None)
@ -541,6 +595,12 @@ class SupplierPartSerializer(
kwargs = {'manufacturer': manufacturer, 'MPN': MPN}
supplier_part.save(**kwargs)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
supplier_part.copy_parameters_from(original)
return supplier_part

View File

@ -16,6 +16,7 @@ from taggit.serializers import TagListSerializerField
import data_exporter.serializers
import data_exporter.tasks
import InvenTree.exceptions
import InvenTree.serializers
from common.models import DataOutput
from InvenTree.helpers import str2bool
from InvenTree.tasks import offload_task
@ -64,6 +65,14 @@ class DataExportSerializerMixin:
# Exclude fields which are not required for data export
for field in self.get_export_exclude_fields(**kwargs):
self.fields.pop(field, None)
# Duplication options are never used for data export
for field in [
name
for name, field in self.fields.items()
if isinstance(field, InvenTree.serializers.DuplicateOptionsSerializer)
]:
self.fields.pop(field, None)
else:
# Exclude fields which are only used for data export
for field in self.get_export_only_fields(**kwargs):

View File

@ -3,6 +3,8 @@
from rest_framework import fields, serializers
from taggit.serializers import TagListSerializerField
import InvenTree.serializers
class DataImportSerializerMixin:
"""Mixin class for adding data import functionality to a DRF serializer."""
@ -41,6 +43,14 @@ class DataImportSerializerMixin:
for field in self.get_import_exclude_fields(**kwargs):
self.fields.pop(field, None)
# Duplication options are never used for data import
for field in [
name
for name, field in self.fields.items()
if isinstance(field, InvenTree.serializers.DuplicateOptionsSerializer)
]:
self.fields.pop(field, None)
else:
# Exclude fields which are only used for data import
for field in self.get_import_only_fields(**kwargs):

View File

@ -32,6 +32,7 @@ from InvenTree.helpers import extract_serial_numbers, hash_barcode, normalize, s
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
DuplicateOptionsSerializer,
FilterableSerializerMixin,
InvenTreeCurrencySerializer,
InvenTreeDecimalField,
@ -67,40 +68,6 @@ class TotalPriceMixin(serializers.Serializer):
)
class DuplicateOrderSerializer(serializers.Serializer):
"""Serializer for specifying options when duplicating an order."""
class Meta:
"""Metaclass options."""
fields = ['order_id', 'copy_lines', 'copy_extra_lines', 'copy_parameters']
order_id = serializers.IntegerField(
required=True, label=_('Order ID'), help_text=_('ID of the order to duplicate')
)
copy_lines = serializers.BooleanField(
required=False,
default=True,
label=_('Copy Lines'),
help_text=_('Copy line items from the original order'),
)
copy_extra_lines = serializers.BooleanField(
required=False,
default=True,
label=_('Copy Extra Lines'),
help_text=_('Copy extra line items from the original order'),
)
copy_parameters = serializers.BooleanField(
required=False,
default=True,
label=_('Copy Parameters'),
help_text=_('Copy order parameters from the original order'),
)
class AbstractOrderSerializer(
CustomStatusSerializerMixin,
DataImportExportSerializerMixin,
@ -110,9 +77,9 @@ class AbstractOrderSerializer(
):
"""Abstract serializer class which provides fields common to all order types."""
export_exclude_fields = ['notes', 'duplicate']
export_exclude_fields = ['notes']
import_exclude_fields = ['notes', 'duplicate']
import_exclude_fields = ['notes']
# Number of line items in this order
line_items = serializers.IntegerField(
@ -195,12 +162,8 @@ class AbstractOrderSerializer(
created_by = UserSerializer(read_only=True)
duplicate = DuplicateOrderSerializer(
label=_('Duplicate Order'),
help_text=_('Specify options for duplicating this order'),
required=False,
write_only=True,
)
# Note: The 'duplicate' field must be defined by each concrete serializer class,
# as it requires a queryset specific to the order model type
def validate_reference(self, reference):
"""Custom validation for the reference field."""
@ -293,30 +256,21 @@ class AbstractOrderSerializer(
instance = super().create(validated_data)
if duplicate:
order_id = duplicate.get('order_id', None)
copy_lines = duplicate.get('copy_lines', True)
copy_extra_lines = duplicate.get('copy_extra_lines', True)
copy_parameters = duplicate.get('copy_parameters', True)
original = duplicate['original']
try:
copy_from = instance.__class__.objects.get(pk=order_id)
except Exception:
# If the order ID is invalid, raise a validation error
raise ValidationError(_('Invalid order ID'))
if copy_lines:
for line in copy_from.lines.all():
if duplicate.get('copy_lines', False):
for line in original.lines.all():
instance.clean_line_item(line)
line.save()
if copy_extra_lines:
for line in copy_from.extra_lines.all():
if duplicate.get('copy_extra_lines', False):
for line in original.extra_lines.all():
line.pk = None
line.order = instance
line.save()
if copy_parameters:
instance.copy_parameters_from(copy_from)
if duplicate.get('copy_parameters', False):
instance.copy_parameters_from(original)
return instance
@ -452,6 +406,13 @@ class PurchaseOrderSerializer(
return [*fields, 'duplicate']
duplicate = DuplicateOptionsSerializer(
order.models.PurchaseOrder.objects.all(),
copy_lines=True,
copy_extra_lines=True,
copy_parameters=True,
)
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset.
@ -1134,6 +1095,13 @@ class SalesOrderSerializer(
return [*fields, 'duplicate']
duplicate = DuplicateOptionsSerializer(
order.models.SalesOrder.objects.all(),
copy_lines=True,
copy_extra_lines=True,
copy_parameters=True,
)
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset.
@ -1411,6 +1379,8 @@ class SalesOrderShipmentSerializer(
):
"""Serializer for the SalesOrderShipment class."""
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Metaclass options."""
@ -1423,6 +1393,7 @@ class SalesOrderShipmentSerializer(
'shipment_address',
'delivery_date',
'checked_by',
'duplicate',
'reference',
'tracking_number',
'invoice_number',
@ -1507,6 +1478,25 @@ class SalesOrderShipmentSerializer(
tags = common.filters.enable_tags_filter()
duplicate = DuplicateOptionsSerializer(
order.models.SalesOrderShipment.objects.all(), copy_parameters=True
)
@transaction.atomic
def create(self, validated_data):
"""Create a new SalesOrderShipment instance, optionally copying data from an existing shipment."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
class SalesOrderAllocationSerializer(
FilterableSerializerMixin, InvenTreeModelSerializer
@ -2198,6 +2188,14 @@ class ReturnOrderSerializer(
return [*fields, 'duplicate']
# Note: line items cannot be duplicated from a ReturnOrder,
# as they are linked to specific stock items
duplicate = DuplicateOptionsSerializer(
order.models.ReturnOrder.objects.all(),
copy_extra_lines=True,
copy_parameters=True,
)
@staticmethod
def annotate_queryset(queryset):
"""Custom annotation for the serializer queryset."""
@ -2485,6 +2483,11 @@ class TransferOrderSerializer(
return [*fields, 'duplicate']
# Note: TransferOrder does not have "extra" line items
duplicate = DuplicateOptionsSerializer(
order.models.TransferOrder.objects.all(), copy_lines=True, copy_parameters=True
)
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset.

View File

@ -573,7 +573,7 @@ class PurchaseOrderTest(OrderTest):
# Duplicate with non-existent PK to provoke error
data['duplicate'] = {
'order_id': 10000001,
'original': 10000001,
'copy_lines': True,
'copy_extra_lines': False,
}
@ -584,7 +584,7 @@ class PurchaseOrderTest(OrderTest):
response = self.post(reverse('api-po-list'), data, expected_code=400)
data['duplicate'] = {
'order_id': 1,
'original': 1,
'copy_lines': True,
'copy_extra_lines': False,
}
@ -605,7 +605,7 @@ class PurchaseOrderTest(OrderTest):
data['reference'] = 'PO-9998'
data['duplicate'] = {
'order_id': 1,
'original': 1,
'copy_lines': False,
'copy_extra_lines': True,
}
@ -1792,7 +1792,7 @@ class SalesOrderTest(OrderTest):
{
'reference': 'SO-12345',
'customer': so.customer.pk,
'duplicate': {'order_id': so.pk, 'copy_parameters': False},
'duplicate': {'original': so.pk, 'copy_parameters': False},
},
)
@ -1809,7 +1809,7 @@ class SalesOrderTest(OrderTest):
{
'reference': 'SO-12346',
'customer': so.customer.pk,
'duplicate': {'order_id': so.pk},
'duplicate': {'original': so.pk},
},
)

View File

@ -408,67 +408,6 @@ class PartBriefSerializer(
)
class DuplicatePartSerializer(serializers.Serializer):
"""Serializer for specifying options when duplicating a Part.
The fields in this serializer control how the Part is duplicated.
"""
class Meta:
"""Metaclass options."""
fields = [
'part',
'copy_image',
'copy_bom',
'copy_parameters',
'copy_notes',
'copy_tests',
]
part = serializers.PrimaryKeyRelatedField(
queryset=Part.objects.all(),
label=_('Original Part'),
help_text=_('Select original part to duplicate'),
required=True,
)
copy_image = serializers.BooleanField(
label=_('Copy Image'),
help_text=_('Copy image from original part'),
required=False,
default=False,
)
copy_bom = serializers.BooleanField(
label=_('Copy BOM'),
help_text=_('Copy bill of materials from original part'),
required=False,
default=False,
)
copy_parameters = serializers.BooleanField(
label=_('Copy Parameters'),
help_text=_('Copy parameter data from original part'),
required=False,
default=False,
)
copy_notes = serializers.BooleanField(
label=_('Copy Notes'),
help_text=_('Copy notes from original part'),
required=False,
default=True,
)
copy_tests = serializers.BooleanField(
label=_('Copy Tests'),
help_text=_('Copy test templates from original part'),
required=False,
default=False,
)
class InitialStockSerializer(serializers.Serializer):
"""Serializer for creating initial stock quantity."""
@ -602,7 +541,7 @@ class PartSerializer(
Used when displaying all details of a single component.
"""
import_exclude_fields = ['creation_date', 'creation_user', 'duplicate']
import_exclude_fields = ['creation_date', 'creation_user']
class Meta:
"""Metaclass defining serializer fields."""
@ -1009,11 +948,37 @@ class PartSerializer(
)
# Extra fields used only for creation of a new Part instance
duplicate = DuplicatePartSerializer(
duplicate = InvenTree.serializers.DuplicateOptionsSerializer(
Part.objects.all(),
label=_('Duplicate Part'),
help_text=_('Copy initial data from another Part'),
write_only=True,
required=False,
copy_parameters=True,
copy_fields=[
{
'name': 'copy_image',
'label': _('Copy Image'),
'help_text': _('Copy image from original part'),
'default': False,
},
{
'name': 'copy_bom',
'label': _('Copy BOM'),
'help_text': _('Copy bill of materials from original part'),
'default': False,
},
{
'name': 'copy_notes',
'label': _('Copy Notes'),
'help_text': _('Copy notes from original part'),
'default': True,
},
{
'name': 'copy_tests',
'label': _('Copy Tests'),
'help_text': _('Copy test templates from original part'),
'default': False,
},
],
)
initial_stock = InitialStockSerializer(
@ -1079,7 +1044,7 @@ class PartSerializer(
# Copy data from original Part
if duplicate:
original = duplicate['part']
original = duplicate['original']
if duplicate.get('copy_bom', False):
instance.copy_bom_from(original)

View File

@ -1649,7 +1649,7 @@ class PartCreationTests(PartAPITestBase):
'testable': do_copy,
'assembly': do_copy,
'duplicate': {
'part': 100,
'original': 100,
'copy_bom': do_copy,
'copy_notes': do_copy,
'copy_image': do_copy,

View File

@ -57,4 +57,5 @@ export interface SettingsStateProps {
pathParams?: PathParams;
getSetting: (key: string, default_value?: string) => string; // Return a raw setting value
isSet: (key: string, default_value?: boolean) => boolean; // Check a "boolean" setting
getSettingLength: () => number;
}

View File

@ -108,10 +108,10 @@
"zustand": "^5.0.14"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@babel/preset-react": "^7.29.7",
"@babel/preset-typescript": "^7.29.7",
"@babel/runtime": "^7.29.7",
"@babel/core": "^8.0.1",
"@babel/preset-react": "^8.0.1",
"@babel/preset-typescript": "^8.0.1",
"@babel/runtime": "^8.0.0",
"@codecov/vite-plugin": "^2.0.1",
"@flakiness/playwright": "^1.13.0",
"@lingui/babel-plugin-lingui-macro": "^5.9.5",
@ -126,7 +126,7 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-window": "^1.8.8",
"@vanilla-extract/vite-plugin": "^5.2.2",
"@vitejs/plugin-react": "^5.2.0",
"@vitejs/plugin-react": "^5",
"babel-plugin-macros": "^3.1.0",
"nyc": "^18.0.0",
"otpauth": "^9.5.1",

View File

@ -33,7 +33,7 @@ export const PdfPreviewComponent: PreviewAreaComponent = forwardRef(
}
let preview = await api.post(
printingUrl,
printingUrl!,
{
items: [previewItem],
template: template.pk

View File

@ -1,6 +1,11 @@
import { Boundary } from '@lib/components/Boundary';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { t } from '@lingui/core/macro';
import {
Alert,
Button,
CloseButton,
Group,
List,
@ -24,11 +29,6 @@ import {
import Split from '@uiw/react-split';
import type React from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Boundary } from '@lib/components/Boundary';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { api } from '../../../App';
import type { TemplateI } from '../../../tables/settings/TemplateTable';
import { SplitButton } from '../../buttons/SplitButton';
@ -78,26 +78,38 @@ export type PreviewArea = {
export type TemplateEditorProps = {
templateUrl: string;
printingUrl: string;
printingUrl?: string;
editors: Editor[];
previewAreas: PreviewArea[];
template: TemplateI;
// Name of the file field on the template model (e.g. 'template' or 'snippet')
fileField?: string;
};
export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
const { templateUrl, editors, previewAreas, template } = props;
const {
templateUrl,
editors,
previewAreas,
template,
fileField = 'template'
} = props;
const editorRef = useRef<EditorRef | null>(null);
const previewRef = useRef<PreviewAreaRef | null>(null);
// If no preview areas are provided, the template is saved directly
const hasPreview = previewAreas.length > 0;
const [hasSaveConfirmed, setHasSaveConfirmed] = useState(false);
const [previewItem, setPreviewItem] = useState<string>('');
const [renderingErrors, setRenderingErrors] = useState<string[] | null>(null);
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [editorValue, setEditorValue] = useState<null | string>(editors[0].key);
const [previewValue, setPreviewValue] = useState<null | string>(
previewAreas[0].key
previewAreas[0]?.key ?? null
);
const codeRef = useRef<string | undefined>(undefined);
@ -131,12 +143,12 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
if (!templateUrl) return;
api.get(templateUrl).then((response: any) => {
if (response.data?.template) {
if (response.data?.[fileField]) {
// Fetch the raw template file from the server
// Request that it is provided without any caching,
// to ensure that we always get the latest version
api
.get(response.data.template, {
.get(response.data[fileField], {
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
@ -149,7 +161,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
})
.catch(() => {
console.error(
`ERR: Could not load template from ${response.data.template}`
`ERR: Could not load template from ${response.data[fileField]}`
);
codeRef.current = undefined;
hideNotification('template-load-error');
@ -244,6 +256,45 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
[previewItem]
);
// Save the template file directly to the server, without any preview
const saveTemplate = useCallback(async () => {
const code = await getCodeFromEditor();
if (code === undefined) return;
const filename =
(template as any)[fileField]?.split('/').pop() ?? 'template.html';
const formData = new FormData();
formData.append(fileField, new File([code], filename));
setIsSaving(true);
api
.patch(templateUrl, formData)
.then(() => {
hideNotification('template-save');
showNotification({
id: 'template-save',
title: t`Saved`,
message: t`Template file has been updated`,
color: 'green'
});
})
.catch(() => {
hideNotification('template-save');
showNotification({
id: 'template-save',
title: t`Error`,
message: t`Could not save the template to the server.`,
color: 'red'
});
})
.finally(() => {
setIsSaving(false);
});
}, [getCodeFromEditor, template, templateUrl, fileField]);
const previewApiUrl = useMemo(
() =>
ModelInformationDict[template.model_type ?? ModelType.stockitem]
@ -261,18 +312,20 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
}, [template]);
useEffect(() => {
if (!hasPreview) return;
api
.get(apiUrl(previewApiUrl), { params: { limit: 1, ...templateFilters } })
.then((res) => {
if (res.data.results.length === 0) return;
setPreviewItem(res.data.results[0].pk);
});
}, [previewApiUrl, templateFilters]);
}, [hasPreview, previewApiUrl, templateFilters]);
return (
<Boundary label='TemplateEditor'>
<Stack style={{ height: '100%', flex: '1' }}>
<Split visible style={{ flex: 1 }}>
<Split visible={hasPreview} style={{ flex: 1 }}>
<Tabs
value={editorValue}
onChange={async (v) => {
@ -282,7 +335,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
keepMounted={false}
style={{
minWidth: '300px',
width: '50%',
width: hasPreview ? '50%' : '100%',
display: 'flex',
flexDirection: 'column'
}}
@ -301,6 +354,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
})}
<Group justify='right' style={{ flex: '1' }} wrap='nowrap'>
{hasPreview ? (
<SplitButton
loading={isPreviewLoading}
defaultSelected='preview_save'
@ -324,6 +378,15 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
}
]}
/>
) : (
<Button
leftSection={<IconDeviceFloppy size={18} />}
loading={isSaving}
onClick={() => saveTemplate()}
>
{t`Save`}
</Button>
)}
</Group>
</Tabs.List>
@ -342,6 +405,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
))}
</Tabs>
{hasPreview && (
<Tabs
value={previewValue}
onChange={setPreviewValue}
@ -436,6 +500,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
</Tabs.Panel>
))}
</Tabs>
)}
</Split>
</Stack>
</Boundary>

View File

@ -24,7 +24,7 @@ import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { cancelEvent } from '@lib/functions/Events';
import { getDetailUrl, navigateToLink } from '@lib/index';
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
import type { ApiFormFieldType } from '@lib/types/Forms';
import { useApi } from '../../../contexts/ApiContext';
import {

View File

@ -65,8 +65,8 @@ const AboutContent = ({
function fillTable(lookup: AboutLookupRef[], data: any, alwaysLink = false) {
return lookup
.filter((entry: AboutLookupRef) => !!data[entry.ref])
.map((entry: AboutLookupRef, idx) => (
<Table.Tr key={idx}>
.map((entry: AboutLookupRef) => (
<Table.Tr key={entry.ref}>
<Table.Td>{entry.title}</Table.Td>
<Table.Td>
<Group justify='space-between' gap='xs'>

View File

@ -564,9 +564,9 @@ export function SearchDrawer({
multiple
defaultValue={searchQueries.map((q) => q.model)}
>
{queryResults.map((query, idx) => (
{queryResults.map((query) => (
<QueryResultGroup
key={idx}
key={query.model}
searchText={searchText}
query={query}
navigate={navigate}

View File

@ -31,9 +31,9 @@ export type { InstanceRenderInterface } from '@lib/types/Rendering';
import {
getBaseUrl,
getDetailUrl,
navigateToLink,
shortenString
} from '@lib/index';
navigateToLink
} from '@lib/functions/Navigation';
import { shortenString } from '@lib/functions/String';
import { IconLink } from '@tabler/icons-react';
import { useApi } from '../../contexts/ApiContext';
import { usePluginState } from '../../states/PluginState';

View File

@ -12,7 +12,7 @@ import type { NavigateFunction } from 'react-router-dom';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType';
import { getDetailUrl, navigateToLink } from '@lib/index';
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
import { IconLink } from '@tabler/icons-react';
/**

View File

@ -28,21 +28,34 @@ export function SettingList({
settingsState,
keys,
onChange,
onLoaded
onLoaded,
doGet
}: Readonly<{
heading?: string;
settingsState: SettingsStateProps;
keys?: string[];
onChange?: () => void;
onLoaded?: (settings: SettingsStateProps) => void;
doGet?: boolean;
}>) {
useEffect(() => {
if (settingsState.loaded) {
if (settingsState.loaded === true) {
// Call the onLoaded callback if provided
onLoaded?.(settingsState);
}
}, [settingsState.loaded, settingsState.settings]);
// get data if doGet is true to break memos leading to hidden group panels
useMemo(() => {
if (doGet && !settingsState.loaded) {
settingsState.fetchSettings().then((success) => {
if (success) {
onLoaded?.(settingsState);
}
});
}
}, [doGet, settingsState]);
const api = useApi();
const allKeys = useMemo(
@ -226,9 +239,11 @@ export function GlobalSettingList({
export function PluginSettingList({
pluginKey,
doGet,
onLoaded
}: Readonly<{
pluginKey: string;
doGet?: boolean;
onLoaded?: (settings: SettingsStateProps) => void;
}>) {
const store = useMemo(
@ -246,14 +261,22 @@ export function PluginSettingList({
pluginSettings.fetchSettings();
}, [pluginSettings.fetchSettings]);
return <SettingList settingsState={pluginSettings} onLoaded={onLoaded} />;
return (
<SettingList
settingsState={pluginSettings}
onLoaded={onLoaded}
doGet={doGet}
/>
);
}
export function PluginUserSettingList({
pluginKey,
doGet,
onLoaded
}: Readonly<{
pluginKey: string;
doGet?: boolean;
onLoaded?: (settings: SettingsStateProps) => void;
}>) {
const store = useMemo(
@ -271,7 +294,13 @@ export function PluginUserSettingList({
pluginUserSettings.fetchSettings();
}, [pluginUserSettings.fetchSettings]);
return <SettingList settingsState={pluginUserSettings} onLoaded={onLoaded} />;
return (
<SettingList
settingsState={pluginUserSettings}
onLoaded={onLoaded}
doGet={doGet}
/>
);
}
export function MachineSettingList({

View File

@ -36,16 +36,18 @@ import {
} from '../hooks/UseGenerator';
import { useGlobalSettingsState } from '../states/SettingsStates';
import { RenderPartColumn } from '../tables/ColumnRenderers';
import { ProjectCodeField, TagsField } from './CommonFields';
import { DuplicateField, ProjectCodeField, TagsField } from './CommonFields';
/**
* Field set for BuildOrder forms
*/
export function useBuildOrderFields({
create,
duplicateBuildId,
modalId
}: {
create: boolean;
duplicateBuildId?: number | null;
modalId: string;
}): ApiFormFieldSet {
const [destination, setDestination] = useState<number | null | undefined>(
@ -133,7 +135,13 @@ export function useBuildOrderFields({
is_active: true
}
},
external: {}
external: {},
duplicate: DuplicateField({
originalId: duplicateBuildId,
extraFields: {
copy_parameters: {}
}
})
};
if (!globalSettings.isSet('PROJECT_CODES_ENABLED', true)) {
@ -144,8 +152,19 @@ export function useBuildOrderFields({
delete fields.external;
}
if (!duplicateBuildId) {
delete fields.duplicate;
}
return fields;
}, [create, destination, batchCode, batchGenerator.result, globalSettings]);
}, [
create,
destination,
batchCode,
batchGenerator.result,
globalSettings,
duplicateBuildId
]);
}
export function useBuildOrderOutputFields({

View File

@ -2,6 +2,26 @@ import type { ApiFormFieldType } from '@lib/types/Forms';
import { t } from '@lingui/core/macro';
import { IconList } from '@tabler/icons-react';
// Generic field for implementing a "duplication options" form field
export function DuplicateField({
originalId,
extraFields
}: Readonly<{
originalId?: number | null;
extraFields?: Record<string, any>;
}>): ApiFormFieldType {
return {
children: {
original: {
value: originalId,
hidden: true
},
...extraFields
}
};
}
// Generic field for rendering a list of tags within a form
export function TagsField({
label,
description,

View File

@ -13,7 +13,7 @@ import {
IconPhone
} from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { TagsField } from './CommonFields';
import { DuplicateField, TagsField } from './CommonFields';
/**
* Field set for SupplierPart instance
@ -21,11 +21,13 @@ import { TagsField } from './CommonFields';
export function useSupplierPartFields({
manufacturerId,
manufacturerPartId,
partId
partId,
duplicateSupplierPartId
}: {
manufacturerId?: number;
manufacturerPartId?: number;
partId?: number;
duplicateSupplierPartId?: number | null;
}) {
const [part, setPart] = useState<any>({});
@ -95,14 +97,34 @@ export function useSupplierPartFields({
icon: <IconPackage />
},
primary: {},
active: {}
active: {},
duplicate: DuplicateField({
originalId: duplicateSupplierPartId,
extraFields: {
copy_parameters: {}
}
})
};
return fields;
}, [manufacturerId, manufacturerPartId, partId, part]);
if (!duplicateSupplierPartId) {
delete fields.duplicate;
}
export function useManufacturerPartFields() {
return fields;
}, [
manufacturerId,
manufacturerPartId,
partId,
part,
duplicateSupplierPartId
]);
}
export function useManufacturerPartFields({
duplicateManufacturerPartId
}: {
duplicateManufacturerPartId?: number | null;
} = {}) {
return useMemo(() => {
const fields: ApiFormFieldSet = {
part: {},
@ -120,18 +142,32 @@ export function useManufacturerPartFields() {
MPN: {},
description: {},
tags: TagsField({}),
link: {}
link: {},
duplicate: DuplicateField({
originalId: duplicateManufacturerPartId,
extraFields: {
copy_parameters: {}
}
})
};
if (!duplicateManufacturerPartId) {
delete fields.duplicate;
}
return fields;
}, []);
}, [duplicateManufacturerPartId]);
}
/**
* Field set for editing a company instance
*/
export function companyFields(): ApiFormFieldSet {
return {
export function companyFields({
duplicateCompanyId
}: {
duplicateCompanyId?: number | null;
} = {}): ApiFormFieldSet {
const fields: ApiFormFieldSet = {
name: {},
description: {},
website: {
@ -151,6 +187,18 @@ export function companyFields(): ApiFormFieldSet {
is_supplier: {},
is_manufacturer: {},
is_customer: {},
active: {}
};
active: {},
duplicate: DuplicateField({
originalId: duplicateCompanyId,
extraFields: {
copy_parameters: {}
}
})
};
if (!duplicateCompanyId) {
delete fields.duplicate;
}
return fields;
}

View File

@ -159,7 +159,7 @@ export function usePartFields({
fields.duplicate = {
icon: <IconCopy />,
children: {
part: {
original: {
value: duplicatePartInstance?.pk,
hidden: true
},

View File

@ -316,7 +316,7 @@ export function usePurchaseOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},

View File

@ -84,15 +84,10 @@ export function useReturnOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},
copy_lines: {
// Cannot duplicate lines from a return order!
value: false,
hidden: true
},
copy_extra_lines: {},
copy_parameters: {}
}

View File

@ -97,7 +97,7 @@ export function useSalesOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},

View File

@ -54,13 +54,12 @@ export function useTransferOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},
copy_lines: {},
// Transfer Orders don't have extra lines for now...
copy_extra_lines: { hidden: true, value: false }
copy_parameters: {}
}
};
}

View File

@ -10,13 +10,13 @@ import 'mantine-datatable/styles.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import type * as LinguiCore from '@lingui/core';
import type * as LinguiReact from '@lingui/react';
import * as LinguiCore from '@lingui/core';
import * as LinguiReact from '@lingui/react';
// Global types to be exported for use in plugins
import type * as MantineCore from '@mantine/core';
import type * as MantineNotifications from '@mantine/notifications';
import * as MantineCore from '@mantine/core';
import * as MantineNotifications from '@mantine/notifications';
import * as React from 'react';
import type * as ReactDOM from 'react-dom';
import * as ReactDOM from 'react-dom';
import * as ReactDOMClient from 'react-dom/client';
import './styles/overrides.css';
@ -24,7 +24,6 @@ import './styles/overrides.css';
import { getBaseUrl } from '@lib/functions/Navigation';
import type { HostList } from '@lib/types/Server';
import MainView from './views/MainView';
import { loadWindowGlobals } from './window';
// define settings
declare global {
@ -123,5 +122,11 @@ ReactDOMClient.createRoot(
</React.StrictMode>
);
// Load globals onto the window object, so that they can be accessed by plugins without requiring direct imports
loadWindowGlobals();
// All window globals assigned here since these modules are already statically bundled
window.React = React;
window.ReactDOM = ReactDOM;
window.ReactDOMClient = ReactDOMClient;
window.MantineCore = MantineCore;
window.MantineNotifications = MantineNotifications;
window.LinguiCore = LinguiCore;
window.LinguiReact = LinguiReact;

View File

@ -1,3 +1,6 @@
import { PluginPanelKey } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import type { PanelGroupType, PanelType } from '@lib/types/Panel';
import { t } from '@lingui/core/macro';
import { Stack } from '@mantine/core';
import {
@ -5,6 +8,7 @@ import {
IconCpu,
IconDevicesPc,
IconExclamationCircle,
IconFileCode,
IconFileDownload,
IconFileUpload,
IconHome,
@ -12,6 +16,7 @@ import {
IconListDetails,
IconMail,
IconPackages,
IconPhoto,
IconPlugConnected,
IconQrcode,
IconReport,
@ -21,10 +26,6 @@ import {
IconUsersGroup
} from '@tabler/icons-react';
import { lazy, useMemo } from 'react';
import { PluginPanelKey } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import type { PanelGroupType, PanelType } from '@lib/types/Panel';
import PermissionDenied from '../../../../components/errors/PermissionDenied';
import PageTitle from '../../../../components/nav/PageTitle';
import { SettingsHeader } from '../../../../components/nav/SettingsHeader';
@ -103,6 +104,14 @@ const LocationTypesTable = Loadable(
lazy(() => import('../../../../tables/stock/LocationTypesTable'))
);
const SnippetTable = Loadable(
lazy(() => import('../../../../tables/settings/SnippetTable'))
);
const AssetTable = Loadable(
lazy(() => import('../../../../tables/settings/AssetTable'))
);
export default function AdminCenter() {
const user = useUserState();
@ -221,6 +230,18 @@ export default function AdminCenter() {
icon: <IconReport />,
content: <ReportTemplatePanel />
},
{
name: 'snippets',
label: t`Report Snippets`,
icon: <IconFileCode />,
content: <SnippetTable />
},
{
name: 'assets',
label: t`Report Assets`,
icon: <IconPhoto />,
content: <AssetTable />
},
{
name: 'location-types',
label: t`Location Types`,
@ -273,7 +294,7 @@ export default function AdminCenter() {
{
id: 'reporting',
label: t`Reporting`,
panelIDs: ['labels', 'reports']
panelIDs: ['labels', 'reports', 'snippets', 'assets']
},
{
id: 'plm',

View File

@ -25,11 +25,13 @@ function PluginSettingGroupItem({
}) {
// Hide the accordion item if there are no settings for this plugin
const [count, setCount] = useState<number>(0);
const [doGet, setDoGet] = useState<boolean>(true);
// Callback once the plugin settings have been loaded
const onLoaded = useCallback(
(settings: SettingsStateProps) => {
setCount(settings.settings?.length || 0);
setCount(settings.getSettingLength());
setDoGet(false);
},
[pluginKey]
);
@ -50,11 +52,20 @@ function PluginSettingGroupItem({
)}
</Group>
</Accordion.Control>
<Accordion.Panel>
{global ? (
<PluginSettingList pluginKey={pluginKey} onLoaded={onLoaded} />
<PluginSettingList
pluginKey={pluginKey}
onLoaded={onLoaded}
doGet={doGet}
/>
) : (
<PluginUserSettingList pluginKey={pluginKey} onLoaded={onLoaded} />
<PluginUserSettingList
pluginKey={pluginKey}
onLoaded={onLoaded}
doGet={doGet}
/>
)}
</Accordion.Panel>
</Accordion.Item>

View File

@ -632,6 +632,7 @@ export default function BuildDetail() {
const duplicateBuildOrderFields = useBuildOrderFields({
create: false,
duplicateBuildId: build.pk,
modalId: 'duplicate-build-order'
});

View File

@ -31,6 +31,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
DeleteItemAction,
DuplicateItemAction,
EditItemAction,
OptionsActionDropdown
} from '../../components/items/ActionDropdown';
@ -43,6 +44,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
import ParametersPanel from '../../components/panels/ParametersPanel';
import { companyFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
@ -293,11 +295,23 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
url: ApiEndpoints.company_list,
pk: company?.pk,
title: t`Edit Company`,
fields: companyFields(),
fields: useMemo(() => companyFields({}), []),
queryParams: new URLSearchParams({ tags: 'true' }),
onFormSuccess: refreshInstance
});
const duplicateCompany = useCreateApiFormModal({
url: ApiEndpoints.company_list,
title: t`Duplicate Company`,
initialData: useMemo(() => ({ ...company }), [company]),
fields: useMemo(
() => companyFields({ duplicateCompanyId: company?.pk }),
[company]
),
follow: true,
modelType: ModelType.company
});
const deleteCompany = useDeleteApiFormModal({
url: ApiEndpoints.company_list,
pk: company?.pk,
@ -322,6 +336,10 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => editCompany.open()
}),
DuplicateItemAction({
hidden: !user.hasAddRole(UserRoles.purchase_order),
onClick: () => duplicateCompany.open()
}),
DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => deleteCompany.open()
@ -345,6 +363,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
<>
{editCompany.modal}
{deleteCompany.modal}
{duplicateCompany.modal}
<InstanceDetail
query={instanceQuery}
requiredPermission={ModelType.company}

View File

@ -220,10 +220,14 @@ export default function ManufacturerPartDetail() {
onFormSuccess: refreshInstance
});
const duplicateManufacturerPartFields = useManufacturerPartFields({
duplicateManufacturerPartId: manufacturerPart?.pk
});
const duplicateManufacturerPart = useCreateApiFormModal({
url: ApiEndpoints.manufacturer_part_list,
title: t`Add Manufacturer Part`,
fields: editManufacturerPartFields,
fields: duplicateManufacturerPartFields,
initialData: {
...manufacturerPart
},

View File

@ -357,10 +357,14 @@ export default function SupplierPartDetail() {
}
});
const duplicateSupplierPartFields = useSupplierPartFields({
duplicateSupplierPartId: supplierPart?.pk
});
const duplicateSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: supplierPartFields,
fields: duplicateSupplierPartFields,
initialData: {
...supplierPart
},

View File

@ -65,6 +65,9 @@ export const useGlobalSettingsState = create<SettingsStateProps>(
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
})
);
@ -114,6 +117,9 @@ export const useUserSettingsState = create<SettingsStateProps>((set, get) => ({
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
}));
@ -185,6 +191,9 @@ export const createPluginSettingsState = ({
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
}));
};
@ -246,6 +255,9 @@ export const createMachineSettingsState = ({
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
}));
};

View File

@ -2,11 +2,12 @@ import { t } from '@lingui/core/macro';
import { Alert, Divider, Group, Stack, Text } from '@mantine/core';
import { useCallback, useMemo, useState } from 'react';
import { RowEditAction } from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import { ActionButton, RowEditAction, UserRoles } from '@lib/index';
import { ActionButton, UserRoles } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters';
import type { RowAction, TableColumn } from '@lib/types/Tables';
import { IconExclamationCircle, IconReplace } from '@tabler/icons-react';

View File

@ -1,9 +1,8 @@
import { RowDeleteAction, RowEditAction } from '@lib/components/RowActions';
import useTable from '@lib/hooks/UseTable';
import {
ApiEndpoints,
ModelType,
RowDeleteAction,
RowEditAction,
YesNoButton,
apiUrl,
formatDecimal

View File

@ -1,11 +1,13 @@
import {
RowDeleteAction,
RowDuplicateAction,
RowEditAction
} from '@lib/components/RowActions';
import useTable from '@lib/hooks/UseTable';
import {
AddItemButton,
ApiEndpoints,
type ApiFormFieldSet,
RowDeleteAction,
RowDuplicateAction,
RowEditAction,
UserRoles,
apiUrl
} from '@lib/index';

View File

@ -23,6 +23,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '@lib/components/AddItemButton';
import { RowDeleteAction, RowEditAction } from '@lib/components/RowActions';
import { StylishText } from '@lib/components/StylishText';
import { YesNoButton } from '@lib/components/YesNoButton';
import {
@ -32,7 +33,7 @@ import {
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import { RowDeleteAction, RowEditAction, formatDecimal } from '@lib/index';
import { formatDecimal } from '@lib/index';
import type { RowAction, TableColumn } from '@lib/types/Tables';
import type { InvenTreeTableProps } from '@lib/types/Tables';
import { Trans } from '@lingui/react/macro';

View File

@ -24,6 +24,7 @@ import { ActionDropdown } from '../../components/items/ActionDropdown';
import ImportPartWizard from '../../components/wizards/ImportPartWizard';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
import { DuplicateField } from '../../forms/CommonFields';
import { dataImporterSessionFields } from '../../forms/ImporterForms';
import { usePartFields } from '../../forms/PartForms';
import { InvenTreeIcon } from '../../functions/icons';
@ -316,12 +317,9 @@ export function PartListTable({
const duplicatePartFields: ApiFormFieldSet = useMemo(() => {
return {
...createPartFields,
duplicate: {
children: {
part: {
value: selectedPart.pk,
hidden: true
},
duplicate: DuplicateField({
originalId: selectedPart.pk,
extraFields: {
copy_image: {
value: true
},
@ -341,7 +339,7 @@ export function PartListTable({
hidden: !selectedPart.testable
}
}
}
})
};
}, [createPartFields, globalSettings, selectedPart]);

View File

@ -133,10 +133,14 @@ export function ManufacturerPartTable({
table: table
});
const duplicateManufacturerPartFields = useManufacturerPartFields({
duplicateManufacturerPartId: selectedPart?.pk
});
const duplicateManufacturerPart = useCreateApiFormModal({
url: ApiEndpoints.manufacturer_part_list,
title: t`Add Manufacturer Part`,
fields: useMemo(() => manufacturerPartFields, [manufacturerPartFields]),
fields: duplicateManufacturerPartFields,
table: table,
initialData: {
...selectedPart

View File

@ -292,10 +292,14 @@ export function SupplierPartTable({
}
});
const duplicateSupplierPartFields = useSupplierPartFields({
duplicateSupplierPartId: selectedSupplierPart?.pk
});
const duplicateSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
fields: duplicateSupplierPartFields,
initialData: {
...selectedSupplierPart,
primary: false,

View File

@ -0,0 +1,124 @@
import { AddItemButton } from '@lib/components/AddItemButton';
import { type RowAction, RowDeleteAction } from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { Alert, Text } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import {
useCreateApiFormModal,
useDeleteApiFormModal
} from '../../hooks/UseForm';
import { useUserState } from '../../states/UserState';
import { DescriptionColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export type AssetI = {
pk: number;
asset: string;
description: string;
};
export default function AssetTable() {
const table = useTable('report-asset');
const user = useUserState();
const columns: TableColumn<AssetI>[] = useMemo(() => {
return [
{
accessor: 'asset',
title: t`Asset`,
sortable: false,
switchable: false,
render: (record: AssetI) => {
if (!record.asset) {
return '-';
}
return <AttachmentLink attachment={record.asset} />;
},
noContext: true
},
DescriptionColumn({
accessor: 'description',
sortable: false,
switchable: false
})
];
}, []);
const [selectedAsset, setSelectedAsset] = useState<number>(-1);
const rowActions = useCallback(
(record: AssetI): RowAction[] => {
return [
RowDeleteAction({
hidden: !user.isStaff(),
onClick: () => {
setSelectedAsset(record.pk);
deleteAsset.open();
}
})
];
},
[user]
);
const newAssetFields: ApiFormFieldSet = useMemo(() => {
return {
asset: {},
description: {}
};
}, []);
const deleteAsset = useDeleteApiFormModal({
url: ApiEndpoints.report_asset,
pk: selectedAsset,
title: t`Delete Asset`,
table: table
});
const newAsset = useCreateApiFormModal({
url: ApiEndpoints.report_asset,
title: t`Add Asset`,
fields: newAssetFields,
table: table
});
const tableActions: ReactNode[] = useMemo(() => {
return [
<AddItemButton
key='add-asset'
onClick={() => newAsset.open()}
tooltip={t`Add asset`}
hidden={!user.isStaff()}
/>
];
}, [user]);
return (
<>
{newAsset.modal}
{deleteAsset.modal}
<Alert icon={<IconInfoCircle />} title={t`Assets`}>
<Text>{t`Assets are files (such as images) which can be used when rendering reports and labels.`}</Text>
</Alert>
<InvenTreeTable
url={apiUrl(ApiEndpoints.report_asset)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
tableActions: tableActions,
enableSearch: false,
enableFilters: false
}}
/>
</>
);
}

View File

@ -15,8 +15,9 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import useTable from '@lib/hooks/UseTable';
import { type ApiFormModalProps, getDetailUrl } from '@lib/index';
import type { ApiFormModalProps } from '@lib/index';
import type { TableColumn, TableState } from '@lib/types/Tables';
import { IconUsersGroup } from '@tabler/icons-react';
import { useNavigate } from 'react-router-dom';

View File

@ -0,0 +1,241 @@
import { AddItemButton } from '@lib/components/AddItemButton';
import {
type RowAction,
RowDeleteAction,
RowEditAction
} from '@lib/components/RowActions';
import { DetailDrawer } from '@lib/components/nav/DetailDrawer';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import {
Alert,
Group,
LoadingOverlay,
Stack,
Text,
Title
} from '@mantine/core';
import { IconFileCode, IconInfoCircle } from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
CodeEditor,
TemplateEditor
} from '../../components/editors/TemplateEditor';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import { DescriptionColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import type { TemplateI } from './TemplateTable';
export type SnippetI = {
pk: number;
snippet: string;
description: string;
};
export function SnippetDrawer({
id
}: Readonly<{
id: string | number;
}>) {
const {
instance: snippet,
instanceQuery: { isFetching, error }
} = useInstance<SnippetI>({
endpoint: ApiEndpoints.report_snippet,
hasPrimaryKey: true,
pk: id
});
const filename = useMemo(
() => snippet?.snippet?.split('/').pop() ?? '',
[snippet]
);
if (isFetching) {
return <LoadingOverlay visible={true} />;
}
if (error || !snippet) {
return (
<Text>
{(error as any)?.response?.status === 404 ? (
<Trans>Snippet not found</Trans>
) : (
<Trans>An error occurred while fetching snippet details</Trans>
)}
</Text>
);
}
return (
<Stack gap='xs' style={{ display: 'flex', flex: '1' }}>
<Group justify='left'>
<Title order={4}>{filename}</Title>
</Group>
<TemplateEditor
templateUrl={apiUrl(ApiEndpoints.report_snippet, id)}
template={snippet as unknown as TemplateI}
fileField='snippet'
editors={[CodeEditor]}
previewAreas={[]}
/>
</Stack>
);
}
export default function SnippetTable() {
const table = useTable('report-snippet');
const navigate = useNavigate();
const user = useUserState();
const openDetailDrawer = useCallback((pk: number) => navigate(`${pk}/`), []);
const columns: TableColumn<SnippetI>[] = useMemo(() => {
return [
{
accessor: 'snippet',
title: t`Snippet`,
sortable: false,
switchable: false,
render: (record: SnippetI) => {
if (!record.snippet) {
return '-';
}
return <AttachmentLink attachment={record.snippet} />;
},
noContext: true
},
DescriptionColumn({
accessor: 'description',
sortable: false,
switchable: false
})
];
}, []);
const [selectedSnippet, setSelectedSnippet] = useState<number>(-1);
const rowActions = useCallback(
(record: SnippetI): RowAction[] => {
return [
{
title: t`Modify`,
tooltip: t`Modify snippet file`,
icon: <IconFileCode />,
onClick: () => openDetailDrawer(record.pk),
hidden: !user.isStaff()
},
RowEditAction({
hidden: !user.isStaff(),
onClick: () => {
setSelectedSnippet(record.pk);
editSnippet.open();
}
}),
RowDeleteAction({
hidden: !user.isStaff(),
onClick: () => {
setSelectedSnippet(record.pk);
deleteSnippet.open();
}
})
];
},
[user]
);
const editSnippetFields: ApiFormFieldSet = useMemo(() => {
return {
description: {}
};
}, []);
const newSnippetFields: ApiFormFieldSet = useMemo(() => {
return {
snippet: {},
description: {}
};
}, []);
const editSnippet = useEditApiFormModal({
url: ApiEndpoints.report_snippet,
pk: selectedSnippet,
title: t`Edit Snippet`,
fields: editSnippetFields,
onFormSuccess: (record: any) => table.updateRecord(record)
});
const deleteSnippet = useDeleteApiFormModal({
url: ApiEndpoints.report_snippet,
pk: selectedSnippet,
title: t`Delete Snippet`,
table: table
});
const newSnippet = useCreateApiFormModal({
url: ApiEndpoints.report_snippet,
title: t`Add Snippet`,
fields: newSnippetFields,
onFormSuccess: (data) => {
table.refreshTable();
openDetailDrawer(data.pk);
}
});
const tableActions: ReactNode[] = useMemo(() => {
return [
<AddItemButton
key='add-snippet'
onClick={() => newSnippet.open()}
tooltip={t`Add snippet`}
hidden={!user.isStaff()}
/>
];
}, [user]);
return (
<>
{newSnippet.modal}
{editSnippet.modal}
{deleteSnippet.modal}
<DetailDrawer
title={t`Edit Snippet`}
size={'90%'}
closeOnEscape={false}
renderContent={(id) => {
return <SnippetDrawer id={id ?? ''} />;
}}
/>
<Alert icon={<IconInfoCircle />} title={t`Snippets`}>
<Text>{t`Snippets are reusable pieces of HTML content that can be inserted into reports and labels.`}</Text>
</Alert>
<InvenTreeTable
url={apiUrl(ApiEndpoints.report_snippet)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
tableActions: tableActions,
enableSearch: false,
enableFilters: false,
onRowClick: (record) => openDetailDrawer(record.pk)
}}
/>
</>
);
}

View File

@ -22,8 +22,9 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import useTable from '@lib/hooks/UseTable';
import { type ApiFormModalProps, getDetailUrl } from '@lib/index';
import type { ApiFormModalProps } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn, TableState } from '@lib/types/Tables';
import { showNotification } from '@mantine/notifications';

View File

@ -16,7 +16,12 @@ import {
} from '../../forms/StockForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useUserState } from '../../states/UserState';
import { PartColumn, StatusColumn, StockColumn } from '../ColumnRenderers';
import {
IPNColumn,
PartColumn,
StatusColumn,
StockColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export default function InstalledItemsTable({
@ -61,6 +66,7 @@ export default function InstalledItemsTable({
PartColumn({
part: 'part_detail'
}),
IPNColumn({}),
StockColumn({
accessor: '',
title: t`Stock Item`,

View File

@ -1,3 +1,9 @@
import {
RowDeleteAction,
RowDuplicateAction,
RowEditAction,
RowViewAction
} from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
@ -6,10 +12,6 @@ import {
AddItemButton,
ModelType,
ProgressBar,
RowDeleteAction,
RowDuplicateAction,
RowEditAction,
RowViewAction,
UserRoles,
formatDecimal
} from '@lib/index';

View File

@ -1,38 +0,0 @@
/**
* Expose certain globals to the window object, so that they can be accessed by plugins,
* without requiring plugins to import these dependencies directly.
*/
export function loadWindowGlobals() {
// (window as any).React = React;
import('react').then((module) => {
window.React = module;
});
// (window as any).ReactDOM = ReactDOM;
import('react-dom').then((module) => {
window.ReactDOM = module;
});
// (window as any).ReactDOMClient = ReactDOMClient;
import('react-dom/client').then((module) => {
window.ReactDOMClient = module;
});
// (window as any).MantineCore = MantineCore;
import('@mantine/core').then((module) => {
window.MantineCore = module;
});
// (window as any).MantineNotifications = MantineNotifications;
import('@mantine/notifications').then((module) => {
window.MantineNotifications = module;
});
import('@lingui/core').then((module) => {
window.LinguiCore = module;
});
import('@lingui/react').then((module) => {
window.LinguiReact = module;
});
}

View File

@ -75,8 +75,8 @@ test('Plugins - User Settings', async ({ browser }) => {
await navigate(page, 'settings/user/');
await loadTab(page, 'Plugin Settings');
// User settings for the "Sample Plugin" should be visible
await page.getByRole('button', { name: 'Sample Plugin' }).click();
// User settings for the "SampleIntegrationPlugin" should be visible
await page.getByRole('button', { name: 'SampleIntegrationPlugin' }).click();
await page.getByText('User Setting 1').waitFor();
await page.getByText('User Setting 2').waitFor();

View File

@ -254,6 +254,37 @@ test('Settings - Admin', async ({ browser }) => {
await loadTab(page, 'Category Parameters');
await loadTab(page, 'Label Templates');
await loadTab(page, 'Report Templates');
// Check the "report snippets" panel
await loadTab(page, 'Report Snippets');
await page
.getByText(
'Snippets are reusable pieces of HTML content that can be inserted into reports and labels.'
)
.waitFor();
// Launch the dialog to upload a new snippet
await page.getByLabel('action-button-add-snippet').click();
await page.getByText('Add Snippet', { exact: true }).waitFor();
await page.locator('input[type="file"]').waitFor({ state: 'attached' });
await page.getByLabel('text-field-description').waitFor();
await page.getByRole('button', { name: 'Cancel' }).click();
// Check the "report assets" panel
await loadTab(page, 'Report Assets');
await page
.getByText(
'Assets are files (such as images) which can be used when rendering reports and labels.'
)
.waitFor();
// Launch the dialog to upload a new asset
await page.getByLabel('action-button-add-asset').click();
await page.getByText('Add Asset', { exact: true }).waitFor();
await page.locator('input[type="file"]').waitFor({ state: 'attached' });
await page.getByLabel('text-field-description').waitFor();
await page.getByRole('button', { name: 'Cancel' }).click();
await loadTab(page, 'Plugins');
// Adjust some "location type" items

View File

@ -60,12 +60,25 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/code-frame@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-8.0.0.tgz#f374ea9392011ffecf223805dd0d9315606e5bbf"
integrity sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==
dependencies:
"@babel/helper-validator-identifier" "^8.0.0"
js-tokens "^10.0.0"
"@babel/compat-data@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
"@babel/core@^7.17.7", "@babel/core@^7.20.12", "@babel/core@^7.21.0", "@babel/core@^7.23.9", "@babel/core@^7.29.0", "@babel/core@^7.29.7":
"@babel/compat-data@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-8.0.0.tgz#e780eb6052d8ceeaeadd3a6cd82dda470a945622"
integrity sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==
"@babel/core@^7.17.7", "@babel/core@^7.20.12", "@babel/core@^7.21.0", "@babel/core@^7.23.9", "@babel/core@^7.29.0":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
@ -86,6 +99,28 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-8.0.1.tgz#dcb133d3ddcfb2b5a6558da03bf240319da0b8c8"
integrity sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==
dependencies:
"@babel/code-frame" "^8.0.0"
"@babel/generator" "^8.0.0"
"@babel/helper-compilation-targets" "^8.0.0"
"@babel/helpers" "^8.0.0"
"@babel/parser" "^8.0.0"
"@babel/template" "^8.0.0"
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@types/gensync" "^1.0.5"
convert-source-map "^2.0.0"
empathic "^2.0.1"
gensync "^1.0.0-beta.2"
import-meta-resolve "^4.2.0"
json5 "^2.2.3"
obug "^2.1.1"
semver "^7.7.3"
"@babel/generator@^7.21.1", "@babel/generator@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
@ -97,12 +132,24 @@
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz#c70fe3c6ecbdc3fd2dd1b0f498428b88b82ce47f"
integrity sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==
"@babel/generator@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-8.0.0.tgz#24b7b53a74fa8e74cc2377e054fecef4dcdada96"
integrity sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==
dependencies:
"@babel/types" "^7.29.7"
"@babel/parser" "^8.0.0"
"@babel/types" "^8.0.0"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
"@types/jsesc" "^2.5.0"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz#e094d0ea95f319cdc399fe25ec82e4b3dddb8461"
integrity sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==
dependencies:
"@babel/types" "^8.0.0"
"@babel/helper-compilation-targets@^7.29.7":
version "7.29.7"
@ -115,31 +162,47 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz#6eddf286f2ec418f740c91d60a83347c55838ddd"
integrity sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==
"@babel/helper-compilation-targets@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz#63f230f427c9ea82323d829c935f6190d93b3736"
integrity sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-member-expression-to-functions" "^7.29.7"
"@babel/helper-optimise-call-expression" "^7.29.7"
"@babel/helper-replace-supers" "^7.29.7"
"@babel/helper-skip-transparent-expression-wrappers" "^7.29.7"
"@babel/traverse" "^7.29.7"
semver "^6.3.1"
"@babel/compat-data" "^8.0.0"
"@babel/helper-validator-option" "^8.0.0"
browserslist "^4.24.0"
lru-cache "^11.0.0"
semver "^7.7.3"
"@babel/helper-create-class-features-plugin@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz#d8c5f55a3741a5f7d526bb4bfc361805601ef954"
integrity sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==
dependencies:
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-member-expression-to-functions" "^8.0.0"
"@babel/helper-optimise-call-expression" "^8.0.0"
"@babel/helper-replace-supers" "^8.0.1"
"@babel/helper-skip-transparent-expression-wrappers" "^8.0.0"
"@babel/traverse" "^8.0.0"
semver "^7.7.3"
"@babel/helper-globals@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
"@babel/helper-member-expression-to-functions@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz#8dbdb3ce0b5c487e1aec10e13c9a43a500814df8"
integrity sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==
"@babel/helper-globals@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-8.0.0.tgz#c93789d6c1c2f1b65a97a07515c471a049559bf1"
integrity sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==
"@babel/helper-member-expression-to-functions@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz#c097e24f7aeb60ab08966ef6c2127fc8ee32449e"
integrity sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.29.7":
version "7.29.7"
@ -149,6 +212,14 @@
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-module-imports@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz#b6b16c75f80616b545f72e842d8224b981fd41d5"
integrity sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==
dependencies:
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/helper-module-transforms@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
@ -158,50 +229,79 @@
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-optimise-call-expression@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz#77b0b5b94f1997fa9d6e3125f445227b1faf9d85"
integrity sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==
"@babel/helper-module-transforms@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-8.0.1.tgz#e8cf988892d7d8c774a9072dca0962f94b606bb6"
integrity sha512-UgAhl1kqiW5ciE0yCXqqvnb4H2n3IELJ7lIIQRezwDPilPEZX5i+Rvbja9MFTkwUn2biEiSMeV31aUzR4Lwakw==
dependencies:
"@babel/types" "^7.29.7"
"@babel/helper-module-imports" "^8.0.0"
"@babel/helper-validator-identifier" "^8.0.0"
"@babel/traverse" "^8.0.0"
"@babel/helper-optimise-call-expression@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz#dc1118493494fbfc3bb24b490550673908a99b6c"
integrity sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==
dependencies:
"@babel/types" "^8.0.0"
"@babel/helper-plugin-utils@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4"
integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==
"@babel/helper-replace-supers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz#bc3c3964329043c79112e513c1b198f16589ac21"
integrity sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.29.7"
"@babel/helper-optimise-call-expression" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-plugin-utils@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz#d1c9d8ae225902c597deac8c79ace4227feac8af"
integrity sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==
"@babel/helper-skip-transparent-expression-wrappers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz#50c95c7e4c4f54936cfa0116428edc559862d551"
integrity sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==
"@babel/helper-replace-supers@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz#c9b23aba6b9aff16cacac135052e790cde8d7d5b"
integrity sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-member-expression-to-functions" "^8.0.0"
"@babel/helper-optimise-call-expression" "^8.0.0"
"@babel/traverse" "^8.0.0"
"@babel/helper-skip-transparent-expression-wrappers@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz#27a6db3fb4ed17d2b32dbb69c4b30be346d8e8a6"
integrity sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==
dependencies:
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/helper-string-parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
"@babel/helper-string-parser@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz#4d47d9ad35d0f5bd7d202b348bf558328a347977"
integrity sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==
"@babel/helper-validator-identifier@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
"@babel/helper-validator-identifier@^8.0.0":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.2.tgz#5527f2e24e5a9f4de7426dca448031749691bb6f"
integrity sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==
"@babel/helper-validator-option@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
"@babel/helper-validator-option@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz#53307a6882254fa8d940017a701a8f7ec603a1a9"
integrity sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==
"@babel/helpers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
@ -210,6 +310,14 @@
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helpers@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-8.0.0.tgz#2c612d4b7fdbd5f1d2fa7321015e36b02d1a2d79"
integrity sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==
dependencies:
"@babel/template" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.0", "@babel/parser@^7.23.9", "@babel/parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
@ -217,41 +325,62 @@
dependencies:
"@babel/types" "^7.29.7"
"@babel/plugin-syntax-jsx@^7.16.7", "@babel/plugin-syntax-jsx@^7.29.7":
"@babel/parser@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-8.0.0.tgz#31f6860840277dc1c6d6f8b67bf74e0ccaa5df0a"
integrity sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==
dependencies:
"@babel/types" "^8.0.0"
"@babel/plugin-syntax-jsx@^7.16.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz#622c16f9ad63782fe6e83dadc7e40330744b7f1e"
integrity sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.23.3", "@babel/plugin-syntax-typescript@^7.29.7":
"@babel/plugin-syntax-jsx@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz#ab457699bd42e3071541fdc2dbade25049f2cc2f"
integrity sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==
dependencies:
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.23.3":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz#7c29388932313ed58413a0343048d75d92fb5b24"
integrity sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-transform-modules-commonjs@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz#70e6835abf2663dafbe94b8ef1f51de7351ef135"
integrity sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==
"@babel/plugin-syntax-typescript@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-8.0.1.tgz#c02d0096b297bdd4dbd32b63dbac44c0f195b82f"
integrity sha512-YBpIeuOZSUZx7RGH/U+dIAsHDHyojBVDRHNBUgOiUDS5IIcgBm1uyu9xs/2kM27B/bmDfOxzJ9k8cU2QuOwGIA==
dependencies:
"@babel/helper-module-transforms" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-react-display-name@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz#bf161a6d750267b79db7ff6f8fb89c3369b02df3"
integrity sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==
"@babel/plugin-transform-modules-commonjs@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz#ae8c414da671dc7ac4bb4b502a2714841ab84402"
integrity sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-module-transforms" "^8.0.1"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-react-jsx-development@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz#64e6aacb5cb43b9e80d3d5f19ddefc158a624f09"
integrity sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==
"@babel/plugin-transform-react-display-name@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz#da70220bacd0abcfb3a5fa4af89acde94d891fbc"
integrity sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==
dependencies:
"@babel/plugin-transform-react-jsx" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-react-jsx-development@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz#11fa2b7af26ceaafc47a3cc6e2bd720bba96fc04"
integrity sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==
dependencies:
"@babel/plugin-transform-react-jsx" "^8.0.1"
"@babel/plugin-transform-react-jsx-self@^7.27.1":
version "7.29.7"
@ -267,64 +396,68 @@
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-transform-react-jsx@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz#3d16a0e5773f079400a8c82a190709cdf92ee204"
integrity sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==
"@babel/plugin-transform-react-jsx@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz#0b7192bd35e5ca68d1324cd9347f5990f296177f"
integrity sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-module-imports" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-syntax-jsx" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-module-imports" "^8.0.0"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-syntax-jsx" "^8.0.1"
"@babel/types" "^8.0.0"
"@babel/plugin-transform-react-pure-annotations@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz#76445c90112dd0a7371b63264563bfa9a4fcd6e3"
integrity sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==
"@babel/plugin-transform-react-pure-annotations@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz#95d5bf8dab5ce0631b320b75ce3c6a6e550c356a"
integrity sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-typescript@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz#f0449c3df7037bbe232043476851c38f5e4a7615"
integrity sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==
"@babel/plugin-transform-typescript@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-8.0.1.tgz#805b11cae5334b4346640286950003c897f24b72"
integrity sha512-0Svqp3413Eg0GElldykF/T7SNsxQO5YVGD70fZyAdZTnX8WRgcopmbiU7GTa5xY5ZnJcEpNbfns8/GjX+/1yeA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-create-class-features-plugin" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-skip-transparent-expression-wrappers" "^7.29.7"
"@babel/plugin-syntax-typescript" "^7.29.7"
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-create-class-features-plugin" "^8.0.1"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/helper-skip-transparent-expression-wrappers" "^8.0.0"
"@babel/plugin-syntax-typescript" "^8.0.1"
"@babel/preset-react@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.29.7.tgz#2ed18366e38c2081bbf1760dc01e88fa5674eb17"
integrity sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==
"@babel/preset-react@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-8.0.1.tgz#0cb1802ff37a96faf983b5e781270f2752bb3476"
integrity sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
"@babel/plugin-transform-react-display-name" "^7.29.7"
"@babel/plugin-transform-react-jsx" "^7.29.7"
"@babel/plugin-transform-react-jsx-development" "^7.29.7"
"@babel/plugin-transform-react-pure-annotations" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/helper-validator-option" "^8.0.0"
"@babel/plugin-transform-react-display-name" "^8.0.1"
"@babel/plugin-transform-react-jsx" "^8.0.1"
"@babel/plugin-transform-react-jsx-development" "^8.0.1"
"@babel/plugin-transform-react-pure-annotations" "^8.0.1"
"@babel/preset-typescript@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz#de9be1f47b785c979ec7b3a71f4cd8bae5267b62"
integrity sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==
"@babel/preset-typescript@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-8.0.1.tgz#603e709db117e5fafef296de087e8a0a7679a543"
integrity sha512-qrPhQIN1NLrPmzgazF9XKQqXrOcp/WJly+K+6ReFonn24FZqRJO7clxOJo6Ni75L+2vAqI3cHVU2OJLBxoPp5A==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
"@babel/plugin-syntax-jsx" "^7.29.7"
"@babel/plugin-transform-modules-commonjs" "^7.29.7"
"@babel/plugin-transform-typescript" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/helper-validator-option" "^8.0.0"
"@babel/plugin-transform-modules-commonjs" "^8.0.1"
"@babel/plugin-transform-typescript" "^8.0.1"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.29.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.7.tgz#12022450c45a4da6d8d8287b18a4ff2ddb23f768"
integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==
"@babel/runtime@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-8.0.0.tgz#d7bd513e6843662346552c2798ab895716cf97f2"
integrity sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==
"@babel/template@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
@ -334,6 +467,15 @@
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/template@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-8.0.0.tgz#18c12626b0d6d23a0443771655864ed7566a3452"
integrity sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==
dependencies:
"@babel/code-frame" "^8.0.0"
"@babel/parser" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/traverse@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
@ -347,6 +489,19 @@
"@babel/types" "^7.29.7"
debug "^4.3.1"
"@babel/traverse@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-8.0.0.tgz#0473589140c796d13e27641ab0783069f0fe7512"
integrity sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw==
dependencies:
"@babel/code-frame" "^8.0.0"
"@babel/generator" "^8.0.0"
"@babel/helper-globals" "^8.0.0"
"@babel/parser" "^8.0.0"
"@babel/template" "^8.0.0"
"@babel/types" "^8.0.0"
obug "^2.1.1"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.28.2", "@babel/types@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
@ -355,6 +510,14 @@
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/types@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-8.0.0.tgz#b518f1ef7f9838bffdca4e123b449d3f644f4494"
integrity sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==
dependencies:
"@babel/helper-string-parser" "^8.0.0"
"@babel/helper-validator-identifier" "^8.0.0"
"@codecov/bundler-plugin-core@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-2.0.1.tgz#bdbeb575339a25b330178154eee6df9dcb01f31e"
@ -470,9 +633,9 @@
crelt "^1.0.5"
"@codemirror/search@^6.0.0", "@codemirror/search@^6.6.0":
version "6.7.0"
resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.7.0.tgz#c6bea0ab0e882395d03a760b1a7d3959b1f3a4ac"
integrity sha512-ZvGm99wc/s2cITtMT15LFdn8aH/aS+V+DqyGq/N5ZlV5vWtH+nILvC2nw0zX7ByNoHHDZ2IxxdW38O0tc5nVHg==
version "6.7.1"
resolved "https://registry.yarnpkg.com/@codemirror/search/-/search-6.7.1.tgz#2523a762871d18ad982edc2d4b2fa1da483b0392"
integrity sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==
dependencies:
"@codemirror/state" "^6.0.0"
"@codemirror/view" "^6.37.0"
@ -803,26 +966,26 @@
integrity sha512-wGnAPhfzivDwBWYmEG8MSrEXPruoiMMo48NnsRkj1NZkoaawgOijPNAiSHKMYEoCsqTBSgLTzL6EqTTWGaUR4w==
"@fullcalendar/core@^6.1.20":
version "6.1.20"
resolved "https://registry.yarnpkg.com/@fullcalendar/core/-/core-6.1.20.tgz#e4cfa767a9b8b7dad015b33fcb8acb0fac986a85"
integrity sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==
version "6.1.21"
resolved "https://registry.yarnpkg.com/@fullcalendar/core/-/core-6.1.21.tgz#e8b0ed592a60aba44499802fefdbb1fcc91b0a07"
integrity sha512-t3u/+sqh3Iq7TWtUnVLcGDUE6OWZh0UD3c04bI/l7lSLAgAKr3kngBmhHiQD1QXpwC8ZN5iNqG7a7gOVixhSKQ==
dependencies:
preact "~10.12.1"
"@fullcalendar/daygrid@^6.1.20":
version "6.1.20"
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-6.1.20.tgz#fcf33af4ea908b46fd755414278341811e01bdf8"
integrity sha512-AO9vqhkLP77EesmJzuU+IGXgxNulsA8mgQHynclJ8U70vSwAVnbcLG9qftiTAFSlZjiY/NvhE7sflve6cJelyQ==
version "6.1.21"
resolved "https://registry.yarnpkg.com/@fullcalendar/daygrid/-/daygrid-6.1.21.tgz#70eef7af5951b35a5f0380b15e32e5389ae029bc"
integrity sha512-QYb1y40RGYLlOxKpYWg8O+7njEnKnFG8Tt7qjnubJGR35s1phQg67E+81y2TyAbbm59p2JFOCXGDk9t6KDujIA==
"@fullcalendar/interaction@^6.1.20":
version "6.1.20"
resolved "https://registry.yarnpkg.com/@fullcalendar/interaction/-/interaction-6.1.20.tgz#a73a53af5f93ac261f864b025ae80201609f1cd7"
integrity sha512-p6txmc5txL0bMiPaJxe2ip6o0T384TyoD2KGdsU6UjZ5yoBlaY+dg7kxfnYKpYMzEJLG58n+URrHr2PgNL2fyA==
version "6.1.21"
resolved "https://registry.yarnpkg.com/@fullcalendar/interaction/-/interaction-6.1.21.tgz#e7fd36d7d328a02e1d7a1dcc174db7650a6edf31"
integrity sha512-WPYpqtljDWmU0Xm2cOtFrLlocgxv7cgkOppj34Q6OUUat8a6Cnd6kYo2JR+irP223PE5lBYHFNp1qh7SIpJc0w==
"@fullcalendar/react@^6.1.20":
version "6.1.20"
resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-6.1.20.tgz#7f2a8ede416364ab3b426c8e7c6739ec0ec476d0"
integrity sha512-1w0pZtceaUdfAnxMSCGHCQalhi+mR1jOe76sXzyAXpcPz/Lf0zHSdcGK/U2XpZlnQgQtBZW+d+QBnnzVQKCxAA==
version "6.1.21"
resolved "https://registry.yarnpkg.com/@fullcalendar/react/-/react-6.1.21.tgz#6f396ab7460d3ad9d07f4c32b1ac483b9b21120f"
integrity sha512-TLpmGUd5k/PMdCh8XbeFC9PW9wuGvMms1oCxWgXyjK3EFPXAAd0PLfcvwKdyxoAS5eK1E4RJFkjMHvsYHpimcg==
"@github/webauthn-json@^2.1.1":
version "2.1.1"
@ -1053,19 +1216,19 @@
"@lingui/core" "5.9.5"
"@mantine/carousel@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/carousel/-/carousel-9.2.1.tgz#bc470ae1c2801d66b40de9d1e76eee869995fb1c"
integrity sha512-b0ZBDnKhzztEFWNWbbFpPMqaV/4k/lclX8KB0PfU+qhu3Fa6wjS1VIn4XLZu9N3vgkzPkNXwDzpcwFShA9Uvqw==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/carousel/-/carousel-9.3.2.tgz#1044f49a416e7c0ab89bd43ed8bd1bdf487ecd4c"
integrity sha512-eQAmaeB2xHYEhNnGwKqtqlInnGgcrQATe310GdD1wL7JXuAoYJswdFqlMXXgGPNfrx6wxf1JsIay/mIlHvs4eQ==
"@mantine/charts@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/charts/-/charts-9.2.1.tgz#64811e07c1094b2b9bc67f04230e5dcebfe76be1"
integrity sha512-AVP0VEfcsbWwLBeU8rbEsN2gz3GebQECTreRq5AJ0Z5V1eWmTO7XFkRP9C5C6oHnkY4Vppa1x8cRYgyTsc/YbQ==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/charts/-/charts-9.3.2.tgz#4d4842875a17da3004cf8e34e9c66fecdf13913c"
integrity sha512-G3cFLLxmmRr0ij5fOHeO/jqR/U+DnMNZy0S7GEvs4lIzDRl871TDb1BogZyeHUGKuLnsLBYpTA719IcmswYGMw==
"@mantine/core@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-9.2.1.tgz#6bed4beac0b8c5f385f8923e4f158eb7472b633d"
integrity sha512-CicPg9i2dM2pGp1jj+dMiR/63OFDsPjgJke4v5+0nbfJ+C7gn4C+7ltrp4RIETDMZHcj0fFuDRG0qtbiyBxvWA==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/core/-/core-9.3.2.tgz#cb23bea516281d96ad86e543827580a84820ce05"
integrity sha512-Upy/Z9Sj2eW2dGrFgUy/2kISVsxMTBYTDfP2TFdsIA3PdPSBkdqfSOPX/ug3d3F3a4bnwQSqcO6+aPEpBIh8dg==
dependencies:
"@floating-ui/react" "^0.27.19"
clsx "^2.1.1"
@ -1074,62 +1237,62 @@
type-fest "^5.6.0"
"@mantine/dates@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-9.2.1.tgz#47c9eb8a854a7f1e6fa3993e9966babb793e9e4a"
integrity sha512-cyRC8bnZ6W+SzQf/RM+/eDeWhZTZF148z+OcgqiERdkAujh0q88Ddybv/Hshv1EOf0rCe6oiHSZWxIxmUf7KAg==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-9.3.2.tgz#6dda1854fe97631348cea7e1cc1d4380041bf14b"
integrity sha512-MzHrXGoOb3rCnHBlsI/BPAjC8K5tZ4f8pzg66tsTsupVCQBaBpHSVatQwNRJ6K2JC9pyCyTa9BL803C8AiWycA==
dependencies:
clsx "^2.1.1"
"@mantine/dropzone@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-9.2.1.tgz#3003d745da2f08246fc055137d250beb4f743283"
integrity sha512-rgebEz2bUubqA5jQpsh0SZ9/4DJFnUSF+a4p8uzAVlgNfo6aU/r+I4lEsD8gzLKuxrfN0TgkwR1qmAjLWIy0yQ==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/dropzone/-/dropzone-9.3.2.tgz#884131edba1644d1a515e4cfe0af8098eabd5baf"
integrity sha512-wx/6wSVLVW+OfPU9FgkMkfnaUd7roPZG1zOlzE6EIRYIqsHciY3OAlrhQDpYnTkowsseJpXDg4JG788l5kNDUQ==
dependencies:
react-dropzone "15.0.0"
"@mantine/form@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-9.2.1.tgz#f75d82a16d82a1aa3105ff86328a032b6ff712bb"
integrity sha512-PV0dcbmsKhZkn3Ryztqp8Kb1N16v2nWXO71p4utTmN7E/lHWHO1ccj7Y72sIteWJo1YeNHzzkssaYRSnxgOvYA==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/form/-/form-9.3.2.tgz#4aed07aa4aae09fa96921101bde1892fcfbf120f"
integrity sha512-jWEXcx8hhddOsny+q/lFtVToNS3VBJYICrkfFMWJsN+WAZamnPItWTPLqulTYVp1T8uma9MsEZ1LX4kqWUFANA==
dependencies:
"@standard-schema/spec" "^1.1.0"
fast-deep-equal "^3.1.3"
klona "^2.0.6"
"@mantine/hooks@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-9.2.1.tgz#8f81303b5eab68b10f4b3d7c99795b70932bc2db"
integrity sha512-IX/ztVG9eWmQTRsN7G8odyW4JckNvN8qv5A2ULzXyazjtAKLuaUpuMz0c6XhRp10J0g4bVfV3rhrTgWeImqxqg==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-9.3.2.tgz#1a53767d3d2ec983c105498bf4e3805f08d1ddc3"
integrity sha512-jOjpUe0x1A/k3XUiu2/aaSCasXRI5ZKZOucm3ypwsvm9F2u8C1xzwBvagrzlbNZwJRF5vNYIJtCaT7NunPyc0A==
"@mantine/modals@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-9.2.1.tgz#257c09088372b38848fb08beeb1124ed3fbe63b3"
integrity sha512-YvZ85ZtMg6arFserkmmP18gJRD9ztLLT3vz8UrkwCdVwTGGr14X93hhS0UtR5X96ANXUzC8fU/S2ncQmNqw+NQ==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-9.3.2.tgz#d0c4e8d21bb5224230272cfdb2888dd63917420a"
integrity sha512-XorW2PcYU+DQQVZmY80OlwI/bgkBPzf9MscjEZep8Z3aowrlNpnJXFbu2RoPCPMwROU2nX4gt0HJP2OiuGr28A==
"@mantine/notifications@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-9.2.1.tgz#de99ed4fc64e05024cb5a66e2d619a248ac2e042"
integrity sha512-H6lSsKUPdWPYcXFeOcqqcegUl72Iua9yD369s/gchfDvI+PrL4IM5UuWNHTeABJ1eb9FbTOw8B5RDSMZjTZNSA==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-9.3.2.tgz#c608bb5af801c7091f9eb2c15be51d1ec8251363"
integrity sha512-mnda8hE/DizPi5Bbiwd2M2jXxfSZSUeayvnLYMbFweGQKXM+QRmX1ootyDRVqWew8paltewTNp84cWLjj/6fKw==
dependencies:
"@mantine/store" "9.2.1"
"@mantine/store" "9.3.2"
react-transition-group "4.4.5"
"@mantine/spotlight@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-9.2.1.tgz#4157cef5a9a1b9ac5f6025b57435a8dc484b58b9"
integrity sha512-D6/BJ8/ftUBwiSFGTtYUM/LqlBk40XZqZFlmEFRM8chDPzEId1O+5FU7mj8KapPR9zRgoAcdT4LMI6JaLp0c0A==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-9.3.2.tgz#3cab5fe280b1612577b8d0b18314ff287873e7ff"
integrity sha512-JAJhW51qvYNiPTEnX9ewZX521gKDsVsmaecuuR2iNyk0NbrOPB5SWF9ZZrPSMwFVtqnqYHziq5BLukzlf+kG/Q==
dependencies:
"@mantine/store" "9.2.1"
"@mantine/store" "9.3.2"
"@mantine/store@9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/store/-/store-9.2.1.tgz#27a3548c4cc1567baa2613490d7dcb9300b391ba"
integrity sha512-sBTHt9ilfSZAeXQlqFkm8nRm22RunhevxuOUtdSwS9HhuMuS8T27dRRgbdKH2oEFUbaccdQSy5bHbmGbEgVO8w==
"@mantine/store@9.3.2":
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/store/-/store-9.3.2.tgz#2b878741b04147fdaecb40fed0c157eefde95e3b"
integrity sha512-uNmvjkwfUHZJ5bvzQroLFvubuUAHjBM0rh69RBRsUCG5FR5K4f8y4SX8+lch0wS6UJuuvodsSIK8H5jvZuFSgQ==
"@mantine/vanilla-extract@^9.2.1":
version "9.2.1"
resolved "https://registry.yarnpkg.com/@mantine/vanilla-extract/-/vanilla-extract-9.2.1.tgz#ba86032eaaf278153d3dd110c8e4327457cc0606"
integrity sha512-5cNesX5kdmClWCMFqxDIVZF2JRCne2OrIj5UE3tuyGJAY3IGkchBnAf/bEx3c5QDSXxq4+6y97hs5gQBgODePA==
version "9.3.2"
resolved "https://registry.yarnpkg.com/@mantine/vanilla-extract/-/vanilla-extract-9.3.2.tgz#0ac3e777a1dbf30385929f26c702447b0906d714"
integrity sha512-e4m1hmdfAHqm8XF4jwU4b2UnuwTPcnVG7r8KM7B8i+A3d19rRTeWt7+wv10628RcYzU71eM0tk3zt3Iop77KWQ==
"@marijn/find-cluster-break@^1.0.0":
version "1.0.2"
@ -1234,11 +1397,11 @@
"@octokit/openapi-types" "^27.0.0"
"@playwright/test@^1.60.0":
version "1.60.0"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.60.0.tgz#e696c31427e8882851235cd556dc2490c3206d97"
integrity sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==
version "1.61.0"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.61.0.tgz#dcc3d43e581aab2985d70413309d89350e980ad8"
integrity sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==
dependencies:
playwright "1.60.0"
playwright "1.61.0"
"@reduxjs/toolkit@^1.9.0 || 2.x.x":
version "2.12.0"
@ -1396,59 +1559,59 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz#3671ce3f9b928d5c01f879792d5c0b60ae14d4ad"
integrity sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==
"@sentry-internal/browser-utils@10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-10.57.0.tgz#5bda27f6ded3529afbc764195443335a9e08f9da"
integrity sha512-tXObp954rMTSYKlbftjVXHtNl4t/6ssks3jkqyzmKb+PDPWzabGQO7sWwqVuTjT8Kx/8A3FmriS1bGmqxiJy3A==
"@sentry/browser-utils@10.58.0":
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/browser-utils/-/browser-utils-10.58.0.tgz#e5d17e78f54ecd9c671c312b8ac0823b92e9c98b"
integrity sha512-TzXrhZq3Llj6qPSv0ZVG5N5c7C6qNN/aRKJXhq2LombJrLwiQrWdgizp7zdHA0FGlZ7F5YpyRA2JIpkhvrFqYA==
dependencies:
"@sentry/core" "10.57.0"
"@sentry/core" "10.58.0"
"@sentry-internal/feedback@10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-10.57.0.tgz#846b9c424208f9ebe798c8abb2c0f4272cfc3c1d"
integrity sha512-ZcF4QhkqGX3iiQSXB2N0N3Awp+j5iqnDRu6PA/qyLFrWqH5ZiiAAgu59OLD9E6XAdg6iFtLYw19MAMZVK8qNOQ==
"@sentry/browser@10.58.0":
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-10.58.0.tgz#bd6243c9b3c20d4d04eecc0623237b05c723e344"
integrity sha512-Syf6h6HDwC15fk86+/0ql8ebwwJFw2wp+lvOX9GfLTJVOqrSILCrtLKNI+f4v/3w8mzImsv9ttJGGbUugLNvcA==
dependencies:
"@sentry/core" "10.57.0"
"@sentry/browser-utils" "10.58.0"
"@sentry/core" "10.58.0"
"@sentry/feedback" "10.58.0"
"@sentry/replay" "10.58.0"
"@sentry/replay-canvas" "10.58.0"
"@sentry-internal/replay-canvas@10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-10.57.0.tgz#4c0fa99628872fd2db6d6da0f63979642c2faa05"
integrity sha512-zsfa4JcfV0AEc9YhNxNabd5lSZL2Av84saAyexGAqcHs+67m9Gd0cGStOzMb/nCl7UAtmdP0aI+G7a3rcxxN/A==
"@sentry/core@10.58.0":
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-10.58.0.tgz#66180ae40b341b352e3932753d5eb71157a00d0e"
integrity sha512-bkIbh2c6dzwhrWn/FGWu7j8hf6TAat2XxpkGM91LiN09fLYUXIMwcohVsXqze5l2cq35TnvqmSROAbRNr27GVw==
"@sentry/feedback@10.58.0":
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/feedback/-/feedback-10.58.0.tgz#7d74a2750433c5e6420f1804c5deaf2f6cd71d63"
integrity sha512-VmIlR/0O0GXITbvgjPkQqd6yM0JDEk52WXv6Rs1kTdaIDU5h3Y64VDVN4MAbYVRHqSz7F1arjRRk2FkaKC7ZOQ==
dependencies:
"@sentry-internal/replay" "10.57.0"
"@sentry/core" "10.57.0"
"@sentry-internal/replay@10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-10.57.0.tgz#729e528d95a5314e0cb54e8e646143816e192a78"
integrity sha512-Wmnx/6ABynVH1iwuoNUqJNyjIUqsqoGML7qsyivBRKb5Wo2YQtPOQlQYfxfZSvWzGpcoSVdInkRjDssUQxQEQg==
dependencies:
"@sentry-internal/browser-utils" "10.57.0"
"@sentry/core" "10.57.0"
"@sentry/browser@10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-10.57.0.tgz#89d95bff41b6b2652a93f8caa9d6c2bc7b73e48e"
integrity sha512-s36AQy/CKXTfyY9Z+qUhzNomntZXgfs0rbaK7q9ffnFkqcPwzE8qQtVs58y3Suut56u+AhwSztgQtERcuZ5VIA==
dependencies:
"@sentry-internal/browser-utils" "10.57.0"
"@sentry-internal/feedback" "10.57.0"
"@sentry-internal/replay" "10.57.0"
"@sentry-internal/replay-canvas" "10.57.0"
"@sentry/core" "10.57.0"
"@sentry/core@10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-10.57.0.tgz#bda587a82f364ae9dc0be2aa06e721aea8502d05"
integrity sha512-kntItTA2kiT0YpL7encXaF6mkdZMB+y48lwj8w1wkfBpfJAC7sifdgrzLQZqmsqVNE3crg9VfufaAGA+78uFMg==
"@sentry/core" "10.58.0"
"@sentry/react@^10.57.0":
version "10.57.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-10.57.0.tgz#5c0e7e42f332b1b39ea4255ce1a9dbefb348c6bd"
integrity sha512-6QThwQ4XWQ2rwKZEVQ9P9WKl7JlowC7S5LpAvmMdrwlfJBpLDFOsM7tycnIvbXTXf0ZOOuLFPa4L4YYbdyNGmA==
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-10.58.0.tgz#f53a4e3aa36f7ed13a8fac6334c0aee081cea254"
integrity sha512-3FLRtnXrue30UZALrQ9wQZeuvVmZl/pTCA+RyPlaZ5GxcYTapN9CVbm1IvbQpK4w1bt80+JxaKM4HikvII6KpA==
dependencies:
"@sentry/browser" "10.57.0"
"@sentry/core" "10.57.0"
"@sentry/browser" "10.58.0"
"@sentry/core" "10.58.0"
"@sentry/replay-canvas@10.58.0":
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/replay-canvas/-/replay-canvas-10.58.0.tgz#a1ce955549738e815f2ffba5f4b86eb692ca98ba"
integrity sha512-ufFmaJ968DXGe6u9W/UqNVjCCTtAqT2bNtSu1jHAjIFFpWIuM80lcR33grK6SuGnFxceu5iJFaIW6JRyfbM0Sw==
dependencies:
"@sentry/core" "10.58.0"
"@sentry/replay" "10.58.0"
"@sentry/replay@10.58.0":
version "10.58.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-10.58.0.tgz#347eb26b86a458065172d880dc953d5a049eab42"
integrity sha512-EVasQNUenpwJksK9DI1TQ78YytpjLAhxn0UTiHqA3sU/s1fHK5XZdZJPr/9uEGedRoInIP0UIWmbOtOEX8HHDg==
dependencies:
"@sentry/browser-utils" "10.58.0"
"@sentry/core" "10.58.0"
"@sinclair/typebox@^0.27.8":
version "0.27.10"
@ -1585,6 +1748,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
"@types/gensync@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/gensync/-/gensync-1.0.5.tgz#1819eba537d036dcf4ec7e8c8f15fbfe938e99a0"
integrity sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
@ -1609,15 +1777,20 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jsesc@^2.5.0":
version "2.5.1"
resolved "https://registry.yarnpkg.com/@types/jsesc/-/jsesc-2.5.1.tgz#c34defc608ec94b68dc6a12a581b440942c6d503"
integrity sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==
"@types/marked@^4.0.7":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.2.tgz#e2e0ad02ebf5626bd215c5bae2aff6aff0ce9eac"
integrity sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==
"@types/node@*", "@types/node@^25.9.2":
version "25.9.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.2.tgz#fc8958e757994b71fee516f9634bdb03d1b19e9f"
integrity sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==
version "25.9.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-25.9.3.tgz#11dfe7a33e68fa5c560f0aa76cc5595621ef26b9"
integrity sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==
dependencies:
undici-types ">=7.24.0 <7.24.7"
@ -1821,7 +1994,7 @@
"@vanilla-extract/compiler" "^0.7.0"
"@vanilla-extract/integration" "^8.0.9"
"@vitejs/plugin-react@^5.2.0":
"@vitejs/plugin-react@^5":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz#108bd0f566f288ce3566982df4eff137ded7b15f"
integrity sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==
@ -2494,6 +2667,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
empathic@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/empathic/-/empathic-2.0.1.tgz#37b1bede31093e04a03d2abce95ea323fee8ef49"
integrity sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==
error-ex@^1.3.1:
version "1.3.4"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414"
@ -2919,6 +3097,11 @@ import-fresh@^3.2.1, import-fresh@^3.3.0:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-meta-resolve@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz#08cb85b5bd37ecc8eb1e0f670dc2767002d43734"
integrity sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@ -3119,6 +3302,11 @@ js-sha256@^0.10.1:
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.10.1.tgz#b40104ba1368e823fdd5f41b66b104b15a0da60d"
integrity sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==
js-tokens@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-10.0.0.tgz#dffe7599b4a8bb7fe30aff8d0235234dffb79831"
integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@ -3274,14 +3462,14 @@ make-dir@^4.0.0:
semver "^7.5.3"
mantine-contextmenu@^9.2.1:
version "9.2.1"
resolved "https://registry.yarnpkg.com/mantine-contextmenu/-/mantine-contextmenu-9.2.1.tgz#d1d4dab0ca1eeaba7f1bba6cf2cad8cf31636709"
integrity sha512-oqFPT9qOQBPgf2eSuiujGQp38QgV0WsIIMxQkPfkjVc8FWAKcnXv7OGxGN1ctRrcMqUbsS34rjI0SvNdmFwG8A==
version "9.3.0"
resolved "https://registry.yarnpkg.com/mantine-contextmenu/-/mantine-contextmenu-9.3.0.tgz#e32348f79a3eb1a75778d84e3f3d3b5835510a0e"
integrity sha512-8Uy9eK9aTP/ba6VpLgMMjhOV6WI0Ye2AB6MwsaJTNWR82IkxzS3mx89UuG0xtNvVh2KH7xPiM598f/PIzmiXuQ==
mantine-datatable@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/mantine-datatable/-/mantine-datatable-9.2.2.tgz#2b1238ac8b115d2c76e6b3646b425a626d92fed4"
integrity sha512-pSWIAggUQC1I4lNUzwVoNm85CIkx8jst0syzRo8N9CrTHdc+Z2+8aFLfBniSHbF4nJyLE6nkeAcb8uvFdHbVfw==
version "9.3.0"
resolved "https://registry.yarnpkg.com/mantine-datatable/-/mantine-datatable-9.3.0.tgz#32cd4ca52d30aa3340a7f2e406d84809cfc62910"
integrity sha512-YCLC5VCRPUHDoDUvWgVY81bx17MDk3Gz38icOINe/QO2ZKASGxNzf/wiqV3Dv1N/BRO601GyVMHRH6LzFWVC7g==
marked@^4.1.0:
version "4.3.0"
@ -3639,17 +3827,17 @@ pkg-types@^2.3.0:
exsolve "^1.0.8"
pathe "^2.0.3"
playwright-core@1.60.0:
version "1.60.0"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.60.0.tgz#24e0d9cc4730713db5dffcace29b5e4696b1907a"
integrity sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==
playwright-core@1.61.0:
version "1.61.0"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.61.0.tgz#caf8078b2a39cd7738dc75ec11cb3b47f385c3f0"
integrity sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==
playwright@1.60.0:
version "1.60.0"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.60.0.tgz#89710863a51f21112633ef8b6b182594d3bfd7b5"
integrity sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==
playwright@1.61.0:
version "1.61.0"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.61.0.tgz#7082df3df08ffa82b11420ea5fae84a40bd16e3f"
integrity sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==
dependencies:
playwright-core "1.60.0"
playwright-core "1.61.0"
optionalDependencies:
fsevents "2.3.2"
@ -3775,9 +3963,9 @@ react-grid-layout@1.4.4:
resize-observer-polyfill "^1.5.1"
react-hook-form@^7.78.0:
version "7.78.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.78.0.tgz#b5a7d496d9077a71f36c8f2cacd3990464af2643"
integrity sha512-EEZqc+N23moyzTlz61Pj+JvcXo76ICkpfOZo8JZw+sM4+wLQGh6nI2Ms+PdMOYNluFu0ghlM7B8mCzhRYtJCnA==
version "7.79.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.79.0.tgz#dc965ecb161e67ea91ee46f00d870ea35871b26a"
integrity sha512-mhYp/MTmXvzYX6AJcJVko0rktoIhhmRnEouObj4wF5i/tCttgJvnp1+9wRkpITZjDTqpo4IOSJqu0dBlPlV/Lw==
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
@ -4086,10 +4274,10 @@ semver@^6.0.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3, semver@^7.5.4:
version "7.8.4"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.4.tgz#c73eceebae0616934be8dff28a7fd70757c8e696"
integrity sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==
semver@^7.5.3, semver@^7.5.4, semver@^7.7.3:
version "7.8.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69"
integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
set-blocking@^2.0.0:
version "2.0.0"