feat: passport images for user
This commit is contained in:
@@ -28,6 +28,7 @@ export type { BaseMarketplaceSchema } from './models/BaseMarketplaceSchema';
|
||||
export type { BaseShippingWarehouseSchema } from './models/BaseShippingWarehouseSchema';
|
||||
export type { BillPaymentStatus } from './models/BillPaymentStatus';
|
||||
export type { BillStatusUpdateRequest } from './models/BillStatusUpdateRequest';
|
||||
export type { Body_upload_passport_image } from './models/Body_upload_passport_image';
|
||||
export type { Body_upload_product_barcode_image } from './models/Body_upload_product_barcode_image';
|
||||
export type { Body_upload_product_image } from './models/Body_upload_product_image';
|
||||
export type { CancelDealBillRequest } from './models/CancelDealBillRequest';
|
||||
@@ -194,6 +195,7 @@ export type { MarketplaceCreateSchema } from './models/MarketplaceCreateSchema';
|
||||
export type { MarketplaceSchema } from './models/MarketplaceSchema';
|
||||
export type { NotificationChannel } from './models/NotificationChannel';
|
||||
export type { PaginationInfoSchema } from './models/PaginationInfoSchema';
|
||||
export type { PassportImageSchema } from './models/PassportImageSchema';
|
||||
export type { PaymentRecordCreateSchema } from './models/PaymentRecordCreateSchema';
|
||||
export type { PaymentRecordGetSchema } from './models/PaymentRecordGetSchema';
|
||||
export type { PayRateSchema } from './models/PayRateSchema';
|
||||
@@ -274,6 +276,7 @@ export type { UpdateTimeTrackingRecordRequest } from './models/UpdateTimeTrackin
|
||||
export type { UpdateTimeTrackingRecordResponse } from './models/UpdateTimeTrackingRecordResponse';
|
||||
export type { UpdateUserRequest } from './models/UpdateUserRequest';
|
||||
export type { UpdateUserResponse } from './models/UpdateUserResponse';
|
||||
export type { UploadPassportImageResponse } from './models/UploadPassportImageResponse';
|
||||
export type { UserCreate } from './models/UserCreate';
|
||||
export type { UserSchema } from './models/UserSchema';
|
||||
export type { UserUpdate } from './models/UserUpdate';
|
||||
|
||||
8
src/client/models/Body_upload_passport_image.ts
Normal file
8
src/client/models/Body_upload_passport_image.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type Body_upload_passport_image = {
|
||||
upload_file: Blob;
|
||||
};
|
||||
|
||||
10
src/client/models/PassportImageSchema.ts
Normal file
10
src/client/models/PassportImageSchema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type PassportImageSchema = {
|
||||
id: number;
|
||||
userId: number;
|
||||
imageUrl: string;
|
||||
};
|
||||
|
||||
10
src/client/models/UploadPassportImageResponse.ts
Normal file
10
src/client/models/UploadPassportImageResponse.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type UploadPassportImageResponse = {
|
||||
ok: boolean;
|
||||
message: string;
|
||||
imageUrl?: (string | null);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PassportImageSchema } from './PassportImageSchema';
|
||||
import type { PayRateSchema } from './PayRateSchema';
|
||||
export type UserCreate = {
|
||||
telegramId: number;
|
||||
@@ -16,6 +17,8 @@ export type UserCreate = {
|
||||
isDeleted: boolean;
|
||||
roleKey: string;
|
||||
payRate?: (PayRateSchema | null);
|
||||
passportImageUrl?: (string | null);
|
||||
passportImages?: (Array<PassportImageSchema> | null);
|
||||
positionKey?: (string | null);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PassportImageSchema } from './PassportImageSchema';
|
||||
import type { PayRateSchema } from './PayRateSchema';
|
||||
import type { PositionSchema } from './PositionSchema';
|
||||
import type { RoleSchema } from './RoleSchema';
|
||||
@@ -18,6 +19,8 @@ export type UserSchema = {
|
||||
isDeleted: boolean;
|
||||
roleKey: string;
|
||||
payRate?: (PayRateSchema | null);
|
||||
passportImageUrl?: (string | null);
|
||||
passportImages?: (Array<PassportImageSchema> | null);
|
||||
id: number;
|
||||
role: RoleSchema;
|
||||
position?: (PositionSchema | null);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { PassportImageSchema } from './PassportImageSchema';
|
||||
import type { PayRateSchema } from './PayRateSchema';
|
||||
export type UserUpdate = {
|
||||
telegramId: number;
|
||||
@@ -16,6 +17,8 @@ export type UserUpdate = {
|
||||
isDeleted: boolean;
|
||||
roleKey: string;
|
||||
payRate?: (PayRateSchema | null);
|
||||
passportImageUrl?: (string | null);
|
||||
passportImages?: (Array<PassportImageSchema> | null);
|
||||
id: number;
|
||||
positionKey?: (string | null);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import type { Body_upload_passport_image } from '../models/Body_upload_passport_image';
|
||||
import type { CreateUserRequest } from '../models/CreateUserRequest';
|
||||
import type { CreateUserResponse } from '../models/CreateUserResponse';
|
||||
import type { GetAllUsersResponse } from '../models/GetAllUsersResponse';
|
||||
import type { GetManagersResponse } from '../models/GetManagersResponse';
|
||||
import type { UpdateUserRequest } from '../models/UpdateUserRequest';
|
||||
import type { UpdateUserResponse } from '../models/UpdateUserResponse';
|
||||
import type { UploadPassportImageResponse } from '../models/UploadPassportImageResponse';
|
||||
import type { CancelablePromise } from '../core/CancelablePromise';
|
||||
import { OpenAPI } from '../core/OpenAPI';
|
||||
import { request as __request } from '../core/request';
|
||||
@@ -74,4 +76,29 @@ export class UserService {
|
||||
url: '/user/get-managers',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Upload Passport Image
|
||||
* @returns UploadPassportImageResponse Successful Response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static uploadPassportImage({
|
||||
userId,
|
||||
formData,
|
||||
}: {
|
||||
userId: number,
|
||||
formData: Body_upload_passport_image,
|
||||
}): CancelablePromise<UploadPassportImageResponse> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'POST',
|
||||
url: '/user/passport-images/upload/{user_id}',
|
||||
path: {
|
||||
'user_id': userId,
|
||||
},
|
||||
formData: formData,
|
||||
mediaType: 'multipart/form-data',
|
||||
errors: {
|
||||
422: `Validation Error`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,34 @@
|
||||
import { Dropzone, DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import { FC, useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Fieldset,
|
||||
Flex,
|
||||
Group,
|
||||
Image,
|
||||
Loader,
|
||||
rem,
|
||||
Text,
|
||||
} from "@mantine/core";
|
||||
import { FC } from "react";
|
||||
import { Button, Fieldset, Flex, Group, Image, Loader, rem, Text } from "@mantine/core";
|
||||
import { IconPhoto, IconUpload, IconX } from "@tabler/icons-react";
|
||||
import { omit } from "lodash";
|
||||
import { BaseFormInputProps } from "../../types/utils.ts";
|
||||
import { notifications } from "../../shared/lib/notifications.ts";
|
||||
import { ProductService } from "../../client";
|
||||
import UseImageDropzone from "../../types/UseImageDropzone.tsx";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
imageDropzone: UseImageDropzone;
|
||||
onDrop: (files: FileWithPath[]) => void;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ImageDropzone: FC<Props> = (props: Props) => {
|
||||
const [showDropzone, setShowDropzone] = useState(
|
||||
!(
|
||||
typeof props.imageUrlInputProps?.value === "string" &&
|
||||
props.imageUrlInputProps.value.trim() !== ""
|
||||
)
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const {
|
||||
showDropzone,
|
||||
setShowDropzone,
|
||||
isLoading,
|
||||
imageUrlInputProps,
|
||||
} = props.imageDropzone;
|
||||
|
||||
const restProps = omit(props, [
|
||||
"imageUrl",
|
||||
"productId",
|
||||
"imageUrlInputProps",
|
||||
]);
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!props.productId || !props.imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const file = files[0];
|
||||
// TODO check if file is image
|
||||
setIsLoading(true);
|
||||
ProductService.uploadProductImage({
|
||||
productId: props.productId,
|
||||
formData: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message, imageUrl }) => {
|
||||
notifications.guess(ok, { message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (!ok || !imageUrl) {
|
||||
setShowDropzone(true);
|
||||
|
||||
return;
|
||||
}
|
||||
props.imageUrlInputProps?.onChange(imageUrl);
|
||||
setShowDropzone(false);
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
setShowDropzone(true);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
const getBody = () => {
|
||||
return props.imageUrlInputProps?.value && !showDropzone ? (
|
||||
<Image src={props.imageUrlInputProps.value} />
|
||||
return imageUrlInputProps?.value && !showDropzone ? (
|
||||
<Image src={imageUrlInputProps.value} />
|
||||
) : (
|
||||
<Dropzone
|
||||
{...restProps}
|
||||
@@ -87,7 +44,7 @@ const ImageDropzone: FC<Props> = (props: Props) => {
|
||||
"image/heic",
|
||||
]}
|
||||
multiple={false}
|
||||
onDrop={onDrop}>
|
||||
onDrop={props.onDrop}>
|
||||
<Group
|
||||
justify="center"
|
||||
gap="xl"
|
||||
|
||||
26
src/hooks/useImageDropzone.tsx
Normal file
26
src/hooks/useImageDropzone.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useState } from "react";
|
||||
import { BaseFormInputProps } from "../types/utils.ts";
|
||||
|
||||
type Props = {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
}
|
||||
|
||||
const useImageDropzone = ({ imageUrlInputProps }: Props) => {
|
||||
const [showDropzone, setShowDropzone] = useState(
|
||||
!(
|
||||
typeof imageUrlInputProps?.value === "string" &&
|
||||
imageUrlInputProps.value.trim() !== ""
|
||||
),
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
return {
|
||||
showDropzone,
|
||||
setShowDropzone,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
imageUrlInputProps,
|
||||
};
|
||||
};
|
||||
|
||||
export default useImageDropzone;
|
||||
@@ -0,0 +1,60 @@
|
||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import { FC } from "react";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import { UserService } from "../../../../client";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import useImageDropzone from "../../../../hooks/useImageDropzone.tsx";
|
||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
userId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ProductImageDropzone: FC<Props> = ({ imageUrlInputProps, userId }: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!userId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
UserService.uploadPassportImage({
|
||||
userId: userId,
|
||||
formData: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message, imageUrl }) => {
|
||||
notifications.guess(ok, { message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (!ok || !imageUrl) {
|
||||
setShowDropzone(true);
|
||||
return;
|
||||
}
|
||||
imageUrlInputProps?.onChange(imageUrl);
|
||||
setShowDropzone(false);
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
setShowDropzone(true);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageDropzone onDrop={onDrop} imageDropzone={imageDropzoneProps} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductImageDropzone;
|
||||
@@ -10,6 +10,8 @@ import { capitalize } from "lodash";
|
||||
import { IMaskInput } from "react-imask";
|
||||
import phone from "phone";
|
||||
import PayRateSelect from "../../../../components/Selects/PayRateSelect/PayRateSelect.tsx";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import PassportImageDropzone from "../../components/PassportImageDropzone/PassportImageDropzone.tsx";
|
||||
|
||||
type Props = CreateEditFormProps<UserSchema>;
|
||||
const UserFormModal = ({
|
||||
@@ -107,6 +109,10 @@ const UserFormModal = ({
|
||||
{...form.getInputProps("phoneNumber")}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Паспортные данные"}>
|
||||
<Stack>
|
||||
<Input.Wrapper
|
||||
label={"Серия и номер паспорта"}
|
||||
error={form.getInputProps("passportData").error}>
|
||||
@@ -117,6 +123,18 @@ const UserFormModal = ({
|
||||
{...form.getInputProps("passportData")}
|
||||
/>
|
||||
</Input.Wrapper>
|
||||
{
|
||||
isEditing && (
|
||||
<PassportImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"passportImageUrl",
|
||||
) as BaseFormInputProps<string>
|
||||
}
|
||||
userId={innerProps?.element.id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Stack>
|
||||
</Fieldset>
|
||||
<Fieldset legend={"Роль и должность"}>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { DropzoneProps, FileWithPath } from "@mantine/dropzone";
|
||||
import { FC } from "react";
|
||||
import { notifications } from "../../../../shared/lib/notifications.ts";
|
||||
import { ProductService } from "../../../../client";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import useImageDropzone from "../../../../hooks/useImageDropzone.tsx";
|
||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
|
||||
|
||||
interface RestProps {
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
productId?: number;
|
||||
}
|
||||
|
||||
type Props = Omit<DropzoneProps, "onDrop"> & RestProps;
|
||||
|
||||
const ProductImageDropzone: FC<Props> = ({ imageUrlInputProps, productId }: Props) => {
|
||||
const imageDropzoneProps = useImageDropzone({
|
||||
imageUrlInputProps,
|
||||
});
|
||||
|
||||
const onDrop = (files: FileWithPath[]) => {
|
||||
if (!productId || !imageUrlInputProps) return;
|
||||
if (files.length > 1) {
|
||||
notifications.error({ message: "Прикрепите одно изображение" });
|
||||
return;
|
||||
}
|
||||
const { setIsLoading, setShowDropzone } = imageDropzoneProps;
|
||||
const file = files[0];
|
||||
|
||||
setIsLoading(true);
|
||||
ProductService.uploadProductImage({
|
||||
productId,
|
||||
formData: {
|
||||
upload_file: file,
|
||||
},
|
||||
})
|
||||
.then(({ ok, message, imageUrl }) => {
|
||||
notifications.guess(ok, { message });
|
||||
setIsLoading(false);
|
||||
|
||||
if (!ok || !imageUrl) {
|
||||
setShowDropzone(true);
|
||||
return;
|
||||
}
|
||||
imageUrlInputProps?.onChange(imageUrl);
|
||||
setShowDropzone(false);
|
||||
})
|
||||
.catch(error => {
|
||||
notifications.error({ message: error.toString() });
|
||||
setShowDropzone(true);
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ImageDropzone onDrop={onDrop} imageDropzone={imageDropzoneProps} />
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductImageDropzone;
|
||||
@@ -4,9 +4,9 @@ import { useForm } from "@mantine/form";
|
||||
import { BaseProduct, CreateProductRequest } from "../../types.ts";
|
||||
import { ProductSchema } from "../../../../client";
|
||||
import BarcodeTemplateSelect from "../../../../components/Selects/BarcodeTemplateSelect/BarcodeTemplateSelect.tsx";
|
||||
import ImageDropzone from "../../../../components/ImageDropzone/ImageDropzone.tsx";
|
||||
import { BaseFormInputProps } from "../../../../types/utils.ts";
|
||||
import BarcodeImageDropzone from "../../../../components/BarcodeImageDropzone/BarcodeImageDropzone.tsx";
|
||||
import ProductImageDropzone from "../../components/ProductImageDropzone/ProductImageDropzone.tsx";
|
||||
|
||||
type CreateProps = {
|
||||
clientId: number;
|
||||
@@ -117,7 +117,7 @@ const CreateProductModal = ({
|
||||
isEditProps && (
|
||||
// <Fieldset legend={"Изображение"}>
|
||||
<>
|
||||
<ImageDropzone
|
||||
<ProductImageDropzone
|
||||
imageUrlInputProps={
|
||||
form.getInputProps(
|
||||
"imageUrl",
|
||||
|
||||
12
src/types/UseImageDropzone.tsx
Normal file
12
src/types/UseImageDropzone.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { BaseFormInputProps } from "./utils.ts";
|
||||
|
||||
type UseImageDropzone = {
|
||||
showDropzone: boolean;
|
||||
setShowDropzone: Dispatch<SetStateAction<boolean>>;
|
||||
isLoading: boolean;
|
||||
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
||||
imageUrlInputProps?: BaseFormInputProps<string>;
|
||||
}
|
||||
|
||||
export default UseImageDropzone;
|
||||
Reference in New Issue
Block a user