Refactor backend-frontend interaction and add MoveCst

some bugs are still present in MoveCst and CreateCst
This commit is contained in:
IRBorisov 2023-07-24 22:34:03 +03:00
parent ffbeafc3f5
commit 920a7baff4
21 changed files with 536 additions and 298 deletions

View File

@ -1,4 +1,4 @@
# Generated by Django 4.2.3 on 2023-07-23 11:55 # Generated by Django 4.2.3 on 2023-07-24 19:06
import apps.rsform.models import apps.rsform.models
from django.conf import settings from django.conf import settings
@ -37,13 +37,16 @@ class Migration(migrations.Migration):
name='Constituenta', name='Constituenta',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1)], verbose_name='Позиция')), ('order', models.PositiveIntegerField(default=-1, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Позиция')),
('alias', models.CharField(max_length=8, verbose_name='Имя')), ('alias', models.CharField(default='undefined', max_length=8, verbose_name='Имя')),
('csttype', models.CharField(choices=[('basic', 'Base'), ('constant', 'Constant'), ('structure', 'Structured'), ('axiom', 'Axiom'), ('term', 'Term'), ('function', 'Function'), ('predicate', 'Predicate'), ('theorem', 'Theorem')], default='basic', max_length=10, verbose_name='Тип')), ('csttype', models.CharField(choices=[('basic', 'Base'), ('constant', 'Constant'), ('structure', 'Structured'), ('axiom', 'Axiom'), ('term', 'Term'), ('function', 'Function'), ('predicate', 'Predicate'), ('theorem', 'Theorem')], default='basic', max_length=10, verbose_name='Тип')),
('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')), ('convention', models.TextField(blank=True, default='', verbose_name='Комментарий/Конвенция')),
('term', models.JSONField(default=apps.rsform.models._empty_term, verbose_name='Термин')), ('term_raw', models.TextField(blank=True, default='', verbose_name='Термин (с отсылками)')),
('term_resolved', models.TextField(blank=True, default='', verbose_name='Термин')),
('term_forms', models.JSONField(default=apps.rsform.models._empty_forms, verbose_name='Словоформы')),
('definition_formal', models.TextField(blank=True, default='', verbose_name='Родоструктурное определение')), ('definition_formal', models.TextField(blank=True, default='', verbose_name='Родоструктурное определение')),
('definition_text', models.JSONField(blank=True, default=apps.rsform.models._empty_definition, verbose_name='Текстовое определние')), ('definition_raw', models.TextField(blank=True, default='', verbose_name='Текстовое определние (с отсылками)')),
('definition_resolved', models.TextField(blank=True, default='', verbose_name='Текстовое определние')),
('schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.rsform', verbose_name='Концептуальная схема')), ('schema', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rsform.rsform', verbose_name='Концептуальная схема')),
], ],
options={ options={

View File

@ -2,6 +2,7 @@ import json
from django.db import models, transaction from django.db import models, transaction
from django.core.validators import MinValueValidator from django.core.validators import MinValueValidator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.urls import reverse
from apps.users.models import User from apps.users.models import User
import pyconcept import pyconcept
@ -26,12 +27,8 @@ class Syntax(models.TextChoices):
MATH = 'math' MATH = 'math'
def _empty_term(): def _empty_forms():
return {'raw': '', 'resolved': '', 'forms': []} return []
def _empty_definition():
return {'raw': '', 'resolved': ''}
class RSForm(models.Model): class RSForm(models.Model):
@ -91,7 +88,7 @@ class RSForm(models.Model):
alias=alias, alias=alias,
csttype=type csttype=type
) )
self._recreate_order() self._update_from_core()
self.save() self.save()
return Constituenta.objects.get(pk=result.pk) return Constituenta.objects.get(pk=result.pk)
@ -107,16 +104,40 @@ class RSForm(models.Model):
alias=alias, alias=alias,
csttype=type csttype=type
) )
self._recreate_order() self._update_from_core()
self.save() self.save()
return Constituenta.objects.get(pk=result.pk) return Constituenta.objects.get(pk=result.pk)
@transaction.atomic
def move_cst(self, listCst: list['Constituenta'], target: int):
''' Move list of constituents to specific position '''
count_moved = 0
count_top = 0
count_bot = 0
size = len(listCst)
update_list = []
for cst in self.constituents():
if cst not in listCst:
if count_top + 1 < target:
cst.order = count_top + 1
count_top += 1
else:
cst.order = target + size + count_bot
count_bot += 1
else:
cst.order = target + count_moved
count_moved += 1
update_list.append(cst)
Constituenta.objects.bulk_update(update_list, ['order'])
self._update_from_core()
self.save()
@transaction.atomic @transaction.atomic
def delete_cst(self, listCst): def delete_cst(self, listCst):
''' Delete multiple constituents. Do not check if listCst are from this schema ''' ''' Delete multiple constituents. Do not check if listCst are from this schema '''
for cst in listCst: for cst in listCst:
cst.delete() cst.delete()
self._recreate_order() self._update_from_core()
self.save() self.save()
@staticmethod @staticmethod
@ -143,6 +164,9 @@ class RSForm(models.Model):
def __str__(self): def __str__(self):
return self.title return self.title
def get_absolute_url(self):
return reverse('rsform-detail', kwargs={'pk': self.pk})
def _prepare_json_rsform(self: 'Constituenta') -> dict: def _prepare_json_rsform(self: 'Constituenta') -> dict:
return { return {
'type': 'rsform', 'type': 'rsform',
@ -152,7 +176,7 @@ class RSForm(models.Model):
'items': [] 'items': []
} }
def _recreate_order(self): def _update_from_core(self) -> dict:
checked = json.loads(pyconcept.check_schema(json.dumps(self.to_json()))) checked = json.loads(pyconcept.check_schema(json.dumps(self.to_json())))
update_list = self.constituents().only('id', 'order') update_list = self.constituents().only('id', 'order')
if (len(checked['items']) != update_list.count()): if (len(checked['items']) != update_list.count()):
@ -166,22 +190,12 @@ class RSForm(models.Model):
order += 1 order += 1
break break
Constituenta.objects.bulk_update(update_list, ['order']) Constituenta.objects.bulk_update(update_list, ['order'])
return checked
def _create_cst_from_json(self, items): def _create_cst_from_json(self, items):
order = 1 order = 1
for cst in items: for cst in items:
# TODO: get rid of empty_term etc. Use None instead Constituenta.import_json(cst, self, order)
Constituenta.objects.create(
alias=cst['alias'],
schema=self,
order=order,
csttype=cst['cstType'],
convention=cst.get('convention', 'Без названия'),
definition_formal=cst['definition'].get('formal', '') if 'definition' in cst else '',
term=cst.get('term', _empty_term()),
definition_text=cst['definition']['text'] \
if 'definition' in cst and 'text' in cst['definition'] else _empty_definition() # noqa: E502
)
order += 1 order += 1
@ -194,11 +208,13 @@ class Constituenta(models.Model):
) )
order = models.PositiveIntegerField( order = models.PositiveIntegerField(
verbose_name='Позиция', verbose_name='Позиция',
validators=[MinValueValidator(1)] validators=[MinValueValidator(1)],
default=-1,
) )
alias = models.CharField( alias = models.CharField(
verbose_name='Имя', verbose_name='Имя',
max_length=8 max_length=8,
default='undefined'
) )
csttype = models.CharField( csttype = models.CharField(
verbose_name='Тип', verbose_name='Тип',
@ -211,18 +227,33 @@ class Constituenta(models.Model):
default='', default='',
blank=True blank=True
) )
term = models.JSONField( term_raw = models.TextField(
verbose_name='Термин (с отсылками)',
default='',
blank=True
)
term_resolved = models.TextField(
verbose_name='Термин', verbose_name='Термин',
default=_empty_term default='',
blank=True
)
term_forms = models.JSONField(
verbose_name='Словоформы',
default=_empty_forms
) )
definition_formal = models.TextField( definition_formal = models.TextField(
verbose_name='Родоструктурное определение', verbose_name='Родоструктурное определение',
default='', default='',
blank=True blank=True
) )
definition_text = models.JSONField( definition_raw = models.TextField(
verbose_name='Текстовое определние (с отсылками)',
default='',
blank=True
)
definition_resolved = models.TextField(
verbose_name='Текстовое определние', verbose_name='Текстовое определние',
default=_empty_definition, default='',
blank=True blank=True
) )
@ -230,9 +261,34 @@ class Constituenta(models.Model):
verbose_name = 'Конституета' verbose_name = 'Конституета'
verbose_name_plural = 'Конституенты' verbose_name_plural = 'Конституенты'
def get_absolute_url(self):
return reverse('constituenta-detail', kwargs={'pk': self.pk})
def __str__(self): def __str__(self):
return self.alias return self.alias
@staticmethod
def import_json(data: dict, schema: RSForm, order: int) -> 'Constituenta':
cst = Constituenta(
alias=data['alias'],
schema=schema,
order=order,
csttype=data['cstType'],
convention=data.get('convention', 'Без названия')
)
if 'definition' in data:
if 'formal' in data['definition']:
cst.definition_formal = data['definition']['formal']
if 'text' in data['definition']:
cst.definition_raw = data['definition']['text'].get('raw', '')
cst.definition_resolved = data['definition']['text'].get('resolved', '')
if 'term' in data:
cst.term_raw = data['definition']['text'].get('raw', '')
cst.term_resolved = data['definition']['text'].get('resolved', '')
cst.term_forms = data['definition']['text'].get('forms', [])
cst.save()
return cst
def to_json(self) -> str: def to_json(self) -> str:
return { return {
'entityUID': self.id, 'entityUID': self.id,
@ -240,9 +296,16 @@ class Constituenta(models.Model):
'cstType': self.csttype, 'cstType': self.csttype,
'alias': self.alias, 'alias': self.alias,
'convention': self.convention, 'convention': self.convention,
'term': self.term, 'term': {
'raw': self.term_raw,
'resolved': self.term_resolved,
'forms': self.term_forms,
},
'definition': { 'definition': {
'formal': self.definition_formal, 'formal': self.definition_formal,
'text': self.definition_text 'text': {
} 'raw': self.definition_raw,
'resolved': self.definition_resolved,
},
},
} }

