diff --git a/rsconcept/backend/apps/library/serializers/data_access.py b/rsconcept/backend/apps/library/serializers/data_access.py index 0af30c46..a363f8ea 100644 --- a/rsconcept/backend/apps/library/serializers/data_access.py +++ b/rsconcept/backend/apps/library/serializers/data_access.py @@ -64,10 +64,12 @@ class VersionInnerSerializer(serializers.ModelSerializer): class VersionCreateSerializer(serializers.ModelSerializer): ''' Serializer: Version create data. ''' + items = PKField(many=True, required=False, default=None, queryset=Constituenta.objects.all().only('pk')) + class Meta: ''' serializer metadata. ''' model = Version - fields = 'version', 'description' + fields = 'version', 'description', 'items' class LibraryItemDetailsSerializer(serializers.ModelSerializer): diff --git a/rsconcept/backend/apps/library/tests/s_views/t_versions.py b/rsconcept/backend/apps/library/tests/s_views/t_versions.py index d5e60454..b6e139f5 100644 --- a/rsconcept/backend/apps/library/tests/s_views/t_versions.py +++ b/rsconcept/backend/apps/library/tests/s_views/t_versions.py @@ -6,6 +6,7 @@ from zipfile import ZipFile from rest_framework import status +from apps.library.models import Version from apps.rsform.models import Constituenta, RSForm from shared.EndpointTester import EndpointTester, decl_endpoint @@ -41,6 +42,20 @@ class TestVersionViews(EndpointTester): self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']]) + @decl_endpoint('/api/library/{schema}/create-version', method='post') + def test_create_version_filter(self): + x2 = self.owned.insert_new('X2') + data = {'version': '1.0.0', 'description': 'test', 'items': [x2.pk]} + response = self.executeCreated(data=data, schema=self.owned_id) + version = Version.objects.get(pk=response.data['version']) + items = version.data['items'] + self.assertTrue('version' in response.data) + self.assertTrue('schema' in response.data) + self.assertTrue(response.data['version'] in [v['id'] for v in response.data['schema']['versions']]) + self.assertEqual(len(items), 1) + self.assertEqual(items[0]['id'], x2.pk) + + @decl_endpoint('/api/library/{schema}/versions/{version}', method='get') def test_retrieve_version(self): version_id = self._create_version({'version': '1.0.0', 'description': 'test'}) diff --git a/rsconcept/backend/apps/library/views/versions.py b/rsconcept/backend/apps/library/views/versions.py index 8eed4423..813d1692 100644 --- a/rsconcept/backend/apps/library/views/versions.py +++ b/rsconcept/backend/apps/library/views/versions.py @@ -103,6 +103,9 @@ def create_version(request: Request, pk_item: int) -> HttpResponse: version_input = s.VersionCreateSerializer(data=request.data) version_input.is_valid(raise_exception=True) data = RSFormSerializer(item).to_versioned_data() + items: list[int] = [] if 'items' not in request.data else request.data['items'] + if items: + data['items'] = [cst for cst in data['items'] if cst['id'] in items] result = RSForm(item).create_version( version=version_input.validated_data['version'], description=version_input.validated_data['description'], diff --git a/rsconcept/frontend/src/backend/library.ts b/rsconcept/frontend/src/backend/library.ts index d401609c..a8990727 100644 --- a/rsconcept/frontend/src/backend/library.ts +++ b/rsconcept/frontend/src/backend/library.ts @@ -9,7 +9,7 @@ import { IRenameLocationData, ITargetAccessPolicy, ITargetLocation, - IVersionData + IVersionCreateData } from '@/models/library'; import { IRSFormCloneData, IRSFormData, IVersionCreatedResponse } from '@/models/rsform'; import { ITargetUser, ITargetUsers } from '@/models/user'; @@ -109,7 +109,7 @@ export function patchSetEditors(target: string, request: FrontPush }); } -export function postCreateVersion(target: string, request: FrontExchange) { +export function postCreateVersion(target: string, request: FrontExchange) { AxiosPost({ endpoint: `/api/library/${target}/create-version`, request: request diff --git a/rsconcept/frontend/src/components/Icons.tsx b/rsconcept/frontend/src/components/Icons.tsx index 4bc49e4d..eb41fa14 100644 --- a/rsconcept/frontend/src/components/Icons.tsx +++ b/rsconcept/frontend/src/components/Icons.tsx @@ -92,7 +92,7 @@ export { LuView as IconDBStructure } from 'react-icons/lu'; export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu'; export { LuImage as IconImage } from 'react-icons/lu'; export { TbColumns as IconList } from 'react-icons/tb'; -export { ImStack as IconVersions } from 'react-icons/im'; +export { GoVersions as IconVersions } from 'react-icons/go'; export { TbColumnsOff as IconListOff } from 'react-icons/tb'; export { LuAtSign as IconTerm } from 'react-icons/lu'; export { LuSubscript as IconAlias } from 'react-icons/lu'; @@ -122,6 +122,7 @@ export { BiLeftArrow as IconMoveLeft } from 'react-icons/bi'; export { TbHexagonPlus2 as IconNewRSForm } from 'react-icons/tb'; export { BiPlusCircle as IconNewItem } from 'react-icons/bi'; export { FaSquarePlus as IconNewItem2 } from 'react-icons/fa6'; +export { PiStackPlus as IconNewVersion } from 'react-icons/pi'; export { BiDuplicate as IconClone } from 'react-icons/bi'; export { LuReplace as IconReplace } from 'react-icons/lu'; export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa'; diff --git a/rsconcept/frontend/src/dialogs/DlgCreateVersion.tsx b/rsconcept/frontend/src/dialogs/DlgCreateVersion.tsx index 1f4897c5..c9eb5c7a 100644 --- a/rsconcept/frontend/src/dialogs/DlgCreateVersion.tsx +++ b/rsconcept/frontend/src/dialogs/DlgCreateVersion.tsx @@ -3,30 +3,38 @@ import clsx from 'clsx'; import { useMemo, useState } from 'react'; +import Checkbox from '@/components/ui/Checkbox'; import Modal, { ModalProps } from '@/components/ui/Modal'; import TextArea from '@/components/ui/TextArea'; import TextInput from '@/components/ui/TextInput'; -import { IVersionData, IVersionInfo } from '@/models/library'; +import { IVersionCreateData, IVersionInfo } from '@/models/library'; import { nextVersion } from '@/models/libraryAPI'; +import { ConstituentaID } from '@/models/rsform'; interface DlgCreateVersionProps extends Pick { versions: IVersionInfo[]; - onCreate: (data: IVersionData) => void; + onCreate: (data: IVersionCreateData) => void; + selected: ConstituentaID[]; + totalCount: number; } -function DlgCreateVersion({ hideWindow, versions, onCreate }: DlgCreateVersionProps) { +function DlgCreateVersion({ hideWindow, versions, selected, totalCount, onCreate }: DlgCreateVersionProps) { const [version, setVersion] = useState(versions.length > 0 ? nextVersion(versions[0].version) : '1.0.0'); const [description, setDescription] = useState(''); + const [onlySelected, setOnlySelected] = useState(false); const canSubmit = useMemo(() => { return !versions.find(ver => ver.version === version); }, [versions, version]); function handleSubmit() { - const data: IVersionData = { + const data: IVersionCreateData = { version: version, description: description }; + if (onlySelected) { + data.items = selected; + } onCreate(data); } @@ -55,6 +63,12 @@ function DlgCreateVersion({ hideWindow, versions, onCreate }: DlgCreateVersionPr value={description} onChange={event => setDescription(event.target.value)} /> + setOnlySelected(value)} + /> ); } diff --git a/rsconcept/frontend/src/models/library.ts b/rsconcept/frontend/src/models/library.ts index c2f86cc2..fe0b2678 100644 --- a/rsconcept/frontend/src/models/library.ts +++ b/rsconcept/frontend/src/models/library.ts @@ -2,6 +2,7 @@ * Module: Models for LibraryItem. */ +import { ConstituentaID } from './rsform'; import { UserID } from './user'; /** @@ -52,10 +53,17 @@ export interface IVersionInfo { } /** - * Represents user data, intended to create or update version metadata in persistent storage. + * Represents version data, intended to update version metadata in persistent storage. */ export interface IVersionData extends Omit {} +/** + * Create version metadata in persistent storage. + */ +export interface IVersionCreateData extends IVersionData { + items?: ConstituentaID[]; +} + /** * Represents library item common data typical for all item types. */ diff --git a/rsconcept/frontend/src/pages/ManualsPage/items/HelpVersions.tsx b/rsconcept/frontend/src/pages/ManualsPage/items/HelpVersions.tsx index 68c6c196..ef1f857b 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/items/HelpVersions.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/items/HelpVersions.tsx @@ -1,4 +1,4 @@ -import { IconEditor, IconNewItem, IconShare, IconUpload, IconVersions } from '@/components/Icons'; +import { IconEditor, IconNewVersion, IconShare, IconUpload, IconVersions } from '@/components/Icons'; function HelpVersions() { return ( @@ -18,7 +18,7 @@ function HelpVersions() { Загрузить версию в актуальную схему
  • - Создать версию можно только из актуальной + Создать версию можно только из актуальной схемы
  • diff --git a/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx b/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx index df8f58c6..123d67bd 100644 --- a/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx +++ b/rsconcept/frontend/src/pages/ManualsPage/items/ui/HelpRSMenu.tsx @@ -8,6 +8,7 @@ import { IconEdit2, IconEditor, IconMenu, + IconNewVersion, IconOwner, IconReader, IconShare, @@ -53,6 +54,9 @@ function HelpRSMenu() {
  • Клонировать – создать копию схемы
  • +
  • + Сохранить версию +
  • Выгрузить – сохранить в файле формата Экстеор
  • diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/ToolbarVersioning.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/ToolbarVersioning.tsx index 29dfec22..56b81e20 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/ToolbarVersioning.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorRSFormCard/ToolbarVersioning.tsx @@ -1,4 +1,4 @@ -import { IconNewItem, IconUpload, IconVersions } from '@/components/Icons'; +import { IconNewVersion, IconUpload, IconVersions } from '@/components/Icons'; import BadgeHelp from '@/components/info/BadgeHelp'; import MiniButton from '@/components/ui/MiniButton'; import Overlay from '@/components/ui/Overlay'; @@ -33,7 +33,7 @@ function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) { titleHtml={controller.isContentEditable ? 'Создать версию' : 'Переключитесь
    на актуальную версию'} disabled={!controller.isContentEditable} onClick={controller.createVersion} - icon={} + icon={} /> ) : null} + } + /> } diff --git a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx index a48d8ed0..8eb35e75 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/RSEditContext.tsx @@ -335,9 +335,8 @@ export const RSEditState = ({ if (!model.schema) { return; } - model.versionCreate(data, newVersion => { + model.versionCreate(data, () => { toast.success(information.newVersion(data.version)); - viewVersion(newVersion); }); }, [model, viewVersion] @@ -725,6 +724,8 @@ export const RSEditState = ({ versions={model.schema.versions} hideWindow={() => setShowCreateVersion(false)} onCreate={handleCreateVersion} + selected={selected} + totalCount={model.schema.items.length} /> ) : null} {showEditVersions ? (