diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 62a3a6c43e..d5c5b2ddac 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -8,7 +8,7 @@ name: Frontend on: push: - branches-ignore: ["l10*", "dependabot/*", "backport/*"] + branches-ignore: ["l10*", "dependabot/**", "backport/**"] pull_request: branches-ignore: ["l10*"] diff --git a/.github/workflows/import_export.yaml b/.github/workflows/import_export.yaml index c251bab557..36f7f6d71d 100644 --- a/.github/workflows/import_export.yaml +++ b/.github/workflows/import_export.yaml @@ -7,7 +7,7 @@ name: Import / Export on: push: - branches-ignore: ["l10*", "dependabot/*", "backport/*"] + branches-ignore: ["l10*", "dependabot/**", "backport/**"] pull_request: branches-ignore: ["l10*"] diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index 9dab7c79a1..e3fe43fbde 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -4,7 +4,7 @@ name: QC on: push: - branches-ignore: ["l10*", "dependabot/*", "backport/*"] + branches-ignore: ["l10*", "dependabot/**", "backport/**"] pull_request: branches-ignore: ["l10*"] diff --git a/docs/docs/report/index.md b/docs/docs/report/index.md index 37124e13d6..fd863e20f8 100644 --- a/docs/docs/report/index.md +++ b/docs/docs/report/index.md @@ -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. diff --git a/docs/docs/report/report.md b/docs/docs/report/report.md index 935e977af9..716b481063 100644 --- a/docs/docs/report/report.md +++ b/docs/docs/report/report.md @@ -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: diff --git a/docs/docs/report/template_editor.md b/docs/docs/report/template_editor.md index b4fbddbf04..3008434944 100644 --- a/docs/docs/report/template_editor.md +++ b/docs/docs/report/template_editor.md @@ -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. diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index c0378b7396..d0fdaf62c0 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -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 diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index ba75e875d8..a187d73e51 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -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', ''), + ) diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index 603da825be..d542671e00 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -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 diff --git a/src/backend/InvenTree/company/serializers.py b/src/backend/InvenTree/company/serializers.py index 1aa191e966..809f06710f 100644 --- a/src/backend/InvenTree/company/serializers.py +++ b/src/backend/InvenTree/company/serializers.py @@ -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 diff --git a/src/backend/InvenTree/data_exporter/mixins.py b/src/backend/InvenTree/data_exporter/mixins.py index 0d58d0efff..af1b971db8 100644 --- a/src/backend/InvenTree/data_exporter/mixins.py +++ b/src/backend/InvenTree/data_exporter/mixins.py @@ -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): diff --git a/src/backend/InvenTree/importer/mixins.py b/src/backend/InvenTree/importer/mixins.py index 969f82afa2..9cd5401606 100644 --- a/src/backend/InvenTree/importer/mixins.py +++ b/src/backend/InvenTree/importer/mixins.py @@ -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): diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 7ef36f5609..04dc6e79f4 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -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. diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py index 10b944f40f..e72bd91de5 100644 --- a/src/backend/InvenTree/order/test_api.py +++ b/src/backend/InvenTree/order/test_api.py @@ -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}, }, ) diff --git a/src/backend/InvenTree/part/serializers.py b/src/backend/InvenTree/part/serializers.py index 051abd27a1..e8b2ecff29 100644 --- a/src/backend/InvenTree/part/serializers.py +++ b/src/backend/InvenTree/part/serializers.py @@ -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) diff --git a/src/backend/InvenTree/part/test_api.py b/src/backend/InvenTree/part/test_api.py index 011c700884..7752e95dd4 100644 --- a/src/backend/InvenTree/part/test_api.py +++ b/src/backend/InvenTree/part/test_api.py @@ -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, diff --git a/src/frontend/lib/types/Settings.tsx b/src/frontend/lib/types/Settings.tsx index 46e1b10abe..0c89b19883 100644 --- a/src/frontend/lib/types/Settings.tsx +++ b/src/frontend/lib/types/Settings.tsx @@ -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; } diff --git a/src/frontend/package.json b/src/frontend/package.json index 4ceefd58c5..318b3d0a66 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -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", diff --git a/src/frontend/src/components/editors/TemplateEditor/PdfPreview/PdfPreview.tsx b/src/frontend/src/components/editors/TemplateEditor/PdfPreview/PdfPreview.tsx index 5a0fea7cc2..3536f92c2e 100644 --- a/src/frontend/src/components/editors/TemplateEditor/PdfPreview/PdfPreview.tsx +++ b/src/frontend/src/components/editors/TemplateEditor/PdfPreview/PdfPreview.tsx @@ -33,7 +33,7 @@ export const PdfPreviewComponent: PreviewAreaComponent = forwardRef( } let preview = await api.post( - printingUrl, + printingUrl!, { items: [previewItem], template: template.pk diff --git a/src/frontend/src/components/editors/TemplateEditor/TemplateEditor.tsx b/src/frontend/src/components/editors/TemplateEditor/TemplateEditor.tsx index 6aafc01f54..ef5a0e4587 100644 --- a/src/frontend/src/components/editors/TemplateEditor/TemplateEditor.tsx +++ b/src/frontend/src/components/editors/TemplateEditor/TemplateEditor.tsx @@ -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) { - const { templateUrl, editors, previewAreas, template } = props; + const { + templateUrl, + editors, + previewAreas, + template, + fileField = 'template' + } = props; const editorRef = useRef(null); const previewRef = useRef(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(''); const [renderingErrors, setRenderingErrors] = useState(null); const [isPreviewLoading, setIsPreviewLoading] = useState(false); + const [isSaving, setIsSaving] = useState(false); const [editorValue, setEditorValue] = useState(editors[0].key); const [previewValue, setPreviewValue] = useState( - previewAreas[0].key + previewAreas[0]?.key ?? null ); const codeRef = useRef(undefined); @@ -131,12 +143,12 @@ export function TemplateEditor(props: Readonly) { 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) { }) .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) { [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) { }, [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 ( - + { @@ -282,7 +335,7 @@ export function TemplateEditor(props: Readonly) { keepMounted={false} style={{ minWidth: '300px', - width: '50%', + width: hasPreview ? '50%' : '100%', display: 'flex', flexDirection: 'column' }} @@ -301,29 +354,39 @@ export function TemplateEditor(props: Readonly) { })} - updatePreview(true, false), - disabled: !previewItem || isPreviewLoading - }, - { - key: 'preview_save', - name: t`Save & Reload Preview`, - tooltip: t`Save the current template and reload the preview`, - icon: IconDeviceFloppy, - onClick: () => updatePreview(hasSaveConfirmed), - disabled: !previewItem || isPreviewLoading - } - ]} - /> + {hasPreview ? ( + updatePreview(true, false), + disabled: !previewItem || isPreviewLoading + }, + { + key: 'preview_save', + name: t`Save & Reload Preview`, + tooltip: t`Save the current template and reload the preview`, + icon: IconDeviceFloppy, + onClick: () => updatePreview(hasSaveConfirmed), + disabled: !previewItem || isPreviewLoading + } + ]} + /> + ) : ( + + )} @@ -342,100 +405,102 @@ export function TemplateEditor(props: Readonly) { ))} - - - {previewAreas.map((PreviewArea) => ( - - {PreviewArea.name} - - ))} - - -
- setPreviewItem(value) - }} - /> -
+ + {previewAreas.map((PreviewArea) => ( + + {PreviewArea.name} + + ))} + - {previewAreas.map((PreviewArea) => ( - -
setPreviewItem(value) + }} + /> +
+ + {previewAreas.map((PreviewArea) => ( + - {/* @ts-ignore-next-line */} - +
+ {/* @ts-ignore-next-line */} + - {renderingErrors && ( - - setRenderingErrors(null)} - style={{ - position: 'absolute', - top: '10px', - right: '10px', - color: '#fff' - }} - variant='filled' - /> - } - title={t`Error rendering template`} - mx='10px' - > - - {renderingErrors.map((error, index) => ( - {error} - ))} - - - - )} -
-
- ))} -
+ {renderingErrors && ( + + setRenderingErrors(null)} + style={{ + position: 'absolute', + top: '10px', + right: '10px', + color: '#fff' + }} + variant='filled' + /> + } + title={t`Error rendering template`} + mx='10px' + > + + {renderingErrors.map((error, index) => ( + {error} + ))} + + + + )} + + + ))} + + )}
diff --git a/src/frontend/src/components/forms/fields/TreeField.tsx b/src/frontend/src/components/forms/fields/TreeField.tsx index 4ff583f395..a9a9165cdd 100644 --- a/src/frontend/src/components/forms/fields/TreeField.tsx +++ b/src/frontend/src/components/forms/fields/TreeField.tsx @@ -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 { diff --git a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx index 4233c12d1a..a0d7f2dcb0 100644 --- a/src/frontend/src/components/modals/AboutInvenTreeModal.tsx +++ b/src/frontend/src/components/modals/AboutInvenTreeModal.tsx @@ -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) => ( - + .map((entry: AboutLookupRef) => ( + {entry.title} diff --git a/src/frontend/src/components/nav/SearchDrawer.tsx b/src/frontend/src/components/nav/SearchDrawer.tsx index cb2066a903..d2e3a28c9b 100644 --- a/src/frontend/src/components/nav/SearchDrawer.tsx +++ b/src/frontend/src/components/nav/SearchDrawer.tsx @@ -564,9 +564,9 @@ export function SearchDrawer({ multiple defaultValue={searchQueries.map((q) => q.model)} > - {queryResults.map((query, idx) => ( + {queryResults.map((query) => ( 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 ; + return ( + + ); } 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 ; + return ( + + ); } export function MachineSettingList({ diff --git a/src/frontend/src/forms/BuildForms.tsx b/src/frontend/src/forms/BuildForms.tsx index 2f694c841c..39fa22579e 100644 --- a/src/frontend/src/forms/BuildForms.tsx +++ b/src/frontend/src/forms/BuildForms.tsx @@ -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( @@ -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({ diff --git a/src/frontend/src/forms/CommonFields.tsx b/src/frontend/src/forms/CommonFields.tsx index 07f16e0bb9..988fcb31a4 100644 --- a/src/frontend/src/forms/CommonFields.tsx +++ b/src/frontend/src/forms/CommonFields.tsx @@ -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; +}>): 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, diff --git a/src/frontend/src/forms/CompanyForms.tsx b/src/frontend/src/forms/CompanyForms.tsx index 14d3b6218f..bfd2ede4cc 100644 --- a/src/frontend/src/forms/CompanyForms.tsx +++ b/src/frontend/src/forms/CompanyForms.tsx @@ -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({}); @@ -95,14 +97,34 @@ export function useSupplierPartFields({ icon: }, primary: {}, - active: {} + active: {}, + duplicate: DuplicateField({ + originalId: duplicateSupplierPartId, + extraFields: { + copy_parameters: {} + } + }) }; + if (!duplicateSupplierPartId) { + delete fields.duplicate; + } + return fields; - }, [manufacturerId, manufacturerPartId, partId, part]); + }, [ + manufacturerId, + manufacturerPartId, + partId, + part, + duplicateSupplierPartId + ]); } -export function useManufacturerPartFields() { +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; } diff --git a/src/frontend/src/forms/PartForms.tsx b/src/frontend/src/forms/PartForms.tsx index 38219a924c..e0bfa28e0e 100644 --- a/src/frontend/src/forms/PartForms.tsx +++ b/src/frontend/src/forms/PartForms.tsx @@ -159,7 +159,7 @@ export function usePartFields({ fields.duplicate = { icon: , children: { - part: { + original: { value: duplicatePartInstance?.pk, hidden: true }, diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx index aaf4fa83d4..4776e668eb 100644 --- a/src/frontend/src/forms/PurchaseOrderForms.tsx +++ b/src/frontend/src/forms/PurchaseOrderForms.tsx @@ -316,7 +316,7 @@ export function usePurchaseOrderFields({ if (!!duplicateOrderId) { fields.duplicate = { children: { - order_id: { + original: { hidden: true, value: duplicateOrderId }, diff --git a/src/frontend/src/forms/ReturnOrderForms.tsx b/src/frontend/src/forms/ReturnOrderForms.tsx index 0fe1866b7b..463e7cc60a 100644 --- a/src/frontend/src/forms/ReturnOrderForms.tsx +++ b/src/frontend/src/forms/ReturnOrderForms.tsx @@ -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: {} } diff --git a/src/frontend/src/forms/SalesOrderForms.tsx b/src/frontend/src/forms/SalesOrderForms.tsx index 163861db24..90ec80c034 100644 --- a/src/frontend/src/forms/SalesOrderForms.tsx +++ b/src/frontend/src/forms/SalesOrderForms.tsx @@ -97,7 +97,7 @@ export function useSalesOrderFields({ if (!!duplicateOrderId) { fields.duplicate = { children: { - order_id: { + original: { hidden: true, value: duplicateOrderId }, diff --git a/src/frontend/src/forms/TransferOrderForms.tsx b/src/frontend/src/forms/TransferOrderForms.tsx index a80915e393..a3945fbec5 100644 --- a/src/frontend/src/forms/TransferOrderForms.tsx +++ b/src/frontend/src/forms/TransferOrderForms.tsx @@ -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: {} } }; } diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx index 14ade14584..1bbb000b3f 100644 --- a/src/frontend/src/main.tsx +++ b/src/frontend/src/main.tsx @@ -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( ); -// 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; diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx index ecbdddfae4..454225c5b5 100644 --- a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx +++ b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx @@ -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: , content: }, + { + name: 'snippets', + label: t`Report Snippets`, + icon: , + content: + }, + { + name: 'assets', + label: t`Report Assets`, + icon: , + content: + }, { 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', diff --git a/src/frontend/src/pages/Index/Settings/PluginSettingsGroup.tsx b/src/frontend/src/pages/Index/Settings/PluginSettingsGroup.tsx index 5b07a0601b..03ec15b477 100644 --- a/src/frontend/src/pages/Index/Settings/PluginSettingsGroup.tsx +++ b/src/frontend/src/pages/Index/Settings/PluginSettingsGroup.tsx @@ -25,11 +25,13 @@ function PluginSettingGroupItem({ }) { // Hide the accordion item if there are no settings for this plugin const [count, setCount] = useState(0); + const [doGet, setDoGet] = useState(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({ )} + {global ? ( - + ) : ( - + )} diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index 6b8d8e3459..7fbd0d3307 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -632,6 +632,7 @@ export default function BuildDetail() { const duplicateBuildOrderFields = useBuildOrderFields({ create: false, + duplicateBuildId: build.pk, modalId: 'duplicate-build-order' }); diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx index 2b779d4112..5765cc377f 100644 --- a/src/frontend/src/pages/company/CompanyDetail.tsx +++ b/src/frontend/src/pages/company/CompanyDetail.tsx @@ -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) { 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) { 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) { <> {editCompany.modal} {deleteCompany.modal} + {duplicateCompany.modal} ( 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((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; } })); }; diff --git a/src/frontend/src/tables/bom/UsedInTable.tsx b/src/frontend/src/tables/bom/UsedInTable.tsx index 28cb1f2060..407e5203a5 100644 --- a/src/frontend/src/tables/bom/UsedInTable.tsx +++ b/src/frontend/src/tables/bom/UsedInTable.tsx @@ -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'; diff --git a/src/frontend/src/tables/general/ParameterTable.tsx b/src/frontend/src/tables/general/ParameterTable.tsx index 996f53698d..05f3ce1227 100644 --- a/src/frontend/src/tables/general/ParameterTable.tsx +++ b/src/frontend/src/tables/general/ParameterTable.tsx @@ -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 diff --git a/src/frontend/src/tables/general/ParameterTemplateTable.tsx b/src/frontend/src/tables/general/ParameterTemplateTable.tsx index 035dfc3260..912c1a423d 100644 --- a/src/frontend/src/tables/general/ParameterTemplateTable.tsx +++ b/src/frontend/src/tables/general/ParameterTemplateTable.tsx @@ -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'; diff --git a/src/frontend/src/tables/machine/MachineListTable.tsx b/src/frontend/src/tables/machine/MachineListTable.tsx index d6e1e1d74a..e8c3b99948 100644 --- a/src/frontend/src/tables/machine/MachineListTable.tsx +++ b/src/frontend/src/tables/machine/MachineListTable.tsx @@ -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'; diff --git a/src/frontend/src/tables/part/PartTable.tsx b/src/frontend/src/tables/part/PartTable.tsx index c0d2df7ef0..fd3f124607 100644 --- a/src/frontend/src/tables/part/PartTable.tsx +++ b/src/frontend/src/tables/part/PartTable.tsx @@ -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]); diff --git a/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx b/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx index 0722b58894..20d4297661 100644 --- a/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx +++ b/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx @@ -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 diff --git a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx index 4e38368bce..b6e087fbf4 100644 --- a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx +++ b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx @@ -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, diff --git a/src/frontend/src/tables/settings/AssetTable.tsx b/src/frontend/src/tables/settings/AssetTable.tsx new file mode 100644 index 0000000000..a04ac10af0 --- /dev/null +++ b/src/frontend/src/tables/settings/AssetTable.tsx @@ -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[] = useMemo(() => { + return [ + { + accessor: 'asset', + title: t`Asset`, + sortable: false, + switchable: false, + render: (record: AssetI) => { + if (!record.asset) { + return '-'; + } + + return ; + }, + noContext: true + }, + DescriptionColumn({ + accessor: 'description', + sortable: false, + switchable: false + }) + ]; + }, []); + + const [selectedAsset, setSelectedAsset] = useState(-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 [ + newAsset.open()} + tooltip={t`Add asset`} + hidden={!user.isStaff()} + /> + ]; + }, [user]); + + return ( + <> + {newAsset.modal} + {deleteAsset.modal} + } title={t`Assets`}> + {t`Assets are files (such as images) which can be used when rendering reports and labels.`} + + + + ); +} diff --git a/src/frontend/src/tables/settings/GroupTable.tsx b/src/frontend/src/tables/settings/GroupTable.tsx index 656e177429..cafb9121d4 100644 --- a/src/frontend/src/tables/settings/GroupTable.tsx +++ b/src/frontend/src/tables/settings/GroupTable.tsx @@ -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'; diff --git a/src/frontend/src/tables/settings/SnippetTable.tsx b/src/frontend/src/tables/settings/SnippetTable.tsx new file mode 100644 index 0000000000..1abb3e2b13 --- /dev/null +++ b/src/frontend/src/tables/settings/SnippetTable.tsx @@ -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({ + endpoint: ApiEndpoints.report_snippet, + hasPrimaryKey: true, + pk: id + }); + + const filename = useMemo( + () => snippet?.snippet?.split('/').pop() ?? '', + [snippet] + ); + + if (isFetching) { + return ; + } + + if (error || !snippet) { + return ( + + {(error as any)?.response?.status === 404 ? ( + Snippet not found + ) : ( + An error occurred while fetching snippet details + )} + + ); + } + + return ( + + + {filename} + + + + + ); +} + +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[] = useMemo(() => { + return [ + { + accessor: 'snippet', + title: t`Snippet`, + sortable: false, + switchable: false, + render: (record: SnippetI) => { + if (!record.snippet) { + return '-'; + } + + return ; + }, + noContext: true + }, + DescriptionColumn({ + accessor: 'description', + sortable: false, + switchable: false + }) + ]; + }, []); + + const [selectedSnippet, setSelectedSnippet] = useState(-1); + + const rowActions = useCallback( + (record: SnippetI): RowAction[] => { + return [ + { + title: t`Modify`, + tooltip: t`Modify snippet file`, + icon: , + 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 [ + newSnippet.open()} + tooltip={t`Add snippet`} + hidden={!user.isStaff()} + /> + ]; + }, [user]); + + return ( + <> + {newSnippet.modal} + {editSnippet.modal} + {deleteSnippet.modal} + { + return ; + }} + /> + } title={t`Snippets`}> + {t`Snippets are reusable pieces of HTML content that can be inserted into reports and labels.`} + + openDetailDrawer(record.pk) + }} + /> + + ); +} diff --git a/src/frontend/src/tables/settings/UserTable.tsx b/src/frontend/src/tables/settings/UserTable.tsx index f93fbc2300..cd8c189511 100644 --- a/src/frontend/src/tables/settings/UserTable.tsx +++ b/src/frontend/src/tables/settings/UserTable.tsx @@ -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'; diff --git a/src/frontend/src/tables/stock/InstalledItemsTable.tsx b/src/frontend/src/tables/stock/InstalledItemsTable.tsx index 5ccbc10eef..ed3cfe1c16 100644 --- a/src/frontend/src/tables/stock/InstalledItemsTable.tsx +++ b/src/frontend/src/tables/stock/InstalledItemsTable.tsx @@ -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`, diff --git a/src/frontend/src/tables/stock/TransferOrderLineItemTable.tsx b/src/frontend/src/tables/stock/TransferOrderLineItemTable.tsx index 531ef4da69..c59fedceed 100644 --- a/src/frontend/src/tables/stock/TransferOrderLineItemTable.tsx +++ b/src/frontend/src/tables/stock/TransferOrderLineItemTable.tsx @@ -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'; diff --git a/src/frontend/src/window.tsx b/src/frontend/src/window.tsx deleted file mode 100644 index a6f3d19f3f..0000000000 --- a/src/frontend/src/window.tsx +++ /dev/null @@ -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; - }); -} diff --git a/src/frontend/tests/pui_plugins.spec.ts b/src/frontend/tests/pui_plugins.spec.ts index 210be09a2b..e00435a0a9 100644 --- a/src/frontend/tests/pui_plugins.spec.ts +++ b/src/frontend/tests/pui_plugins.spec.ts @@ -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(); diff --git a/src/frontend/tests/pui_settings.spec.ts b/src/frontend/tests/pui_settings.spec.ts index 71042ba5fa..dbd478c60d 100644 --- a/src/frontend/tests/pui_settings.spec.ts +++ b/src/frontend/tests/pui_settings.spec.ts @@ -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 diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index fbb2ec10ab..019e562213 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -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"