mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
M: Improve location picker
This commit is contained in:
parent
ab6b07ed06
commit
9b4cf2bde2
|
@ -0,0 +1,65 @@
|
|||
'use client';
|
||||
|
||||
import { type FieldError } from 'react-hook-form';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { Label, TextArea } from '@/components/Input';
|
||||
import { type Styling } from '@/components/props';
|
||||
|
||||
import { LocationHead } from '../../models/library';
|
||||
import { combineLocation } from '../../models/libraryAPI';
|
||||
import { SelectLocationHead } from '../SelectLocationHead';
|
||||
|
||||
import { SelectLocationContext } from './SelectLocationContext';
|
||||
|
||||
interface PickLocationProps extends Styling {
|
||||
dropdownHeight?: string;
|
||||
rows?: number;
|
||||
|
||||
value: string;
|
||||
onChange: (newLocation: string) => void;
|
||||
error?: FieldError;
|
||||
}
|
||||
|
||||
export function PickLocation({
|
||||
dropdownHeight,
|
||||
rows = 3,
|
||||
value,
|
||||
onChange,
|
||||
error,
|
||||
className,
|
||||
...restProps
|
||||
}: PickLocationProps) {
|
||||
const { user } = useAuthSuspense();
|
||||
|
||||
return (
|
||||
<div className={clsx('flex', className)} {...restProps}>
|
||||
<div className='flex flex-col gap-2 min-w-28'>
|
||||
<Label className='select-none' text='Корень' />
|
||||
<SelectLocationHead
|
||||
value={value.substring(0, 2) as LocationHead}
|
||||
onChange={newValue => onChange(combineLocation(newValue, value.substring(3)))}
|
||||
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SelectLocationContext
|
||||
className='-mt-1 -ml-8'
|
||||
dropdownHeight={dropdownHeight} //
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id='dlg_location'
|
||||
label='Путь'
|
||||
rows={rows}
|
||||
value={value.substring(3)}
|
||||
onChange={event => onChange(combineLocation(value.substring(0, 2), event.target.value))}
|
||||
error={error}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -8,7 +8,7 @@ import { IconFolderTree } from '@/components/Icons';
|
|||
import { type Styling } from '@/components/props';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { SelectLocation } from './SelectLocation';
|
||||
import { SelectLocation } from '../SelectLocation';
|
||||
|
||||
interface SelectLocationContextProps extends Styling {
|
||||
value: string;
|
||||
|
@ -22,7 +22,7 @@ export function SelectLocationContext({
|
|||
title = 'Проводник...',
|
||||
onChange,
|
||||
className,
|
||||
dropdownHeight,
|
||||
dropdownHeight = 'h-50',
|
||||
...restProps
|
||||
}: SelectLocationContextProps) {
|
||||
const menu = useDropdown();
|
||||
|
@ -35,14 +35,14 @@ export function SelectLocationContext({
|
|||
}
|
||||
|
||||
return (
|
||||
<div ref={menu.ref} className={clsx('relative h-full -mt-1 -ml-6 text-right self-start', className)} {...restProps}>
|
||||
<div ref={menu.ref} className={clsx('relative text-right self-start', className)} {...restProps}>
|
||||
<MiniButton
|
||||
title={title}
|
||||
hideTitle={menu.isOpen}
|
||||
icon={<IconFolderTree size='1.25rem' className='icon-green' />}
|
||||
onClick={() => menu.toggle()}
|
||||
/>
|
||||
<Dropdown isOpen={menu.isOpen} className={clsx('w-80 h-50 z-tooltip', dropdownHeight)}>
|
||||
<Dropdown isOpen={menu.isOpen} className={clsx('w-80 z-tooltip', dropdownHeight)}>
|
||||
<SelectLocation
|
||||
value={value}
|
||||
prefix={prefixes.folders_list}
|
|
@ -0,0 +1 @@
|
|||
export { PickLocation } from './PickLocation';
|
|
@ -4,18 +4,13 @@ import { Controller, useForm } from 'react-hook-form';
|
|||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { Label, TextArea } from '@/components/Input';
|
||||
import { ModalForm } from '@/components/Modal';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { limits } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
|
||||
import { SelectLocationContext } from '../components/SelectLocationContext';
|
||||
import { SelectLocationHead } from '../components/SelectLocationHead';
|
||||
import { LocationHead } from '../models/library';
|
||||
import { combineLocation, validateLocation } from '../models/libraryAPI';
|
||||
import { PickLocation } from '../components/PickLocation';
|
||||
import { validateLocation } from '../models/libraryAPI';
|
||||
|
||||
const schemaLocation = z.strictObject({
|
||||
location: z.string().refine(data => validateLocation(data), { message: errorMsg.invalidLocation })
|
||||
|
@ -30,7 +25,6 @@ export interface DlgChangeLocationProps {
|
|||
|
||||
export function DlgChangeLocation() {
|
||||
const { initial, onChangeLocation } = useDialogsStore(state => state.props as DlgChangeLocationProps);
|
||||
const { user } = useAuthSuspense();
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
|
@ -56,39 +50,16 @@ export function DlgChangeLocation() {
|
|||
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.location_len}`}
|
||||
canSubmit={isValid && isDirty}
|
||||
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||
className='w-140 pb-3 px-6 flex gap-3 h-36'
|
||||
className='w-130 pb-3 px-6 h-36'
|
||||
>
|
||||
<div className='flex flex-col gap-2 min-w-28'>
|
||||
<Label className='select-none' text='Корень' />
|
||||
<Controller
|
||||
control={control}
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationHead
|
||||
value={field.value.substring(0, 2) as LocationHead}
|
||||
onChange={newValue => field.onChange(combineLocation(newValue, field.value.substring(3)))}
|
||||
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationContext dropdownHeight='max-h-36' value={field.value} onChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<TextArea
|
||||
id='dlg_location'
|
||||
label='Путь'
|
||||
rows={3}
|
||||
value={field.value.substring(3)}
|
||||
onChange={event => field.onChange(combineLocation(field.value.substring(0, 2), event.target.value))}
|
||||
<PickLocation
|
||||
dropdownHeight='h-38' //
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={errors.location}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -4,7 +4,6 @@ import { Controller, useForm } from 'react-hook-form';
|
|||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { MiniButton } from '@/components/Control';
|
||||
import { Checkbox, Label, TextArea, TextInput } from '@/components/Input';
|
||||
|
@ -14,11 +13,9 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { AccessPolicy, type ICloneLibraryItemDTO, type ILibraryItem, schemaCloneLibraryItem } from '../backend/types';
|
||||
import { useCloneItem } from '../backend/useCloneItem';
|
||||
import { IconItemVisibility } from '../components/IconItemVisibility';
|
||||
import { PickLocation } from '../components/PickLocation';
|
||||
import { SelectAccessPolicy } from '../components/SelectAccessPolicy';
|
||||
import { SelectLocationContext } from '../components/SelectLocationContext';
|
||||
import { SelectLocationHead } from '../components/SelectLocationHead';
|
||||
import { LocationHead } from '../models/library';
|
||||
import { cloneTitle, combineLocation } from '../models/libraryAPI';
|
||||
import { cloneTitle } from '../models/libraryAPI';
|
||||
|
||||
export interface DlgCloneLibraryItemProps {
|
||||
base: ILibraryItem;
|
||||
|
@ -32,7 +29,6 @@ export function DlgCloneLibraryItem() {
|
|||
state => state.props as DlgCloneLibraryItemProps
|
||||
);
|
||||
const router = useConceptNavigation();
|
||||
const { user } = useAuthSuspense();
|
||||
const { cloneItem } = useCloneItem();
|
||||
|
||||
const {
|
||||
|
@ -108,46 +104,13 @@ export function DlgCloneLibraryItem() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3'>
|
||||
<div className='flex flex-col gap-2 w-28'>
|
||||
<Label text='Корень' />
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationHead
|
||||
value={field.value.substring(0, 2) as LocationHead}
|
||||
onChange={newValue => field.onChange(combineLocation(newValue, field.value.substring(3)))}
|
||||
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationContext
|
||||
value={field.value} //
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<TextArea
|
||||
id='dlg_location'
|
||||
label='Путь'
|
||||
rows={3}
|
||||
value={field.value.substring(3)}
|
||||
onChange={event => field.onChange(combineLocation(field.value.substring(0, 2), event.target.value))}
|
||||
error={errors.location}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control}
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<PickLocation value={field.value} rows={2} onChange={field.onChange} error={errors.location} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<TextArea id='dlg_comment' {...register('comment')} label='Описание' rows={4} error={errors.comment} />
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Controller, useForm, useWatch } from 'react-hook-form';
|
|||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
import { urls, useConceptNavigation } from '@/app';
|
||||
import { useAuthSuspense } from '@/features/auth';
|
||||
|
||||
import { Button, MiniButton, SubmitButton } from '@/components/Control';
|
||||
import { IconDownload } from '@/components/Icons';
|
||||
|
@ -21,17 +20,14 @@ import {
|
|||
} from '../../backend/types';
|
||||
import { useCreateItem } from '../../backend/useCreateItem';
|
||||
import { IconItemVisibility } from '../../components/IconItemVisibility';
|
||||
import { PickLocation } from '../../components/PickLocation';
|
||||
import { SelectAccessPolicy } from '../../components/SelectAccessPolicy';
|
||||
import { SelectItemType } from '../../components/SelectItemType';
|
||||
import { SelectLocationContext } from '../../components/SelectLocationContext';
|
||||
import { SelectLocationHead } from '../../components/SelectLocationHead';
|
||||
import { LocationHead } from '../../models/library';
|
||||
import { combineLocation } from '../../models/libraryAPI';
|
||||
import { useLibrarySearchStore } from '../../stores/librarySearch';
|
||||
|
||||
export function FormCreateItem() {
|
||||
const router = useConceptNavigation();
|
||||
const { user } = useAuthSuspense();
|
||||
const { createItem, isPending, error: serverError, reset: clearServerError } = useCreateItem();
|
||||
|
||||
const searchLocation = useLibrarySearchStore(state => state.location);
|
||||
|
@ -196,6 +192,14 @@ export function FormCreateItem() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<PickLocation value={field.value} rows={2} onChange={field.onChange} error={errors.location} />
|
||||
)}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id='schema_comment'
|
||||
{...register('comment')}
|
||||
|
@ -204,47 +208,6 @@ export function FormCreateItem() {
|
|||
error={errors.comment}
|
||||
/>
|
||||
|
||||
<div className='flex justify-between gap-3 grow'>
|
||||
<div className='flex flex-col gap-2 min-w-28'>
|
||||
<Label text='Корень' />
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationHead
|
||||
value={field.value.substring(0, 2) as LocationHead}
|
||||
onChange={newValue => field.onChange(combineLocation(newValue, field.value.substring(3)))}
|
||||
excluded={!user.is_staff ? [LocationHead.LIBRARY] : []}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<SelectLocationContext
|
||||
value={field.value} //
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control} //
|
||||
name='location'
|
||||
render={({ field }) => (
|
||||
<TextArea
|
||||
id='location'
|
||||
label='Путь'
|
||||
rows={4}
|
||||
value={field.value.substring(3)}
|
||||
onChange={event => field.onChange(combineLocation(field.value.substring(0, 2), event.target.value))}
|
||||
error={errors.location}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-around gap-6 py-3'>
|
||||
<SubmitButton text='Создать схему' loading={isPending} className='min-w-40' />
|
||||
<Button text='Отмена' className='min-w-40' onClick={() => handleCancel()} />
|
||||
|
|
Loading…
Reference in New Issue
Block a user