View File

@ -1,5 +1,7 @@
import json
from rest_framework import serializers from rest_framework import serializers
import pyconcept
from .models import Constituenta, RSForm from .models import Constituenta, RSForm
@ -7,12 +9,6 @@ class FileSerializer(serializers.Serializer):
file = serializers.FileField(allow_empty_file=False) file = serializers.FileField(allow_empty_file=False)
class ItemsListSerlializer(serializers.Serializer):
items = serializers.ListField(
child=serializers.IntegerField()
)
class ExpressionSerializer(serializers.Serializer): class ExpressionSerializer(serializers.Serializer):
expression = serializers.CharField() expression = serializers.CharField()
@ -35,7 +31,60 @@ class ConstituentaSerializer(serializers.ModelSerializer):
return super().update(instance, validated_data) return super().update(instance, validated_data)
class NewConstituentaSerializer(serializers.Serializer): class StandaloneCstSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
class Meta:
model = Constituenta
exclude = ('schema', )
def validate(self, attrs):
try:
attrs['object'] = Constituenta.objects.get(pk=attrs['id'])
except Constituenta.DoesNotExist:
raise serializers.ValidationError({f"{attrs['id']}": 'Конституента не существует'})
return attrs
class CstCreateSerializer(serializers.Serializer):
alias = serializers.CharField(max_length=8) alias = serializers.CharField(max_length=8)
csttype = serializers.CharField(max_length=10) csttype = serializers.CharField(max_length=10)
insert_after = serializers.IntegerField(required=False) insert_after = serializers.IntegerField(required=False)
class CstListSerlializer(serializers.Serializer):
items = serializers.ListField(
child=StandaloneCstSerializer()
)
def validate(self, attrs):
schema = self.context['schema']
cstList = []
for item in attrs['items']:
cst = item['object']
if (cst.schema != schema):
raise serializers.ValidationError(
{'items': f'Конституенты должны относиться к данной схеме: {item}'})
cstList.append(cst)
attrs['constituents'] = cstList
return attrs
class CstMoveSerlializer(CstListSerlializer):
move_to = serializers.IntegerField()
class RSFormDetailsSerlializer(serializers.BaseSerializer):
class Meta:
model = RSForm
def to_representation(self, instance: RSForm):
trs = pyconcept.check_schema(json.dumps(instance.to_json()))
trs = trs.replace('entityUID', 'id')
result = json.loads(trs)
result['id'] = instance.id
result['time_update'] = instance.time_update
result['time_create'] = instance.time_create
result['is_common'] = instance.is_common
result['owner'] = (instance.owner.pk if instance.owner is not None else None)
return result

View File

@ -8,8 +8,7 @@ from apps.rsform.models import (
RSForm, RSForm,
Constituenta, Constituenta,
CstType, CstType,
User, User
_empty_term, _empty_definition
) )
@ -23,6 +22,11 @@ class TestConstituenta(TestCase):
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1, order=1, convention='Test') cst = Constituenta.objects.create(alias=testStr, schema=self.schema1, order=1, convention='Test')
self.assertEqual(str(cst), testStr) self.assertEqual(str(cst), testStr)
def test_url(self):
testStr = 'X1'
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1, order=1, convention='Test')
self.assertEqual(cst.get_absolute_url(), f'/api/constituents/{cst.id}/')
def test_order_not_null(self): def test_order_not_null(self):
with self.assertRaises(IntegrityError): with self.assertRaises(IntegrityError):
Constituenta.objects.create(alias='X1', schema=self.schema1) Constituenta.objects.create(alias='X1', schema=self.schema1)
@ -52,28 +56,11 @@ class TestConstituenta(TestCase):
self.assertEqual(cst.csttype, CstType.BASE) self.assertEqual(cst.csttype, CstType.BASE)
self.assertEqual(cst.convention, '') self.assertEqual(cst.convention, '')
self.assertEqual(cst.definition_formal, '') self.assertEqual(cst.definition_formal, '')
self.assertEqual(cst.term, _empty_term()) self.assertEqual(cst.term_raw, '')
self.assertEqual(cst.definition_text, _empty_definition()) self.assertEqual(cst.term_resolved, '')
self.assertEqual(cst.term_forms, [])
def test_create(self): self.assertEqual(cst.definition_resolved, '')
cst = Constituenta.objects.create( self.assertEqual(cst.definition_raw, '')
alias='S1',
schema=self.schema1,
order=1,
csttype=CstType.STRUCTURED,
convention='Test convention',
definition_formal='X1=X1',
term={'raw': 'Текст @{12|3}', 'resolved': 'Текст тест', 'forms': []},
definition_text={'raw': 'Текст1 @{12|3}', 'resolved': 'Текст1 тест'},
)
self.assertEqual(cst.schema, self.schema1)
self.assertEqual(cst.order, 1)
self.assertEqual(cst.alias, 'S1')
self.assertEqual(cst.csttype, CstType.STRUCTURED)
self.assertEqual(cst.convention, 'Test convention')
self.assertEqual(cst.definition_formal, 'X1=X1')
self.assertEqual(cst.term, {'raw': 'Текст @{12|3}', 'resolved': 'Текст тест', 'forms': []})
self.assertEqual(cst.definition_text, {'raw': 'Текст1 @{12|3}', 'resolved': 'Текст1 тест'})
class TestRSForm(TestCase): class TestRSForm(TestCase):
@ -87,6 +74,11 @@ class TestRSForm(TestCase):
schema = RSForm.objects.create(title=testStr, owner=self.user1, alias='КС1') schema = RSForm.objects.create(title=testStr, owner=self.user1, alias='КС1')
self.assertEqual(str(schema), testStr) self.assertEqual(str(schema), testStr)
def test_url(self):
testStr = 'Test123'
schema = RSForm.objects.create(title=testStr, owner=self.user1, alias='КС1')
self.assertEqual(schema.get_absolute_url(), f'/api/rsforms/{schema.id}/')
def test_create_default(self): def test_create_default(self):
schema = RSForm.objects.create(title='Test') schema = RSForm.objects.create(title='Test')
self.assertIsNone(schema.owner) self.assertIsNone(schema.owner)
@ -191,6 +183,32 @@ class TestRSForm(TestCase):
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 1)
self.assertEqual(d2.order, 2) self.assertEqual(d2.order, 2)
def test_move_cst(self):
schema = RSForm.objects.create(title='Test')
x1 = schema.insert_last('X1', CstType.BASE)
x2 = schema.insert_last('X2', CstType.BASE)
d1 = schema.insert_last('D1', CstType.TERM)
d2 = schema.insert_last('D2', CstType.TERM)
schema.move_cst([x2, d2], 1)
x1.refresh_from_db()
x2.refresh_from_db()
d1.refresh_from_db()
d2.refresh_from_db()
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1)
self.assertEqual(d1.order, 4)
self.assertEqual(d2.order, 3)
def test_move_cst_down(self):
schema = RSForm.objects.create(title='Test')
x1 = schema.insert_last('X1', CstType.BASE)
x2 = schema.insert_last('X2', CstType.BASE)
schema.move_cst([x1], 2)
x1.refresh_from_db()
x2.refresh_from_db()
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1)
def test_to_json(self): def test_to_json(self):
schema = RSForm.objects.create(title='Test', alias='KS1', comment='Test') schema = RSForm.objects.create(title='Test', alias='KS1', comment='Test')
x1 = schema.insert_at(4, 'X1', CstType.BASE) x1 = schema.insert_at(4, 'X1', CstType.BASE)

