mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
Implement NewConstituenta + add selector component
This commit is contained in:
parent
c9e56e7146
commit
e58fd183e9
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.1 on 2023-05-18 18:00
|
||||
# Generated by Django 4.2.3 on 2023-07-23 11:55
|
||||
|
||||
import apps.rsform.models
|
||||
from django.conf import settings
|
||||
|
@ -49,7 +49,6 @@ class Migration(migrations.Migration):
|
|||
options={
|
||||
'verbose_name': 'Конституета',
|
||||
'verbose_name_plural': 'Конституенты',
|
||||
'unique_together': {('schema', 'alias'), ('schema', 'order')},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import json
|
||||
from django.db import models, transaction
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.core.exceptions import ValidationError
|
||||
from apps.users.models import User
|
||||
|
||||
import pyconcept
|
||||
|
||||
|
||||
class CstType(models.TextChoices):
|
||||
''' Type of constituenta '''
|
||||
|
@ -77,29 +80,34 @@ class RSForm(models.Model):
|
|||
''' Insert new constituenta at given position. All following constituents order is shifted by 1 position '''
|
||||
if position <= 0:
|
||||
raise ValidationError('Invalid position: should be positive integer')
|
||||
update_list = Constituenta.objects.filter(schema=self, order__gte=position)
|
||||
update_list = Constituenta.objects.only('id', 'order', 'schema').filter(schema=self, order__gte=position)
|
||||
for cst in update_list:
|
||||
cst.order += 1
|
||||
cst.save()
|
||||
return Constituenta.objects.create(
|
||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||
|
||||
result = Constituenta.objects.create(
|
||||
schema=self,
|
||||
order=position,
|
||||
alias=alias,
|
||||
csttype=type
|
||||
)
|
||||
self._recreate_order()
|
||||
return Constituenta.objects.get(pk=result.pk)
|
||||
|
||||
@transaction.atomic
|
||||
def insert_last(self, alias: str, type: CstType) -> 'Constituenta':
|
||||
''' Insert new constituenta at last position '''
|
||||
position = 1
|
||||
if self.constituents().exists():
|
||||
position += self.constituents().aggregate(models.Max('order'))['order__max']
|
||||
return Constituenta.objects.create(
|
||||
position += self.constituents().only('order').aggregate(models.Max('order'))['order__max']
|
||||
result = Constituenta.objects.create(
|
||||
schema=self,
|
||||
order=position,
|
||||
alias=alias,
|
||||
csttype=type
|
||||
)
|
||||
self._recreate_order()
|
||||
return Constituenta.objects.get(pk=result.pk)
|
||||
|
||||
@staticmethod
|
||||
@transaction.atomic
|
||||
|
@ -111,21 +119,7 @@ class RSForm(models.Model):
|
|||
comment=data.get('comment', ''),
|
||||
is_common=is_common
|
||||
)
|
||||
order = 1
|
||||
for cst in data['items']:
|
||||
# TODO: get rid of empty_term etc. Use None instead
|
||||
Constituenta.objects.create(
|
||||
alias=cst['alias'],
|
||||
schema=schema,
|
||||
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
|
||||
schema._create_cst_from_json(data['items'])
|
||||
return schema
|
||||
|
||||
def to_json(self) -> str:
|
||||
|
@ -133,7 +127,7 @@ class RSForm(models.Model):
|
|||
result = self._prepare_json_rsform()
|
||||
items = self.constituents().order_by('order')
|
||||
for cst in items:
|
||||
result['items'].append(self._prepare_json_cst(cst))
|
||||
result['items'].append(cst.to_json())
|
||||
return result
|
||||
|
||||
def __str__(self):
|
||||
|
@ -148,20 +142,37 @@ class RSForm(models.Model):
|
|||
'items': []
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _prepare_json_cst(cst: 'Constituenta') -> dict:
|
||||
return {
|
||||
'entityUID': cst.id,
|
||||
'type': 'constituenta',
|
||||
'cstType': cst.csttype,
|
||||
'alias': cst.alias,
|
||||
'convention': cst.convention,
|
||||
'term': cst.term,
|
||||
'definition': {
|
||||
'formal': cst.definition_formal,
|
||||
'text': cst.definition_text
|
||||
}
|
||||
}
|
||||
def _recreate_order(self):
|
||||
checked = json.loads(pyconcept.check_schema(json.dumps(self.to_json())))
|
||||
update_list = self.constituents().only('id', 'order')
|
||||
if (len(checked['items']) != update_list.count()):
|
||||
raise ValidationError
|
||||
order = 1
|
||||
for cst in checked['items']:
|
||||
id = cst['entityUID']
|
||||
for oldCst in update_list:
|
||||
if oldCst.id == id:
|
||||
oldCst.order = order
|
||||
order += 1
|
||||
break
|
||||
Constituenta.objects.bulk_update(update_list, ['order'])
|
||||
|
||||
def _create_cst_from_json(self, items):
|
||||
order = 1
|
||||
for cst in items:
|
||||
# TODO: get rid of empty_term etc. Use None instead
|
||||
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
|
||||
|
||||
|
||||
class Constituenta(models.Model):
|
||||
|
@ -208,7 +219,20 @@ class Constituenta(models.Model):
|
|||
class Meta:
|
||||
verbose_name = 'Конституета'
|
||||
verbose_name_plural = 'Конституенты'
|
||||
unique_together = (('schema', 'alias'), ('schema', 'order'))
|
||||
|
||||
def __str__(self):
|
||||
return self.alias
|
||||
|
||||
def to_json(self) -> str:
|
||||
return {
|
||||
'entityUID': self.id,
|
||||
'type': 'constituenta',
|
||||
'cstType': self.csttype,
|
||||
'alias': self.alias,
|
||||
'convention': self.convention,
|
||||
'term': self.term,
|
||||
'definition': {
|
||||
'formal': self.definition_formal,
|
||||
'text': self.definition_text
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,3 +27,9 @@ class ConstituentaSerializer(serializers.ModelSerializer):
|
|||
def update(self, instance: Constituenta, validated_data):
|
||||
instance.schema.save()
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class NewConstituentaSerializer(serializers.Serializer):
|
||||
alias = serializers.CharField(max_length=8)
|
||||
csttype = serializers.CharField(max_length=10)
|
||||
insert_after = serializers.IntegerField(required=False)
|
||||
|
|
|
@ -40,28 +40,6 @@ class TestConstituenta(TestCase):
|
|||
with self.assertRaises(IntegrityError):
|
||||
Constituenta.objects.create(alias='X1', order=1)
|
||||
|
||||
def test_alias_unique(self):
|
||||
alias = 'X1'
|
||||
|
||||
original = Constituenta.objects.create(alias=alias, order=1, schema=self.schema1)
|
||||
self.assertIsNotNone(original)
|
||||
|
||||
clone = Constituenta.objects.create(alias=alias, order=2, schema=self.schema2)
|
||||
self.assertNotEqual(clone, original)
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
Constituenta.objects.create(alias=alias, order=1, schema=self.schema1)
|
||||
|
||||
def test_order_unique(self):
|
||||
original = Constituenta.objects.create(alias='X1', order=1, schema=self.schema1)
|
||||
self.assertIsNotNone(original)
|
||||
|
||||
clone = Constituenta.objects.create(alias='X2', order=1, schema=self.schema2)
|
||||
self.assertNotEqual(clone, original)
|
||||
|
||||
with self.assertRaises(IntegrityError):
|
||||
Constituenta.objects.create(alias='X2', order=1, schema=self.schema1)
|
||||
|
||||
def test_create_default(self):
|
||||
cst = Constituenta.objects.create(
|
||||
alias='X1',
|
||||
|
@ -158,7 +136,7 @@ class TestRSForm(TestCase):
|
|||
cst3 = schema.insert_at(4, 'X3', CstType.BASE)
|
||||
cst2.refresh_from_db()
|
||||
cst1.refresh_from_db()
|
||||
self.assertEqual(cst3.order, 4)
|
||||
self.assertEqual(cst3.order, 3)
|
||||
self.assertEqual(cst3.schema, schema)
|
||||
self.assertEqual(cst2.order, 1)
|
||||
self.assertEqual(cst1.order, 2)
|
||||
|
@ -169,13 +147,25 @@ class TestRSForm(TestCase):
|
|||
cst1.refresh_from_db()
|
||||
self.assertEqual(cst4.order, 3)
|
||||
self.assertEqual(cst4.schema, schema)
|
||||
self.assertEqual(cst3.order, 5)
|
||||
self.assertEqual(cst3.order, 4)
|
||||
self.assertEqual(cst2.order, 1)
|
||||
self.assertEqual(cst1.order, 2)
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
schema.insert_at(0, 'X5', CstType.BASE)
|
||||
|
||||
def test_insert_at_reorder(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
schema.insert_at(1, 'X1', CstType.BASE)
|
||||
d1 = schema.insert_at(2, 'D1', CstType.TERM)
|
||||
d2 = schema.insert_at(1, 'D2', CstType.TERM)
|
||||
d1.refresh_from_db()
|
||||
self.assertEqual(d1.order, 3)
|
||||
self.assertEqual(d2.order, 2)
|
||||
|
||||
x2 = schema.insert_at(4, 'X2', CstType.BASE)
|
||||
self.assertEqual(x2.order, 2)
|
||||
|
||||
def test_insert_last(self):
|
||||
schema = RSForm.objects.create(title='Test')
|
||||
cst1 = schema.insert_last('X1', CstType.BASE)
|
||||
|
|
|
@ -171,6 +171,30 @@ class TestRSFormViewset(APITestCase):
|
|||
response = self.client.post(f'/api/rsforms/{self.rsform_owned.id}/claim/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_create_constituenta(self):
|
||||
data = json.dumps({'alias': 'X3', 'csttype': 'basic'})
|
||||
response = self.client.post(f'/api/rsforms/{self.rsform_unowned.id}/new-constituenta/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
schema = self.rsform_owned
|
||||
Constituenta.objects.create(schema=schema, alias='X1', csttype='basic', order=1)
|
||||
x2 = Constituenta.objects.create(schema=schema, alias='X2', csttype='basic', order=2)
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/new-constituenta/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['alias'], 'X3')
|
||||
x3 = Constituenta.objects.get(alias=response.data['alias'])
|
||||
self.assertEqual(x3.order, 3)
|
||||
|
||||
data = json.dumps({'alias': 'X4', 'csttype': 'basic', 'insert_after': x2.id})
|
||||
response = self.client.post(f'/api/rsforms/{schema.id}/new-constituenta/',
|
||||
data=data, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 201)
|
||||
self.assertEqual(response.data['alias'], 'X4')
|
||||
x4 = Constituenta.objects.get(alias=response.data['alias'])
|
||||
self.assertEqual(x4.order, 3)
|
||||
|
||||
|
||||
class TestFunctionalViews(APITestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -41,7 +41,8 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
return serializer.save()
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ['update', 'destroy', 'partial_update']:
|
||||
if self.action in ['update', 'destroy', 'partial_update',
|
||||
'new_constituenta']:
|
||||
permission_classes = [utils.ObjectOwnerOrAdmin]
|
||||
elif self.action in ['create', 'claim']:
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
@ -49,6 +50,21 @@ class RSFormViewSet(viewsets.ModelViewSet):
|
|||
permission_classes = [permissions.AllowAny]
|
||||
return [permission() for permission in permission_classes]
|
||||
|
||||
@action(detail=True, methods=['post'], url_path='new-constituenta')
|
||||
def new_constituenta(self, request, pk):
|
||||
''' View schema contents (including constituents) '''
|
||||
schema: models.RSForm = self.get_object()
|
||||
serializer = serializers.NewConstituentaSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
if ('insert_after' in serializer.validated_data):
|
||||
cstafter = models.Constituenta.objects.get(pk=serializer.validated_data['insert_after'])
|
||||
constituenta = schema.insert_at(cstafter.order + 1,
|
||||
serializer.validated_data['alias'],
|
||||
serializer.validated_data['csttype'])
|
||||
else:
|
||||
constituenta = schema.insert_last(serializer.validated_data['alias'], serializer.validated_data['csttype'])
|
||||
return Response(status=201, data=constituenta.to_json())
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def claim(self, request, pk=None):
|
||||
schema: models.RSForm = self.get_object()
|
||||
|
|
|
@ -8,9 +8,8 @@ from django.conf.urls.static import static
|
|||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', lambda request: redirect('docs/', permanent=True)),
|
||||
path('docs/', include_docs_urls(title='ConceptPortal API'),
|
||||
name='docs'),
|
||||
path('api/', include('apps.rsform.urls')),
|
||||
path('users/', include('apps.users.urls')),
|
||||
path('docs/', include_docs_urls(title='ConceptPortal API'), name='docs'),
|
||||
path('', lambda request: redirect('docs/', permanent=True)),
|
||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
211
rsconcept/frontend/package-lock.json
generated
211
rsconcept/frontend/package-lock.json
generated
|
@ -25,6 +25,7 @@
|
|||
"react-loader-spinner": "^5.3.4",
|
||||
"react-router-dom": "^6.12.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-select": "^5.7.4",
|
||||
"react-tabs": "^6.0.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
"styled-components": "^6.0.4",
|
||||
|
@ -2369,6 +2370,70 @@
|
|||
"postcss-selector-parser": "^6.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin": {
|
||||
"version": "11.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz",
|
||||
"integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.16.7",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/hash": "^0.9.1",
|
||||
"@emotion/memoize": "^0.8.1",
|
||||
"@emotion/serialize": "^1.1.2",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"convert-source-map": "^1.5.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"find-root": "^1.1.0",
|
||||
"source-map": "^0.5.7",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/babel-plugin/node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
|
||||
},
|
||||
"node_modules/@emotion/cache": {
|
||||
"version": "11.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz",
|
||||
"integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==",
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "^0.8.1",
|
||||
"@emotion/sheet": "^1.2.2",
|
||||
"@emotion/utils": "^1.2.1",
|
||||
"@emotion/weak-memoize": "^0.3.1",
|
||||
"stylis": "4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/cache/node_modules/stylis": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
|
||||
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw=="
|
||||
},
|
||||
"node_modules/@emotion/hash": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
|
||||
"integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ=="
|
||||
},
|
||||
"node_modules/@emotion/is-prop-valid": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz",
|
||||
|
@ -2382,6 +2447,46 @@
|
|||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
|
||||
"integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
|
||||
},
|
||||
"node_modules/@emotion/react": {
|
||||
"version": "11.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz",
|
||||
"integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@emotion/babel-plugin": "^11.11.0",
|
||||
"@emotion/cache": "^11.11.0",
|
||||
"@emotion/serialize": "^1.1.2",
|
||||
"@emotion/use-insertion-effect-with-fallbacks": "^1.0.1",
|
||||
"@emotion/utils": "^1.2.1",
|
||||
"@emotion/weak-memoize": "^0.3.1",
|
||||
"hoist-non-react-statics": "^3.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/serialize": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz",
|
||||
"integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==",
|
||||
"dependencies": {
|
||||
"@emotion/hash": "^0.9.1",
|
||||
"@emotion/memoize": "^0.8.1",
|
||||
"@emotion/unitless": "^0.8.1",
|
||||
"@emotion/utils": "^1.2.1",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/sheet": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz",
|
||||
"integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA=="
|
||||
},
|
||||
"node_modules/@emotion/stylis": {
|
||||
"version": "0.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz",
|
||||
|
@ -2392,6 +2497,24 @@
|
|||
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
|
||||
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
|
||||
},
|
||||
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz",
|
||||
"integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emotion/utils": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz",
|
||||
"integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg=="
|
||||
},
|
||||
"node_modules/@emotion/weak-memoize": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz",
|
||||
"integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
|
||||
|
@ -2485,6 +2608,19 @@
|
|||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz",
|
||||
"integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g=="
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.5.tgz",
|
||||
"integrity": "sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.17.0.tgz",
|
||||
|
@ -4330,6 +4466,14 @@
|
|||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.6.tgz",
|
||||
"integrity": "sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
|
@ -7008,6 +7152,15 @@
|
|||
"utila": "~0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
|
||||
|
@ -8316,6 +8469,11 @@
|
|||
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/find-root": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
|
||||
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
|
@ -12261,6 +12419,11 @@
|
|||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
|
@ -14975,6 +15138,26 @@
|
|||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/react-select": {
|
||||
"version": "5.7.4",
|
||||
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz",
|
||||
"integrity": "sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.0",
|
||||
"@emotion/cache": "^11.4.0",
|
||||
"@emotion/react": "^11.8.1",
|
||||
"@floating-ui/dom": "^1.0.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"use-isomorphic-layout-effect": "^1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-tabs": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.1.tgz",
|
||||
|
@ -14999,6 +15182,21 @@
|
|||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
@ -16923,6 +17121,19 @@
|
|||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-isomorphic-layout-effect": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
|
||||
"integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"react-loader-spinner": "^5.3.4",
|
||||
"react-router-dom": "^6.12.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-select": "^5.7.4",
|
||||
"react-tabs": "^6.0.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
"styled-components": "^6.0.4",
|
||||
|
|
|
@ -8,35 +8,38 @@ interface BackendErrorProps {
|
|||
}
|
||||
|
||||
function DescribeError(error: ErrorInfo) {
|
||||
console.log(error);
|
||||
if (!error) {
|
||||
return <p>Ошибки отсутствуют</p>;
|
||||
} else if (typeof error === 'string') {
|
||||
return <p>{error}</p>;
|
||||
} else if (axios.isAxiosError(error)) {
|
||||
if (!error?.response) {
|
||||
return <p>Нет ответа от сервера</p>;
|
||||
}
|
||||
if (error.response.status === 404) {
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<p>{`Обращение к несуществующему API`}</p>
|
||||
<PrettyJson data={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<p className='underline'>Ошибка</p>
|
||||
<p>{error.message}</p>
|
||||
{error.response.data && (<>
|
||||
<p className='mt-2 underline'>Описание</p>
|
||||
<PrettyJson data={error.response.data} />
|
||||
</>)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
} else if (!axios.isAxiosError(error)) {
|
||||
return <PrettyJson data={error} />;
|
||||
}
|
||||
if (!error?.response) {
|
||||
return <p>Нет ответа от сервера</p>;
|
||||
}
|
||||
if (error.response.status === 404) {
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<p>{`Обращение к несуществующему API`}</p>
|
||||
<PrettyJson data={error} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isHtml = error.response.headers['content-type'].includes('text/html');
|
||||
return (
|
||||
<div className='flex flex-col justify-start'>
|
||||
<p className='underline'>Ошибка</p>
|
||||
<p>{error.message}</p>
|
||||
{error.response.data && (<>
|
||||
<p className='mt-2 underline'>Описание</p>
|
||||
{ isHtml && <div dangerouslySetInnerHTML={{ __html: error.response.data }} /> }
|
||||
{ !isHtml && <PrettyJson data={error.response.data} />}
|
||||
</>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BackendError({error}: BackendErrorProps) {
|
||||
|
|
|
@ -26,6 +26,11 @@ function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, su
|
|||
if(onCancel) onCancel();
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
toggle();
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='fixed top-0 left-0 w-full h-full clr-modal opacity-50 z-50'>
|
||||
|
@ -41,7 +46,7 @@ function Modal({title, show, toggle, onSubmit, onCancel, canSubmit, children, su
|
|||
widthClass='min-w-[6rem] w-fit h-fit'
|
||||
colorClass='clr-btn-primary'
|
||||
disabled={!canSubmit}
|
||||
onClick={onSubmit}
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<Button
|
||||
text='Отмена'
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
import { InputHTMLAttributes } from 'react';
|
||||
import Label from './Label';
|
||||
|
||||
interface TextInputProps {
|
||||
interface TextInputProps
|
||||
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'className'> {
|
||||
id: string
|
||||
type: string
|
||||
label: string
|
||||
required?: boolean
|
||||
disabled?: boolean
|
||||
placeholder?: string
|
||||
widthClass?: string
|
||||
value?: any
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onFocus?: () => void
|
||||
}
|
||||
|
||||
function TextInput({
|
||||
id, type, required, label, disabled, placeholder, widthClass='w-full', value,
|
||||
onChange, onFocus
|
||||
id, required, label, widthClass='w-full',
|
||||
...props
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
|
||||
|
@ -27,12 +22,7 @@ function TextInput({
|
|||
<input id={id}
|
||||
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 truncate hover:text-clip '+ widthClass}
|
||||
required={required}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onFocus={onFocus}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IConstituenta, IRSForm } from '../utils/models';
|
|||
import { useRSFormDetails } from '../hooks/useRSFormDetails';
|
||||
import { ErrorInfo } from '../components/BackendError';
|
||||
import { useAuth } from './AuthContext';
|
||||
import { BackendCallback, deleteRSForm, getTRSFile, patchConstituenta, patchRSForm, postClaimRSForm } from '../utils/backendAPI';
|
||||
import { BackendCallback, deleteRSForm, getTRSFile, patchConstituenta, patchRSForm, postClaimRSForm, postNewConstituenta } from '../utils/backendAPI';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
interface IRSFormContext {
|
||||
|
@ -30,6 +30,7 @@ interface IRSFormContext {
|
|||
download: (callback: BackendCallback) => void
|
||||
|
||||
cstUpdate: (data: any, callback: BackendCallback) => void
|
||||
cstCreate: (data: any, callback: BackendCallback) => void
|
||||
}
|
||||
|
||||
export const RSFormContext = createContext<IRSFormContext>({
|
||||
|
@ -56,16 +57,17 @@ export const RSFormContext = createContext<IRSFormContext>({
|
|||
download: () => {},
|
||||
|
||||
cstUpdate: () => {},
|
||||
cstCreate: () => {},
|
||||
})
|
||||
|
||||
interface RSFormStateProps {
|
||||
id: string
|
||||
schemaID: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
||||
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||
const { user } = useAuth();
|
||||
const { schema, reload, error, setError, loading } = useRSFormDetails({target: id});
|
||||
const { schema, reload, error, setError, loading } = useRSFormDetails({target: schemaID});
|
||||
const [processing, setProcessing] = useState(false)
|
||||
const [active, setActive] = useState<IConstituenta | undefined>(undefined);
|
||||
|
||||
|
@ -91,7 +93,7 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
|
||||
async function update(data: any, callback?: BackendCallback) {
|
||||
setError(undefined);
|
||||
patchRSForm(id, {
|
||||
patchRSForm(schemaID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
|
@ -102,7 +104,7 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
|
||||
async function destroy(callback: BackendCallback) {
|
||||
setError(undefined);
|
||||
deleteRSForm(id, {
|
||||
deleteRSForm(schemaID, {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
|
@ -112,7 +114,7 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
|
||||
async function claim(callback: BackendCallback) {
|
||||
setError(undefined);
|
||||
postClaimRSForm(id, {
|
||||
postClaimRSForm(schemaID, {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
|
@ -122,7 +124,7 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
|
||||
async function download(callback: BackendCallback) {
|
||||
setError(undefined);
|
||||
getTRSFile(id, {
|
||||
getTRSFile(schemaID, {
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
|
@ -141,6 +143,17 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
});
|
||||
}
|
||||
|
||||
async function cstCreate(data: any, callback?: BackendCallback) {
|
||||
setError(undefined);
|
||||
postNewConstituenta(schemaID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
onError: error => setError(error),
|
||||
onSucccess: callback
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<RSFormContext.Provider value={{
|
||||
schema, error, loading, processing,
|
||||
|
@ -150,8 +163,8 @@ export const RSFormState = ({ id, children }: RSFormStateProps) => {
|
|||
toggleReadonly: () => setReadonly(prev => !prev),
|
||||
isOwned, isEditable, isClaimable,
|
||||
isTracking, toggleTracking,
|
||||
cstUpdate,
|
||||
reload, update, download, destroy, claim
|
||||
reload, update, download, destroy, claim,
|
||||
cstUpdate, cstCreate
|
||||
}}>
|
||||
{ children }
|
||||
</RSFormContext.Provider>
|
||||
|
|
|
@ -44,6 +44,7 @@ function LoginPage() {
|
|||
required
|
||||
type='text'
|
||||
value={username}
|
||||
autoFocus
|
||||
onChange={event => setUsername(event.target.value)}
|
||||
/>
|
||||
<TextInput id='password'
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
import { CstType, IConstituenta, ParsingStatus, ValueClass, inferStatus } from '../../utils/models'
|
||||
import { CstType, IConstituenta, INewCstData, ParsingStatus, ValueClass, inferStatus } from '../../utils/models'
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import DataTableThemed, { SelectionInfo } from '../../components/Common/DataTableThemed';
|
||||
import DataTableThemed from '../../components/Common/DataTableThemed';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import Button from '../../components/Common/Button';
|
||||
import { ArrowDownIcon, ArrowUpIcon, ArrowsRotateIcon, DumpBinIcon, SmallPlusIcon } from '../../components/Icons';
|
||||
import { toast } from 'react-toastify';
|
||||
import Divider from '../../components/Common/Divider';
|
||||
import { getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
||||
import { createAliasFor, getCstTypeLabel, getCstTypePrefix, getStatusInfo, getTypeLabel } from '../../utils/staticUI';
|
||||
import CreateCstModal from './CreateCstModal';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
interface ConstituentsTableProps {
|
||||
onOpenEdit: (cst: IConstituenta) => void
|
||||
}
|
||||
|
||||
function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
||||
const { schema, isEditable, } = useRSForm();
|
||||
const { schema, isEditable, cstCreate, reload } = useRSForm();
|
||||
const [selectedRows, setSelectedRows] = useState<IConstituenta[]>([]);
|
||||
const nothingSelected = useMemo(() => selectedRows.length === 0, [selectedRows]);
|
||||
|
||||
const [showCstModal, setShowCstModal] = useState(true);
|
||||
|
||||
const handleRowSelected = useCallback(
|
||||
({selectedRows} : SelectionInfo<IConstituenta>) => {
|
||||
setSelectedRows(selectedRows);
|
||||
}, []);
|
||||
const [showCstModal, setShowCstModal] = useState(false);
|
||||
|
||||
const handleRowClicked = useCallback(
|
||||
(cst: IConstituenta, event: React.MouseEvent<Element, MouseEvent>) => {
|
||||
|
@ -48,13 +44,23 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
|||
toast.info('Переиндексация');
|
||||
}, []);
|
||||
|
||||
const handleAddNew = useCallback((cstType?: CstType) => {
|
||||
if (!cstType) {
|
||||
const handleAddNew = useCallback((csttype?: CstType) => {
|
||||
if (!csttype) {
|
||||
setShowCstModal(true);
|
||||
} else {
|
||||
toast.info(`Новая конституента ${cstType || 'NEW'}`);
|
||||
let data: INewCstData = {
|
||||
csttype: csttype,
|
||||
alias: createAliasFor(csttype, schema!)
|
||||
}
|
||||
if (selectedRows.length > 0) {
|
||||
data['insert_after'] = selectedRows[selectedRows.length - 1].entityUID
|
||||
}
|
||||
cstCreate(data, (response: AxiosResponse) => {
|
||||
reload();
|
||||
toast.info(`Добавлена конституента ${response.data['alias']}`);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
}, [schema, selectedRows, reload, cstCreate]);
|
||||
|
||||
const columns = useMemo(() =>
|
||||
[
|
||||
|
@ -228,19 +234,20 @@ function ConstituentsTable({onOpenEdit}: ConstituentsTableProps) {
|
|||
data={schema!.items!}
|
||||
columns={columns}
|
||||
keyField='id'
|
||||
noDataComponent={<span className='p-2 flex flex-col justify-center text-center'>
|
||||
<p>Список пуст</p>
|
||||
<p>Создайте новую конституенту</p>
|
||||
</span>}
|
||||
noDataComponent={
|
||||
<span className='flex flex-col justify-center p-2 text-center'>
|
||||
<p>Список пуст</p>
|
||||
<p>Создайте новую конституенту</p>
|
||||
</span>
|
||||
}
|
||||
|
||||
striped
|
||||
highlightOnHover
|
||||
pointerOnHover
|
||||
|
||||
selectableRows
|
||||
// selectableRowSelected={(cst) => selectedRows.indexOf(cst) < -1}
|
||||
selectableRowsHighlight
|
||||
onSelectedRowsChange={handleRowSelected}
|
||||
onSelectedRowsChange={({selectedRows}) => setSelectedRows(selectedRows)}
|
||||
onRowDoubleClicked={onOpenEdit}
|
||||
onRowClicked={handleRowClicked}
|
||||
dense
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { toast } from 'react-toastify';
|
||||
import Modal from '../../components/Common/Modal';
|
||||
import { CstType } from '../../utils/models';
|
||||
import { useState } from 'react';
|
||||
import Select from 'react-select';
|
||||
import { CstTypeSelector } from '../../utils/staticUI';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface CreateCstModalProps {
|
||||
show: boolean
|
||||
|
@ -11,11 +12,17 @@ interface CreateCstModalProps {
|
|||
|
||||
function CreateCstModal({show, toggle, onCreate}: CreateCstModalProps) {
|
||||
const [validated, setValidated] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<CstType|undefined>(undefined);
|
||||
|
||||
const handleSubmit = () => {
|
||||
toast.info('Создание конституент');
|
||||
if (selectedType) onCreate(selectedType);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setValidated(selectedType !== undefined);
|
||||
}, [selectedType]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title='Создание конституенты'
|
||||
|
@ -24,8 +31,11 @@ function CreateCstModal({show, toggle, onCreate}: CreateCstModalProps) {
|
|||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<p>Выбор типа конституенты</p>
|
||||
<p>Добавить после выбранной или в конец</p>
|
||||
<Select
|
||||
options={CstTypeSelector}
|
||||
placeholder='Выберите тип'
|
||||
onChange={(data) => setSelectedType(data?.value)}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import RSFormTabs from './RSFormTabs';
|
|||
function RSFormPage() {
|
||||
const { id } = useParams();
|
||||
return (
|
||||
<RSFormState id={id || ''}>
|
||||
<RSFormState schemaID={id || ''}>
|
||||
<RSFormTabs />
|
||||
</RSFormState>
|
||||
);
|
||||
|
|
|
@ -150,6 +150,14 @@ export async function postCheckExpression(schema: string, request?: IFrontReques
|
|||
});
|
||||
}
|
||||
|
||||
export async function postNewConstituenta(schema: string, request?: IFrontRequest) {
|
||||
AxiosPost({
|
||||
title: `New Constituenta for RSForm id=${schema}: ${request?.data['alias']}`,
|
||||
endpoint: `${config.url.BASE}rsforms/${schema}/new-constituenta/`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ====== Helper functions ===========
|
||||
function AxiosGet<ReturnType>({endpoint, request, title}: IAxiosRequest) {
|
||||
|
|
|
@ -32,6 +32,13 @@ export interface IUserSignupData {
|
|||
password2: string
|
||||
}
|
||||
|
||||
// User data for signup
|
||||
export interface INewCstData {
|
||||
alias: string
|
||||
csttype: CstType
|
||||
insert_after?: number
|
||||
}
|
||||
|
||||
// Constituenta type
|
||||
export enum CstType {
|
||||
BASE = 'basic',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { CstType, ExpressionStatus, IConstituenta, ParsingStatus, TokenID } from './models';
|
||||
import { CstType, ExpressionStatus, IConstituenta, IRSForm, ParsingStatus, TokenID } from './models';
|
||||
|
||||
export interface IRSButtonData {
|
||||
text: string
|
||||
|
@ -195,6 +195,12 @@ export function getCstTypeLabel(type: CstType) {
|
|||
}
|
||||
}
|
||||
|
||||
export const CstTypeSelector = (Object.values(CstType)).map(
|
||||
(typeStr) => {
|
||||
const type = typeStr as CstType;
|
||||
return {value: type, label: getCstTypeLabel(type)};
|
||||
});
|
||||
|
||||
export function getCstTypePrefix(type: CstType) {
|
||||
switch(type) {
|
||||
case CstType.BASE: return 'X';
|
||||
|
@ -250,4 +256,20 @@ export function getStatusInfo(status?: ExpressionStatus): IStatusInfo {
|
|||
|
||||
export function extractGlobals(expression: string): Set<string> {
|
||||
return new Set(expression.match(/[XCSADFPT]\d+/g) || []);
|
||||
}
|
||||
|
||||
export function createAliasFor(type: CstType, schema: IRSForm): string {
|
||||
let index = 1;
|
||||
let prefix = getCstTypePrefix(type);
|
||||
let name = prefix + index;
|
||||
if (schema.items && schema.items.length > 0) {
|
||||
for (let i = 0; i < schema.items.length; ++i) {
|
||||
if (schema.items[i].alias === name) {
|
||||
++index;
|
||||
name = prefix + index;
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
Loading…
Reference in New Issue
Block a user