View File

@ -119,12 +119,14 @@ class TestRSFormViewset(APITestCase):
def test_details(self): def test_details(self):
schema = RSForm.objects.create(title='Test') schema = RSForm.objects.create(title='Test')
schema.insert_at(1, 'X1', CstType.BASE) cst = schema.insert_at(1, 'X1', CstType.BASE)
schema.insert_at(2, 'X2', CstType.BASE)
response = self.client.get(f'/api/rsforms/{schema.id}/details/') response = self.client.get(f'/api/rsforms/{schema.id}/details/')
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['title'], 'Test') self.assertEqual(response.data['title'], 'Test')
self.assertEqual(len(response.data['items']), 1) self.assertEqual(len(response.data['items']), 2)
self.assertEqual(response.data['items'][0]['parse']['status'], 'verified') self.assertEqual(response.data['items'][0]['parse']['status'], 'verified')
self.assertEqual(response.data['items'][0]['id'], cst.id)
def test_check(self): def test_check(self):
schema = RSForm.objects.create(title='Test') schema = RSForm.objects.create(title='Test')
@ -183,28 +185,28 @@ class TestRSFormViewset(APITestCase):
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/', response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
data=data, content_type='application/json') data=data, content_type='application/json')
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(response.data['alias'], 'X3') self.assertEqual(response.data['new_cst']['alias'], 'X3')
x3 = Constituenta.objects.get(alias=response.data['alias']) x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x3.order, 3) self.assertEqual(x3.order, 3)
data = json.dumps({'alias': 'X4', 'csttype': 'basic', 'insert_after': x2.id}) data = json.dumps({'alias': 'X4', 'csttype': 'basic', 'insert_after': x2.id})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/', response = self.client.post(f'/api/rsforms/{schema.id}/cst-create/',
data=data, content_type='application/json') data=data, content_type='application/json')
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
self.assertEqual(response.data['alias'], 'X4') self.assertEqual(response.data['new_cst']['alias'], 'X4')
x4 = Constituenta.objects.get(alias=response.data['alias']) x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x4.order, 3) self.assertEqual(x4.order, 3)
def test_delete_constituenta(self): def test_delete_constituenta(self):
schema = self.rsform_owned schema = self.rsform_owned
data = json.dumps({'items': [1337]}) data = json.dumps({'items': [{'id': 1337}]})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/', response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/',
data=data, content_type='application/json') data=data, content_type='application/json')
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 400)
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1) x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2) x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
data = json.dumps({'items': [x1.id]}) data = json.dumps({'items': [{'id': x1.id}]})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/', response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/',
data=data, content_type='application/json') data=data, content_type='application/json')
x2.refresh_from_db() x2.refresh_from_db()
@ -215,11 +217,36 @@ class TestRSFormViewset(APITestCase):
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 1)
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1) x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
data = json.dumps({'items': [x3.id]}) data = json.dumps({'items': [{'id': x3.id}]})
response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/', response = self.client.post(f'/api/rsforms/{schema.id}/cst-multidelete/',
data=data, content_type='application/json') data=data, content_type='application/json')
self.assertEqual(response.status_code, 400) self.assertEqual(response.status_code, 400)
def test_move_constituenta(self):
schema = self.rsform_owned
data = json.dumps({'items': [{'id': 1337}], 'move_to': 1})
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
data=data, content_type='application/json')
self.assertEqual(response.status_code, 400)
x1 = Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
data = json.dumps({'items': [{'id': x2.id}], 'move_to': 1})
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
data=data, content_type='application/json')
x1.refresh_from_db()
x2.refresh_from_db()
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['id'], schema.id)
self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1)
x3 = Constituenta.objects.create(schema=self.rsform_unowned, alias='X1', csttype='basic', order=1)
data = json.dumps({'items': [{'id': x3.id}], 'move_to': 1})
response = self.client.patch(f'/api/rsforms/{schema.id}/cst-moveto/',
data=data, content_type='application/json')
self.assertEqual(response.status_code, 400)
class TestFunctionalViews(APITestCase): class TestFunctionalViews(APITestCase):
def setUp(self): def setUp(self):

View File

@ -7,7 +7,7 @@ rsform_router = routers.SimpleRouter()
rsform_router.register(r'rsforms', views.RSFormViewSet) rsform_router.register(r'rsforms', views.RSFormViewSet)
urlpatterns = [ urlpatterns = [
path('constituents/<int:pk>/', views.ConstituentAPIView.as_view()), path('constituents/<int:pk>/', views.ConstituentAPIView.as_view(), name='constituenta-detail'),
path('rsforms/import-trs/', views.TrsImportView.as_view()), path('rsforms/import-trs/', views.TrsImportView.as_view()),
path('rsforms/create-detailed/', views.create_rsform), path('rsforms/create-detailed/', views.create_rsform),
path('func/parse-expression/', views.parse_expression), path('func/parse-expression/', views.parse_expression),

View File

@ -54,7 +54,7 @@ class RSFormViewSet(viewsets.ModelViewSet):
def cst_create(self, request, pk): def cst_create(self, request, pk):
''' Create new constituenta ''' ''' Create new constituenta '''
schema: models.RSForm = self.get_object() schema: models.RSForm = self.get_object()
serializer = serializers.NewConstituentaSerializer(data=request.data) serializer = serializers.CstCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
if ('insert_after' in serializer.validated_data): if ('insert_after' in serializer.validated_data):
cstafter = models.Constituenta.objects.get(pk=serializer.validated_data['insert_after']) cstafter = models.Constituenta.objects.get(pk=serializer.validated_data['insert_after'])
@ -63,28 +63,32 @@ class RSFormViewSet(viewsets.ModelViewSet):
serializer.validated_data['csttype']) serializer.validated_data['csttype'])
else: else:
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['csttype']) constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['csttype'])
return Response(status=201, data=constituenta.to_json()) schema.refresh_from_db()
outSerializer = serializers.RSFormDetailsSerlializer(schema)
response = Response(status=201, data={'new_cst': constituenta.to_json(), 'schema': outSerializer.data})
response['Location'] = constituenta.get_absolute_url()
return response
@action(detail=True, methods=['post'], url_path='cst-multidelete') @action(detail=True, methods=['post'], url_path='cst-multidelete')
def cst_multidelete(self, request, pk): def cst_multidelete(self, request, pk):
''' Delete multiple constituents ''' ''' Delete multiple constituents '''
schema: models.RSForm = self.get_object() schema: models.RSForm = self.get_object()
serializer = serializers.ItemsListSerlializer(data=request.data) serializer = serializers.CstListSerlializer(data=request.data, context={'schema': schema})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
listCst = [] schema.delete_cst(serializer.validated_data['constituents'])
# TODO: consider moving validation to serializer
try:
for id in serializer.validated_data['items']:
cst = models.Constituenta.objects.get(pk=id)
if (cst.schema != schema):
return Response({'error', 'Конституенты должны относиться к данной схеме'}, status=400)
listCst.append(cst)
except models.Constituenta.DoesNotExist:
return Response(status=404)
schema.delete_cst(listCst)
return Response(status=202) return Response(status=202)
@action(detail=True, methods=['patch'], url_path='cst-moveto')
def cst_moveto(self, request, pk):
''' Delete multiple constituents '''
schema: models.RSForm = self.get_object()
serializer = serializers.CstMoveSerlializer(data=request.data, context={'schema': schema})
serializer.is_valid(raise_exception=True)
schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to'])
schema.refresh_from_db()
outSerializer = serializers.RSFormDetailsSerlializer(schema)
return Response(status=200, data=outSerializer.data)
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
def claim(self, request, pk=None): def claim(self, request, pk=None):
schema: models.RSForm = self.get_object() schema: models.RSForm = self.get_object()
@ -93,7 +97,7 @@ class RSFormViewSet(viewsets.ModelViewSet):
else: else:
schema.owner = self.request.user schema.owner = self.request.user
schema.save() schema.save()
return Response(status=200) return Response(status=200, data=serializers.RSFormSerializer(schema).data)
@action(detail=True, methods=['get']) @action(detail=True, methods=['get'])
def contents(self, request, pk): def contents(self, request, pk):
@ -105,14 +109,8 @@ class RSFormViewSet(viewsets.ModelViewSet):
def details(self, request, pk): def details(self, request, pk):
''' Detailed schema view including statuses ''' ''' Detailed schema view including statuses '''
schema: models.RSForm = self.get_object() schema: models.RSForm = self.get_object()
result = pyconcept.check_schema(json.dumps(schema.to_json())) serializer = serializers.RSFormDetailsSerlializer(schema)
output_data = json.loads(result) return Response(serializer.data)
output_data['id'] = schema.id
output_data['time_update'] = schema.time_update
output_data['time_create'] = schema.time_create
output_data['is_common'] = schema.is_common
output_data['owner'] = (schema.owner.pk if schema.owner is not None else None)
return Response(output_data)
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
def check(self, request, pk): def check(self, request, pk):

View File

@ -23,7 +23,6 @@ function App() {
autoClose={3000} autoClose={3000}
draggable={false} draggable={false}
pauseOnFocusLoss={false} pauseOnFocusLoss={false}
limit={5}
/> />
<main className='min-h-[calc(100vh-7.5rem)] px-2 h-fit'> <main className='min-h-[calc(100vh-7.5rem)] px-2 h-fit'>
<Routes> <Routes>

View File

@ -4,10 +4,7 @@ import type { TabProps } from 'react-tabs';
function ConceptTab({children, className, ...otherProps} : TabProps) { function ConceptTab({children, className, ...otherProps} : TabProps) {
return ( return (
<Tab <Tab
className={ className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab ${className} whitespace-nowrap`}
'px-2 py-1 text-sm hover:cursor-pointer clr-tab'
+ ' ' + className
}
{...otherProps} {...otherProps}
> >
{children} {children}

View File

@ -3,28 +3,34 @@ import { IConstituenta, IRSForm } from '../utils/models';
import { useRSFormDetails } from '../hooks/useRSFormDetails'; import { useRSFormDetails } from '../hooks/useRSFormDetails';
import { ErrorInfo } from '../components/BackendError'; import { ErrorInfo } from '../components/BackendError';
import { useAuth } from './AuthContext'; import { useAuth } from './AuthContext';
import { BackendCallback, deleteRSForm, getTRSFile, patchConstituenta, patchRSForm, postClaimRSForm, postDeleteConstituenta, postNewConstituenta } from '../utils/backendAPI'; import {
BackendCallback, deleteRSForm, getTRSFile,
patchConstituenta, patchMoveConstituenta, patchRSForm,
postClaimRSForm, postDeleteConstituenta, postNewConstituenta
} from '../utils/backendAPI';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
interface IRSFormContext { interface IRSFormContext {
schema?: IRSForm schema?: IRSForm
active?: IConstituenta activeCst?: IConstituenta
activeID?: number
error: ErrorInfo error: ErrorInfo
loading: boolean loading: boolean
processing: boolean processing: boolean
isOwned: boolean isOwned: boolean
isEditable: boolean isEditable: boolean
isClaimable: boolean isClaimable: boolean
forceAdmin: boolean isReadonly: boolean
readonly: boolean
isTracking: boolean isTracking: boolean
isForceAdmin: boolean
setActive: React.Dispatch<React.SetStateAction<IConstituenta | undefined>> setActiveID: React.Dispatch<React.SetStateAction<number | undefined>>
toggleForceAdmin: () => void toggleForceAdmin: () => void
toggleReadonly: () => void toggleReadonly: () => void
toggleTracking: () => void toggleTracking: () => void
reload: () => Promise<void>
update: (data: any, callback?: BackendCallback) => Promise<void> update: (data: any, callback?: BackendCallback) => Promise<void>
destroy: (callback?: BackendCallback) => Promise<void> destroy: (callback?: BackendCallback) => Promise<void>
claim: (callback?: BackendCallback) => Promise<void> claim: (callback?: BackendCallback) => Promise<void>
@ -33,36 +39,19 @@ interface IRSFormContext {
cstUpdate: (data: any, callback?: BackendCallback) => Promise<void> cstUpdate: (data: any, callback?: BackendCallback) => Promise<void>
cstCreate: (data: any, callback?: BackendCallback) => Promise<void> cstCreate: (data: any, callback?: BackendCallback) => Promise<void>
cstDelete: (data: any, callback?: BackendCallback) => Promise<void> cstDelete: (data: any, callback?: BackendCallback) => Promise<void>
cstMoveTo: (data: any, callback?: BackendCallback) => Promise<void>
} }
export const RSFormContext = createContext<IRSFormContext>({ const RSFormContext = createContext<IRSFormContext | null>(null);
schema: undefined, export const useRSForm = () => {
active: undefined, const context = useContext(RSFormContext);
error: undefined, if (!context) {
loading: false, throw new Error(
processing: false, 'useRSForm has to be used within <RSFormState.Provider>'
isOwned: false, );
isEditable: false, }
isClaimable: false, return context;
forceAdmin: false, }
readonly: false,
isTracking: true,
setActive: () => {},
toggleForceAdmin: () => {},
toggleReadonly: () => {},
toggleTracking: () => {},
reload: async () => {},
update: async () => {},
destroy: async () => {},
claim: async () => {},
download: async () => {},
cstUpdate: async () => {},
cstCreate: async () => {},
cstDelete: async () => {},
})
interface RSFormStateProps { interface RSFormStateProps {
schemaID: string schemaID: string
@ -71,22 +60,27 @@ interface RSFormStateProps {
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => { export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const { user } = useAuth(); const { user } = useAuth();
const { schema, reload, error, setError, loading } = useRSFormDetails({target: schemaID}); const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({target: schemaID});
const [processing, setProcessing] = useState(false) const [processing, setProcessing] = useState(false)
const [active, setActive] = useState<IConstituenta | undefined>(undefined); const [activeID, setActiveID] = useState<number | undefined>(undefined);
const [forceAdmin, setForceAdmin] = useState(false); const [isForceAdmin, setIsForceAdmin] = useState(false);
const [readonly, setReadonly] = useState(false); const [isReadonly, setIsReadonly] = useState(false);
const isOwned = useMemo(() => user?.id === schema?.owner || false, [user, schema]); const isOwned = useMemo(() => user?.id === schema?.owner || false, [user, schema?.owner]);
const isClaimable = useMemo(() => (user?.id !== schema?.owner || false), [user, schema]); const isClaimable = useMemo(() => user?.id !== schema?.owner || false, [user, schema?.owner]);
const isEditable = useMemo( const isEditable = useMemo(
() => { () => {
return ( return (
!loading && !readonly && !loading && !isReadonly &&
(isOwned || (forceAdmin && user?.is_staff) || false) (isOwned || (isForceAdmin && user?.is_staff) || false)
) )
}, [user, readonly, forceAdmin, isOwned, loading]); }, [user, isReadonly, isForceAdmin, isOwned, loading]);
const activeCst = useMemo(
() => {
return schema?.items && schema?.items.find((cst) => cst.id === activeID);
}, [schema?.items, activeID]);
const isTracking = useMemo( const isTracking = useMemo(
() => { () => {
@ -106,9 +100,12 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => setError(error), onError: error => setError(error),
onSucccess: callback onSucccess: async (response) => {
await reload();
if (callback) callback(response);
}
}); });
}, [schemaID, setError]); }, [schemaID, setError, reload]);
const destroy = useCallback( const destroy = useCallback(
async (callback?: BackendCallback) => { async (callback?: BackendCallback) => {
@ -128,9 +125,14 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => setError(error), onError: error => setError(error),
onSucccess: callback onSucccess: async (response) => {
schema!.owner = user!.id
schema!.time_update = response.data['time_update']
setSchema(schema)
if (callback) callback(response);
}
}); });
}, [schemaID, setError]); }, [schemaID, setError, schema, user, setSchema]);
const download = useCallback( const download = useCallback(
async (callback: BackendCallback) => { async (callback: BackendCallback) => {
@ -146,14 +148,14 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const cstUpdate = useCallback( const cstUpdate = useCallback(
async (data: any, callback?: BackendCallback) => { async (data: any, callback?: BackendCallback) => {
setError(undefined); setError(undefined);
patchConstituenta(String(active!.entityUID), { patchConstituenta(String(activeID), {
data: data, data: data,
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => setError(error), onError: error => setError(error),
onSucccess: callback onSucccess: callback
}); });
}, [active, setError]); }, [activeID, setError]);
const cstCreate = useCallback( const cstCreate = useCallback(
async (data: any, callback?: BackendCallback) => { async (data: any, callback?: BackendCallback) => {
@ -163,9 +165,12 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => setError(error), onError: error => setError(error),
onSucccess: callback onSucccess: async (response) => {
setSchema(response.data['schema']);
if (callback) callback(response);
}
}); });
}, [schemaID, setError]); }, [schemaID, setError, setSchema]);
const cstDelete = useCallback( const cstDelete = useCallback(
async (data: any, callback?: BackendCallback) => { async (data: any, callback?: BackendCallback) => {
@ -175,25 +180,42 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
showError: true, showError: true,
setLoading: setProcessing, setLoading: setProcessing,
onError: error => setError(error), onError: error => setError(error),
onSucccess: callback onSucccess: async (response) => {
await reload();
if (callback) callback(response);
}
}); });
}, [schemaID, setError]); }, [schemaID, setError, reload]);
const cstMoveTo = useCallback(
async (data: any, callback?: BackendCallback) => {
setError(undefined);
patchMoveConstituenta(schemaID, {
data: data,
showError: true,
setLoading: setProcessing,
onError: error => setError(error),
onSucccess: (response) => {
setSchema(response.data);
if (callback) callback(response);
}
});
}, [schemaID, setError, setSchema]);
return ( return (
<RSFormContext.Provider value={{ <RSFormContext.Provider value={{
schema, error, loading, processing, schema, error, loading, processing,
active, setActive, activeID, activeCst,
forceAdmin, readonly, setActiveID,
toggleForceAdmin: () => setForceAdmin(prev => !prev), isForceAdmin, isReadonly,
toggleReadonly: () => setReadonly(prev => !prev), toggleForceAdmin: () => setIsForceAdmin(prev => !prev),
toggleReadonly: () => setIsReadonly(prev => !prev),
isOwned, isEditable, isClaimable, isOwned, isEditable, isClaimable,
isTracking, toggleTracking, isTracking, toggleTracking,
reload, update, download, destroy, claim, update, download, destroy, claim,
cstUpdate, cstCreate, cstDelete, cstUpdate, cstCreate, cstDelete, cstMoveTo,
}}> }}>
{ children } { children }
</RSFormContext.Provider> </RSFormContext.Provider>
); );
} }
export const useRSForm = () => useContext(RSFormContext);

View File

@ -4,14 +4,20 @@ import { ErrorInfo } from '../components/BackendError';
import { getRSFormDetails } from '../utils/backendAPI'; import { getRSFormDetails } from '../utils/backendAPI';
export function useRSFormDetails({target}: {target?: string}) { export function useRSFormDetails({target}: {target?: string}) {
const [schema, setSchema] = useState<IRSForm | undefined>(); const [schema, setInnerSchema] = useState<IRSForm | undefined>(undefined);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<ErrorInfo>(undefined); const [error, setError] = useState<ErrorInfo>(undefined);
function setSchema(schema?: IRSForm) {
if (schema) CalculateStats(schema);
setInnerSchema(schema);
console.log(schema);
}
const fetchData = useCallback( const fetchData = useCallback(
async () => { async () => {
setError(undefined); setError(undefined);
setSchema(undefined); setInnerSchema(undefined);
if (!target) { if (!target) {
return; return;
} }
@ -19,11 +25,7 @@ export function useRSFormDetails({target}: {target?: string}) {
showError: true, showError: true,
setLoading: setLoading, setLoading: setLoading,
onError: error => setError(error), onError: error => setError(error),
onSucccess: (response) => { onSucccess: (response) => setSchema(response.data)
CalculateStats(response.data)
console.log(response.data);
setSchema(response.data);
}
}); });
}, [target]); }, [target]);
@ -35,5 +37,5 @@ export function useRSFormDetails({target}: {target?: string}) {
fetchData(); fetchData();
}, [fetchData]) }, [fetchData])
return { schema, reload, error, setError, loading }; return { schema, setSchema, reload, error, setError, loading };
} }

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useLayoutEffect, useState } from 'react';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { CstType, EditMode, INewCstData } from '../../utils/models'; import { CstType, EditMode, INewCstData } from '../../utils/models';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
@ -10,13 +10,10 @@ import ConstituentsSideList from './ConstituentsSideList';
import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons'; import { DumpBinIcon, SaveIcon, SmallPlusIcon } from '../../components/Icons';
import CreateCstModal from './CreateCstModal'; import CreateCstModal from './CreateCstModal';
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import { useNavigate } from 'react-router-dom';
import { RSFormTabsList } from './RSFormTabs';
function ConstituentEditor() { function ConstituentEditor() {
const navigate = useNavigate();
const { const {
active, schema, setActive, processing, isEditable, reload, activeCst, activeID, schema, setActiveID, processing, isEditable,
cstDelete, cstUpdate, cstCreate cstDelete, cstUpdate, cstCreate
} = useRSForm(); } = useRSForm();
@ -31,23 +28,23 @@ function ConstituentEditor() {
const [convention, setConvention] = useState(''); const [convention, setConvention] = useState('');
const [typification, setTypification] = useState('N/A'); const [typification, setTypification] = useState('N/A');
useEffect(() => { useLayoutEffect(() => {
if (schema?.items && schema?.items.length > 0) { if (schema?.items && schema?.items.length > 0) {
setActive((prev) => (prev || schema?.items![0])); setActiveID((prev) => (prev || schema?.items![0].id));
} }
}, [schema, setActive]) }, [schema, setActiveID])
useEffect(() => { useLayoutEffect(() => {
if (active) { if (activeCst) {
setAlias(active.alias); setAlias(activeCst.alias);
setType(getCstTypeLabel(active.cstType)); setType(getCstTypeLabel(activeCst.cstType));
setConvention(active.convention || ''); setConvention(activeCst.convention || '');
setTerm(active.term?.raw || ''); setTerm(activeCst.term?.raw || '');
setTextDefinition(active.definition?.text?.raw || ''); setTextDefinition(activeCst.definition?.text?.raw || '');
setExpression(active.definition?.formal || ''); setExpression(activeCst.definition?.formal || '');
setTypification(active?.parse?.typification || 'N/A'); setTypification(activeCst?.parse?.typification || 'N/A');
} }
}, [active]); }, [activeCst]);
const handleSubmit = const handleSubmit =
async (event: React.FormEvent<HTMLFormElement>) => { async (event: React.FormEvent<HTMLFormElement>) => {
@ -64,37 +61,31 @@ function ConstituentEditor() {
'term': { 'term': {
'raw': term, 'raw': term,
'resolved': '', 'resolved': '',
'forms': active?.term?.forms || [], 'forms': activeCst?.term?.forms || [],
} }
}; };
cstUpdate(data) cstUpdate(data).then(() => toast.success('Изменения сохранены'));
.then(() => {
toast.success('Изменения сохранены');
reload();
});
} }
}; };
const handleDelete = useCallback( const handleDelete = useCallback(
async () => { async () => {
if (!active || !window.confirm('Вы уверены, что хотите удалить конституенту?')) { if (!activeID || !schema?.items || !window.confirm('Вы уверены, что хотите удалить конституенту?')) {
return; return;
} }
const data = { const data = {
'items': [active.entityUID] 'items': [activeID]
} }
const index = schema?.items?.indexOf(active) const index = schema.items.findIndex((cst) => cst.id === activeID);
await cstDelete(data); if (index !== -1 && index + 1 < schema.items.length) {
if (schema?.items && index && index + 1 < schema?.items?.length) { setActiveID(schema.items[index + 1].id);
setActive(schema?.items[index + 1]);
} }
toast.success(`Конституента удалена: ${active.alias}`); cstDelete(data).then(() => toast.success('Конституента удалена'));
reload(); }, [activeID, schema, setActiveID, cstDelete]);
}, [active, schema, setActive, cstDelete, reload]);
const handleAddNew = useCallback( const handleAddNew = useCallback(
async (csttype?: CstType) => { async (csttype?: CstType) => {
if (!active || !schema) { if (!activeID || !schema?.items) {
return; return;
} }
if (!csttype) { if (!csttype) {
@ -103,14 +94,16 @@ function ConstituentEditor() {
const data: INewCstData = { const data: INewCstData = {
'csttype': csttype, 'csttype': csttype,
'alias': createAliasFor(csttype, schema!), 'alias': createAliasFor(csttype, schema!),
'insert_after': active.entityUID 'insert_after': activeID
} }
cstCreate(data, (response: AxiosResponse) => { cstCreate(data,
navigate(`/rsforms/${schema.id}?tab=${RSFormTabsList.CST_EDIT}&active=${response.data['entityUID']}`); async (response: AxiosResponse) => {
window.location.reload(); // navigate(`/rsforms/${schema.id}?tab=${RSFormTabsList.CST_EDIT}&active=${response.data['new_cst']['id']}`);
setActiveID(response.data['new_cst']['id']);
toast.success(`Конституента добавлена: ${response.data['new_cst']['alias']}`);
}); });
} }
}, [active, schema, cstCreate, navigate]); }, [activeID, schema, cstCreate, setActiveID]);
const handleRename = useCallback(() => { const handleRename = useCallback(() => {
toast.info('Переименование в разработке'); toast.info('Переименование в разработке');
@ -127,7 +120,7 @@ function ConstituentEditor() {
show={showCstModal} show={showCstModal}
toggle={() => setShowCstModal(!showCstModal)} toggle={() => setShowCstModal(!showCstModal)}
onCreate={handleAddNew} onCreate={handleAddNew}
defaultType={active?.cstType as CstType} defaultType={activeCst?.cstType as CstType}
/> />
<form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'> <form onSubmit={handleSubmit} className='flex-grow min-w-[50rem] max-w-min px-4 py-2 border'>
<div className='flex items-start justify-between'> <div className='flex items-start justify-between'>

View File

@ -11,7 +11,7 @@ interface ConstituentsSideListProps {
} }
function ConstituentsSideList({expression}: ConstituentsSideListProps) { function ConstituentsSideList({expression}: ConstituentsSideListProps) {
const { schema, setActive } = useRSForm(); const { schema, setActiveID } = useRSForm();
const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items || []); const [filteredData, setFilteredData] = useState<IConstituenta[]>(schema?.items || []);
const [filterText, setFilterText] = useLocalStorage('side-filter-text', '') const [filterText, setFilterText] = useLocalStorage('side-filter-text', '')
const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false); const [onlyExpression, setOnlyExpression] = useLocalStorage('side-filter-flag', false);
@ -27,7 +27,7 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
if (diff.length > 0) { if (diff.length > 0) {
diff.forEach( diff.forEach(
(alias, i) => filtered.push({ (alias, i) => filtered.push({
entityUID: -i, id: -i,
alias: alias, alias: alias,
convention: 'Конституента отсутствует', convention: 'Конституента отсутствует',
cstType: CstType.BASE cstType: CstType.BASE
@ -43,21 +43,21 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
const handleRowClicked = useCallback( const handleRowClicked = useCallback(
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => { (cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
if (event.altKey && cst.entityUID > 0) { if (event.altKey && cst.id > 0) {
setActive(cst); setActiveID(cst.id);
} }
}, [setActive]); }, [setActiveID]);
const handleDoubleClick = useCallback( const handleDoubleClick = useCallback(
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => { (cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
if (cst.entityUID > 0) setActive(cst); if (cst.id > 0) setActiveID(cst.id);
}, [setActive]); }, [setActiveID]);
const columns = useMemo(() => const columns = useMemo(() =>
[ [
{ {
id: 'id', id: 'id',
selector: (cst: IConstituenta) => cst.entityUID, selector: (cst: IConstituenta) => cst.id,
omit: true, omit: true,
}, },
{ {
@ -68,7 +68,7 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
maxWidth: '62px', maxWidth: '62px',
conditionalCellStyles: [ conditionalCellStyles: [
{ {
when: (cst: IConstituenta) => cst.entityUID <= 0, when: (cst: IConstituenta) => cst.id <= 0,
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]'] classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
}, },
], ],
@ -81,7 +81,7 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
wrap: true, wrap: true,
conditionalCellStyles: [ conditionalCellStyles: [
{ {
when: (cst: IConstituenta) => cst.entityUID <= 0, when: (cst: IConstituenta) => cst.id <= 0,
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]'] classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
}, },
], ],
@ -96,7 +96,7 @@ function ConstituentsSideList({expression}: ConstituentsSideListProps) {
wrap: true, wrap: true,
conditionalCellStyles: [ conditionalCellStyles: [
{ {
when: (cst: IConstituenta) => cst.entityUID <= 0, when: (cst: IConstituenta) => cst.id <= 0,
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]'] classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
}, },
], ],

View File

@ -15,8 +15,11 @@ interface ConstituentsTableProps {
} }
function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) { function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
const { schema, isEditable, cstCreate, cstDelete, reload } = useRSForm(); const {
const [selected, setSelected] = useState<IConstituenta[]>([]); schema, isEditable,
cstCreate, cstDelete, cstMoveTo
} = useRSForm();
const [selected, setSelected] = useState<number[]>([]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]); const nothingSelected = useMemo(() => selected.length === 0, [selected]);
const [showCstModal, setShowCstModal] = useState(false); const [showCstModal, setShowCstModal] = useState(false);
@ -28,32 +31,78 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
} }
}, [onOpenEdit]); }, [onOpenEdit]);
const handleSelectionChange = useCallback(
({selectedRows}: {
allSelected: boolean;
selectedCount: number;
selectedRows: IConstituenta[];
}) => {
setSelected(selectedRows.map((cst) => cst.id));
}, [setSelected]);
// Delete selected constituents
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
if (!window.confirm('Вы уверены, что хотите удалить выбранные конституенты?')) { if (!schema?.items || !window.confirm('Вы уверены, что хотите удалить выбранные конституенты?')) {
return; return;
} }
const data = { const data = {
'items': selected.map(cst => cst.entityUID) 'items': selected.map(id => { return {'id': id }; }),
} }
const deletedNamed = selected.map(cst => cst.alias) const deletedNamed = selected.map(id => schema.items?.find((cst) => cst.id === id)?.alias);
cstDelete(data, (response: AxiosResponse) => { cstDelete(data, () => toast.success(`Конституенты удалены: ${deletedNamed}`));
reload().then(() => toast.success(`Конституенты удалены: ${deletedNamed}`)); }, [selected, schema?.items, cstDelete]);
});
}, [selected, cstDelete, reload]);
const handleMoveUp = useCallback(() => { // Move selected cst up
toast.info('Перемещение вверх'); const handleMoveUp = useCallback(
() => {
}, []); if (!schema?.items || selected.length === 0) {
return;
}
const currentIndex = schema.items.reduce((prev, cst, index) => {
if (selected.indexOf(cst.id) < 0) {
return prev;
} else if (prev === -1) {
return index;
}
return Math.min(prev, index);
}, -1);
const insertIndex = Math.max(0, currentIndex - 1) + 1
const data = {
'items': selected.map(id => { return {'id': id }; }),
'move_to': insertIndex
}
cstMoveTo(data).then(() => toast.info('Перемещение вверх ' + insertIndex));
}, [selected, schema?.items, cstMoveTo]);
const handleMoveDown = useCallback(() => {
toast.info('Перемещение вниз'); // Move selected cst down
}, []); const handleMoveDown = useCallback(
async () => {
if (!schema?.items || selected.length === 0) {
return;
}
const currentIndex = schema.items.reduce((prev, cst, index) => {
if (selected.indexOf(cst.id) < 0) {
return prev;
} else if (prev === -1) {
return index;
}
return Math.max(prev, index);
}, -1);
const insertIndex = Math.min(schema.items.length - 1, currentIndex + 1) + 1
const data = {
'items': selected.map(id => { return {'id': id }; }),
'move_to': insertIndex
}
cstMoveTo(data).then(() => toast.info('Перемещение вниз ' + insertIndex));
}, [selected, schema?.items, cstMoveTo]);
// Generate new names for all constituents
const handleReindex = useCallback(() => { const handleReindex = useCallback(() => {
toast.info('Переиндексация'); toast.info('Переиндексация');
}, []); }, []);
// Add new constituent
const handleAddNew = useCallback((csttype?: CstType) => { const handleAddNew = useCallback((csttype?: CstType) => {
if (!csttype) { if (!csttype) {
setShowCstModal(true); setShowCstModal(true);
@ -63,20 +112,34 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
'alias': createAliasFor(csttype, schema!) 'alias': createAliasFor(csttype, schema!)
} }
if (selected.length > 0) { if (selected.length > 0) {
data['insert_after'] = selected[selected.length - 1].entityUID data['insert_after'] = selected[selected.length - 1]
} }
cstCreate(data, (response: AxiosResponse) => { cstCreate(data, (response: AxiosResponse) =>
reload().then(() => toast.success(`Добавлена конституента ${response.data['alias']}`)); toast.success(`Добавлена конституента ${response.data['new_cst']['alias']}`));
});
} }
}, [schema, selected, reload, cstCreate]); }, [schema, selected, cstCreate]);
// Implement hotkeys for working with constituents table
const handleTableKey = useCallback((event: React.KeyboardEvent<HTMLDivElement>) => {
if (!event.altKey) {
return;
}
if (!isEditable || selected.length === 0) {
return;
}
switch(event.key) {
case 'ArrowUp': handleMoveUp(); return;
case 'ArrowDown': handleMoveDown(); return;
}
console.log(event);
}, [isEditable, selected, handleMoveUp, handleMoveDown]);
const columns = useMemo(() => const columns = useMemo(() =>
[ [
{ {
name: 'ID', name: 'ID',
id: 'id', id: 'id',
selector: (cst: IConstituenta) => cst.entityUID, selector: (cst: IConstituenta) => cst.id,
omit: true, omit: true,
}, },
{ {
@ -239,6 +302,7 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
})} })}
</div>} </div>}
</div> </div>
<div className='w-full h-full' onKeyDown={handleTableKey} tabIndex={0}>
<DataTableThemed <DataTableThemed
data={schema!.items!} data={schema!.items!}
columns={columns} columns={columns}
@ -256,11 +320,12 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
selectableRows selectableRows
selectableRowsHighlight selectableRowsHighlight
onSelectedRowsChange={({selectedRows}) => setSelected(selectedRows)} onSelectedRowsChange={handleSelectionChange}
onRowDoubleClicked={onOpenEdit} onRowDoubleClicked={onOpenEdit}
onRowClicked={handleRowClicked} onRowClicked={handleRowClicked}
dense dense
/> />
</div>
</div> </div>
</>); </>);
} }

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import Label from '../../components/Common/Label'; import Label from '../../components/Common/Label';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
@ -30,18 +30,18 @@ function ExpressionEditor({
id, label, disabled, isActive, placeholder, value, setValue, id, label, disabled, isActive, placeholder, value, setValue,
toggleEditMode, setTypification, onChange toggleEditMode, setTypification, onChange
}: ExpressionEditorProps) { }: ExpressionEditorProps) {
const { schema, active } = useRSForm(); const { schema, activeCst } = useRSForm();
const [isModified, setIsModified] = useState(false); const [isModified, setIsModified] = useState(false);
const { parseData, checkExpression, resetParse, loading } = useCheckExpression({schema: schema}); const { parseData, checkExpression, resetParse, loading } = useCheckExpression({schema: schema});
const expressionCtrl = useRef<HTMLTextAreaElement>(null); const expressionCtrl = useRef<HTMLTextAreaElement>(null);
useEffect(() => { useLayoutEffect(() => {
setIsModified(false); setIsModified(false);
resetParse(); resetParse();
}, [active, resetParse]); }, [activeCst, resetParse]);
const handleCheckExpression = useCallback(() => { const handleCheckExpression = useCallback(() => {
const prefix = active?.alias + (active?.cstType === CstType.STRUCTURED ? '::=' : ':=='); const prefix = activeCst?.alias + (activeCst?.cstType === CstType.STRUCTURED ? '::=' : ':==');
const expression = prefix + value; const expression = prefix + value;
checkExpression(expression, (response: AxiosResponse) => { checkExpression(expression, (response: AxiosResponse) => {
// TODO: update cursor position // TODO: update cursor position
@ -49,7 +49,7 @@ function ExpressionEditor({
setTypification(response.data['typification']); setTypification(response.data['typification']);
toast.success('проверка завершена'); toast.success('проверка завершена');
}); });
}, [value, checkExpression, active, setTypification]); }, [value, checkExpression, activeCst, setTypification]);
const handleEdit = useCallback((id: TokenID, key?: string) => { const handleEdit = useCallback((id: TokenID, key?: string) => {
if (!expressionCtrl.current) { if (!expressionCtrl.current) {
@ -198,7 +198,7 @@ function ExpressionEditor({
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
{isActive && <StatusBar {isActive && <StatusBar
isModified={isModified} isModified={isModified}
constituenta={active} constituenta={activeCst}
parseData={parseData} parseData={parseData}
/>} />}
<Button <Button
@ -210,7 +210,7 @@ function ExpressionEditor({
{isActive && EditButtons} {isActive && EditButtons}
{!isActive && <StatusBar {!isActive && <StatusBar
isModified={isModified} isModified={isModified}
constituenta={active} constituenta={activeCst}
parseData={parseData} parseData={parseData}
/>} />}
</div> </div>

View File

@ -18,7 +18,7 @@ function RSFormCard() {
const intl = useIntl(); const intl = useIntl();
const { getUserLabel } = useUsers(); const { getUserLabel } = useUsers();
const { const {
schema, update, download, reload, schema, update, download,
isEditable, isOwned, isClaimable, processing, destroy, claim isEditable, isOwned, isClaimable, processing, destroy, claim
} = useRSForm(); } = useRSForm();
const { user } = useAuth(); const { user } = useAuth();
@ -43,10 +43,7 @@ function RSFormCard() {
'comment': comment, 'comment': comment,
'is_common': common, 'is_common': common,
}; };
update(data, () => { update(data).then(() => toast.success('Изменения сохранены'));
toast.success('Изменения сохранены');
reload();
});
}; };
const handleDelete = const handleDelete =
@ -107,7 +104,7 @@ function RSFormCard() {
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' } tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
disabled={!isClaimable || processing || !user} disabled={!isClaimable || processing || !user}
icon={<CrownIcon color={isOwned ? '' : 'text-green'}/>} icon={<CrownIcon color={isOwned ? '' : 'text-green'}/>}
onClick={() => claimOwnershipProc(claim, reload)} onClick={() => claimOwnershipProc(claim)}
/> />
<Button <Button
tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'} tooltip={ isEditable ? 'Удалить схему' : 'Вы не можете редактировать данную схему'}

View File

@ -2,7 +2,7 @@ import { Tabs, TabList, TabPanel } from 'react-tabs';
import ConstituentsTable from './ConstituentsTable'; import ConstituentsTable from './ConstituentsTable';
import { IConstituenta } from '../../utils/models'; import { IConstituenta } from '../../utils/models';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useEffect, useState } from 'react'; import { useEffect, useLayoutEffect, useState } from 'react';
import ConceptTab from '../../components/Common/ConceptTab'; import ConceptTab from '../../components/Common/ConceptTab';
import RSFormCard from './RSFormCard'; import RSFormCard from './RSFormCard';
import { Loader } from '../../components/Common/Loader'; import { Loader } from '../../components/Common/Loader';
@ -19,13 +19,13 @@ export enum RSFormTabsList {
} }
function RSFormTabs() { function RSFormTabs() {
const { setActive, active, error, schema, loading } = useRSForm(); const { setActiveID, activeCst, activeID, error, schema, loading } = useRSForm();
const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', RSFormTabsList.CARD); const [tabIndex, setTabIndex] = useLocalStorage('rsform_edit_tab', RSFormTabsList.CARD);
const [init, setInit] = useState(false); const [init, setInit] = useState(false);
const onEditCst = (cst: IConstituenta) => { const onEditCst = (cst: IConstituenta) => {
console.log(`Set active cst: ${cst.alias}`); console.log(`Set active cst: ${cst.alias}`);
setActive(cst); setActiveID(cst.id);
setTabIndex(RSFormTabsList.CST_EDIT) setTabIndex(RSFormTabsList.CST_EDIT)
}; };
@ -33,15 +33,15 @@ function RSFormTabs() {
setTabIndex(index); setTabIndex(index);
}; };
useEffect(() => { useLayoutEffect(() => {
if (schema) { if (schema) {
const url = new URL(window.location.href); const url = new URL(window.location.href);
const activeQuery = url.searchParams.get('active'); const activeQuery = url.searchParams.get('active');
const activeCst = schema?.items?.find((cst) => cst.entityUID === Number(activeQuery)) || undefined; const activeCst = schema?.items?.find((cst) => cst.id === Number(activeQuery)) || undefined;
setActive(activeCst); setActiveID(activeCst?.id);
setInit(true); setInit(true);
} }
}, [setActive, schema, setInit]); }, [setActiveID, schema, setInit]);
useEffect(() => { useEffect(() => {
const url = new URL(window.location.href); const url = new URL(window.location.href);
@ -54,13 +54,13 @@ function RSFormTabs() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
let currentActive = url.searchParams.get('active'); let currentActive = url.searchParams.get('active');
const currentTab = url.searchParams.get('tab'); const currentTab = url.searchParams.get('tab');
const saveHistory = tabIndex === RSFormTabsList.CST_EDIT && currentActive !== String(active?.entityUID); const saveHistory = tabIndex === RSFormTabsList.CST_EDIT && currentActive !== String(activeID);
if (currentTab !== String(tabIndex)) { if (currentTab !== String(tabIndex)) {
url.searchParams.set('tab', String(tabIndex)); url.searchParams.set('tab', String(tabIndex));
} }
if (active) { if (activeID) {
if (currentActive !== String(active.entityUID)) { if (currentActive !== String(activeID)) {
url.searchParams.set('active', String(active.entityUID)); url.searchParams.set('active', String(activeID));
} }
} else { } else {
url.searchParams.delete('active'); url.searchParams.delete('active');
@ -71,7 +71,7 @@ function RSFormTabs() {
window.history.replaceState(null, '', url.toString()); window.history.replaceState(null, '', url.toString());
} }
} }
}, [tabIndex, active, init]); }, [tabIndex, activeID, init]);
return ( return (
<div className='w-full'> <div className='w-full'>

View File

@ -15,17 +15,17 @@ function TablistTools() {
const navigate = useNavigate(); const navigate = useNavigate();
const {user} = useAuth(); const {user} = useAuth();
const { schema, const { schema,
isOwned, isEditable, isTracking, readonly, forceAdmin, isOwned, isEditable, isTracking, isReadonly: readonly, isForceAdmin: forceAdmin,
toggleTracking, toggleForceAdmin, toggleReadonly, toggleTracking, toggleForceAdmin, toggleReadonly,
claim, reload, destroy, download claim, destroy, download
} = useRSForm(); } = useRSForm();
const schemaMenu = useDropdown(); const schemaMenu = useDropdown();
const editMenu = useDropdown(); const editMenu = useDropdown();
const handleClaimOwner = useCallback(() => { const handleClaimOwner = useCallback(() => {
editMenu.hide(); editMenu.hide();
claimOwnershipProc(claim, reload); claimOwnershipProc(claim);
}, [claim, reload, editMenu]); }, [claim, editMenu]);
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
schemaMenu.hide(); schemaMenu.hide();
@ -69,31 +69,31 @@ function TablistTools() {
{ schemaMenu.isActive && { schemaMenu.isActive &&
<Dropdown> <Dropdown>
<DropdownButton onClick={handleShare}> <DropdownButton onClick={handleShare}>
<div className='inline-flex items-center gap-2 justify-start'> <div className='inline-flex items-center justify-start gap-2'>
<ShareIcon color='text-primary' size={4}/> <ShareIcon color='text-primary' size={4}/>
<p>Поделиться</p> <p>Поделиться</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={handleClone}> <DropdownButton onClick={handleClone}>
<div className='inline-flex items-center gap-2 justify-start'> <div className='inline-flex items-center justify-start gap-2'>
<CloneIcon color='text-primary' size={4}/> <CloneIcon color='text-primary' size={4}/>
<p>Клонировать</p> <p>Клонировать</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={handleDownload}> <DropdownButton onClick={handleDownload}>
<div className='inline-flex items-center gap-2 justify-start'> <div className='inline-flex items-center justify-start gap-2'>
<DownloadIcon color='text-primary' size={4}/> <DownloadIcon color='text-primary' size={4}/>
<p>Выгрузить файл Экстеор</p> <p>Выгрузить файл Экстеор</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleUpload}> <DropdownButton disabled={!isEditable} onClick={handleUpload}>
<div className='inline-flex items-center gap-2 justify-start'> <div className='inline-flex items-center justify-start gap-2'>
<UploadIcon color={isEditable ? 'text-red' : ''} size={4}/> <UploadIcon color={isEditable ? 'text-red' : ''} size={4}/>
<p>Загрузить из Экстеора</p> <p>Загрузить из Экстеора</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleDelete}> <DropdownButton disabled={!isEditable} onClick={handleDelete}>
<span className='inline-flex items-center gap-2 justify-start'> <span className='inline-flex items-center justify-start gap-2'>
<DumpBinIcon color={isEditable ? 'text-red' : ''} size={4} /> <DumpBinIcon color={isEditable ? 'text-red' : ''} size={4} />
<p>Удалить схему</p> <p>Удалить схему</p>
</span> </span>

View File

@ -143,7 +143,7 @@ export async function postClaimRSForm(target: string, request?: IFrontRequest) {
} }
export async function postCheckExpression(schema: string, request?: IFrontRequest) { export async function postCheckExpression(schema: string, request?: IFrontRequest) {
return AxiosPost({ AxiosPost({
title: `Check expression for RSForm id=${schema}: ${request?.data['expression']}`, title: `Check expression for RSForm id=${schema}: ${request?.data['expression']}`,
endpoint: `${config.url.BASE}rsforms/${schema}/check/`, endpoint: `${config.url.BASE}rsforms/${schema}/check/`,
request: request request: request
@ -151,7 +151,7 @@ export async function postCheckExpression(schema: string, request?: IFrontReques
} }
export async function postNewConstituenta(schema: string, request?: IFrontRequest) { export async function postNewConstituenta(schema: string, request?: IFrontRequest) {
return AxiosPost({ AxiosPost({
title: `New Constituenta for RSForm id=${schema}: ${request?.data['alias']}`, title: `New Constituenta for RSForm id=${schema}: ${request?.data['alias']}`,
endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`, endpoint: `${config.url.BASE}rsforms/${schema}/cst-create/`,
request: request request: request
@ -159,13 +159,21 @@ export async function postNewConstituenta(schema: string, request?: IFrontReques
} }
export async function postDeleteConstituenta(schema: string, request?: IFrontRequest) { export async function postDeleteConstituenta(schema: string, request?: IFrontRequest) {
return AxiosPost({ AxiosPost({
title: `Delete Constituents for RSForm id=${schema}: ${request?.data['items'].toString()}`, title: `Delete Constituents for RSForm id=${schema}: ${request?.data['items'].toString()}`,
endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`, endpoint: `${config.url.BASE}rsforms/${schema}/cst-multidelete/`,
request: request request: request
}); });
} }
export async function patchMoveConstituenta(schema: string, request?: IFrontRequest) {
AxiosPatch<IRSForm>({
title: `Moving Constituents for RSForm id=${schema}: ${JSON.stringify(request?.data['items'])} to ${request?.data['move_to']}`,
endpoint: `${config.url.BASE}rsforms/${schema}/cst-moveto/`,
request: request
});
}
// ====== Helper functions =========== // ====== Helper functions ===========
async function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) { async function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) {
@ -228,13 +236,14 @@ async function AxiosDelete({endpoint, request, title}: IAxiosRequest) {
}); });
} }
async function AxiosPatch({endpoint, request, title}: IAxiosRequest) { async function AxiosPatch<ReturnType>({endpoint, request, title}: IAxiosRequest) {
if (title) console.log(`[[${title}]] is being patrially updated`); if (title) console.log(`[[${title}]] is being patrially updated`);
if (request?.setLoading) request?.setLoading(true); if (request?.setLoading) request?.setLoading(true);
axios.patch(endpoint, request?.data) axios.patch<ReturnType>(endpoint, request?.data)
.then((response) => { .then((response) => {
if (request?.setLoading) request?.setLoading(false); if (request?.setLoading) request?.setLoading(false);
if (request?.onSucccess) request.onSucccess(response); if (request?.onSucccess) request.onSucccess(response);
return response.data;
}) })
.catch((error) => { .catch((error) => {
if (request?.setLoading) request?.setLoading(false); if (request?.setLoading) request?.setLoading(false);

View File

@ -74,7 +74,7 @@ export enum ParsingStatus {
// Constituenta data // Constituenta data
export interface IConstituenta { export interface IConstituenta {
entityUID: number id: number
alias: string alias: string
cstType: CstType cstType: CstType
convention?: string convention?: string

View File

@ -9,16 +9,12 @@ export function shareCurrentURLProc() {
} }
export async function claimOwnershipProc( export async function claimOwnershipProc(
claim: (callback: BackendCallback) => Promise<void>, claim: (callback: BackendCallback) => Promise<void>,
reload: Function
) { ) {
if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) { if (!window.confirm('Вы уверены, что хотите стать владельцем данной схемы?')) {
return; return;
} }
claim(() => { claim(() => toast.success('Вы стали владельцем схемы'));
toast.success('Вы стали владельцем схемы');
reload();
});
} }
export async function deleteRSFormProc( export async function deleteRSFormProc(